Compare commits

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

No files matched your search

+1 -3
View File
@@ -4,8 +4,6 @@
/build_test_run/
/coverage/
/dist/
/gen/
/large_project/
/doc/
/pkg/
/test/
/yard/
+70 -103
View File
@@ -1,113 +1,80 @@
## v2.0.0
## ChangeLog
- convert rscons from a Ruby gem to a standalone script
- compress rscons distributable script
- add configure operation to detect compilers, check for headers/libraries, etc... (invoked automatically if needed)
- Environments store builder classes instead of instances of builder classes
- use a separate Builder instance for each build operation
- load Rsconscript from Rsconscript/Rsconscript.rb instead of Rsconsfile
- drop support for builder run methods using the old 5 parameter signature
- remove Environment#build_dir
- set Environment build root in configure step
- remove Builder#finalize (now #run called repeatedly until builder completes)
- remove Builder#setup
- remove Builder#features and Builder#produces?
- add functionality to allow builders to wait on Ruby threads or other builders
- add install/uninstall/distclean command-line operations
- preserve makefile dependency files under build directory
- remove a few deprecated methods
- pass a Builder instance to build hooks instead of a build operation Hash
- support a basic markup syntax in builder run messages to colorize target/source files
- hide (but store) failed compilation command by default so the user doesn't have to scroll back as much to see compiler output
- refactor to remove some redundancy among built-in builders
- track object file source language (correctly determine linker when only passed object files previously built by particular toolchains)
- add barriers
- add InstallDirectory builder
- change Install builder to copy files on 'install' operation
- add "prefix" construction variable based on configured installation prefix
- allow passing builder objects as sources to build targets
- differentiate 'build' targets from 'install' targets in cache contents
- add verbose mode
- show build progress as a percentage in builder output messages
- various performance improvements
- wrote a new user guide
- added new website ([https://holtrop.github.io/rscons/](https://holtrop.github.io/rscons/))
### v1.17.0
## v1.17.0
### New Features
#### New Features
- allow construction variable expansion on `true` and `false` values.
- remove makefile target name check when parsing dependencies
## v1.16.0
### v1.16.0
### New Features
#### New Features
- Add `Rscons.glob`
- 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
### Fixes
#### Fixes
- generate dependencies for D builds
## v1.15.0
### v1.15.0
- 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
## 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
### Fixes
#### Fixes
- #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
## v1.11.0
### v1.11.0
### New Features
#### New Features
- Change default Environment :clone option to :all to clone all attributes
- #38 - raise error when circular dependencies are found
- #34 - Allow overriding `n_threads` on a per-Environment level
- #34 - Allow overriding n_threads on a per-Environment level
### Fixes
#### Fixes
- #35 - `env.build_after` should expand paths
- #36 - `SHCFLAGS` and `SHCXXFLAGS` should inherit non-SH flags by default
- #37 - Fix non-blocking thread-wait if `Rscons.n_threads` is set to 0
- #35 - env.build_after should expand paths
- #36 - SHCFLAGS and SHCXXFLAGS should inherit non-SH flags by default
- #37 - Fix non-blocking thread-wait if Rscons.n_threads is set to 0
## v1.10.0
### v1.10.0
### New Features
#### New Features
- #23 - add parallelization - builds are now parallelized by default
- #31 - add LEXSUFFIX, YACCSUFFIX construction variables
@@ -117,100 +84,100 @@
- Add builder features
- #8 - add SharedObject and SharedLibrary builders
### Fixes
#### Fixes
- expand target and source paths before calling `Builder#create_build_target`
- #29 - fix `PROGSUFFIX` handling
- expand target and source paths before calling Builder#create_build_target
- #29 - fix PROGSUFFIX handling
- #32 - Pre-build hooks do not respect modified key values
## v1.9.3
### 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
## v1.9.1
### v1.9.1
- change *SUFFIX defaults to arrays
- add various C++ file suffixes
- use ${INCPREFIX} instead of hard-coded "-I" in Preprocess builder
## v1.9.0
### v1.9.0
### New Features
#### New Features
- #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
- #25 - add support for phony targets given as Symbols instead of Strings
- #26 - support registering multiple build targets with the same target name
- #27 - add Directory builder
### Fixes
#### Fixes
- #20 - fix variable references that expand to arrays in build target sources
- #21 - rework Preprocess builder to consider deep dependencies
- fix `Rscons.set_suffix` to append the given suffix if the filename has none
- remove ${CFLAGS} from default `CPP_CMD`
- fix Rscons.set_suffix to append the given suffix if the filename has none
- remove ${CFLAGS} from default CPP_CMD
## v1.8.1
### v1.8.1
- fix Environment#dump when construction variables are symbols
## v1.8.0
### v1.8.0
- new Command builder to execute arbitrary user commands
- new SimpleBuilder class
- create new builders quickly by passing a block to `Environment#add_builder`
- create new builders quickly by passing a block to Environment#add_builder
- improved YARD documentation
- add Environment#dump to debug Environment construction variables
## v1.7.0
### v1.7.0
- 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
- 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
- fix Environment#depends to expand its arguments for construction variables
## v1.6.0
### v1.6.0
- support lambdas as construction variable values
## v1.5.0
### v1.5.0
- add "json" as a runtime dependency
- update construction variables to match SCons more closely
- add `CPPDEFPREFIX`, `INCPREFIX`, `CPPDEFINES`, `CCFLAGS`, `LIBDIRPREFIX`, and `LIBLINKPREFIX`
- add `Environment#shell`
- add `Environment#parse_flags`, `#parse_flags!`, `#merge_flags`
- unbuffer `$stdout` by default
- add `PROGSUFFIX` construction variable (defaults to `.exe` on MinGW/Cygwin)
- add `Rscons::BuildTarget` and `Builder#create_build_target`
- add CPPDEFPREFIX, INCPREFIX, CPPDEFINES, CCFLAGS, LIBDIRPREFIX, and LIBLINKPREFIX
- add Environment#shell
- add Environment#parse_flags, #parse_flags!, #merge_flags
- unbuffer $stdout by default
- add PROGSUFFIX construction variable (defaults to .exe on MinGW/Cygwin)
- add Rscons::BuildTarget and Builder#create_build_target
- update specs to RSpec 3.x and fix to run on MinGW/Cygwin/Linux
- add YARD documentation to get to 100% coverage
## v1.4.3
### v1.4.3
- fix builders properly using construction variable overrides
- 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
## v1.4.1
### v1.4.1
- fix invoking a builder with no sources while a build root defined
## v1.4.0
### v1.4.0
- add CFile builder
- add Disassemble builder
@@ -223,21 +190,21 @@
- implement copy-on-write semantics for construction variables when cloning Environments
- only load the cache once instead of on each Environment#process
- only write the cache when something has changed
- fix `Cache#mkdir_p` to handle relative paths (Issue #5)
- fix Cache#mkdir_p to handle relative paths (Issue #5)
- flush the cache to disk if a builder raises an exception (Issue #4)
## v1.3.0
### v1.3.0
- change Environment#execute() options parameter to accept the following options keys:
- :env to pass an environment Hash to Kernel#system
- :options to pass an options Hash to Kernel#system
## v1.2.0
### v1.2.0
- add :clone option to Environment#clone to control exactly which Environment attributes are cloned
- allow nil to be passed in to `Environment#build_root=`
- 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
-2
View File
@@ -6,5 +6,3 @@ gem "rake"
gem "simplecov"
gem "yard"
gem "rdoc"
gem "redcarpet"
gem "syntax"
+17 -21
View File
@@ -2,31 +2,29 @@ GEM
remote: https://rubygems.org/
specs:
diff-lcs (1.3)
docile (1.3.2)
json (2.2.0)
rake (12.3.2)
rdoc (6.1.1)
redcarpet (3.4.0)
rspec (3.8.0)
rspec-core (~> 3.8.0)
rspec-expectations (~> 3.8.0)
rspec-mocks (~> 3.8.0)
rspec-core (3.8.2)
rspec-support (~> 3.8.0)
rspec-expectations (3.8.4)
docile (1.1.5)
json (2.1.0)
rake (12.3.1)
rdoc (5.1.0)
rspec (3.6.0)
rspec-core (~> 3.6.0)
rspec-expectations (~> 3.6.0)
rspec-mocks (~> 3.6.0)
rspec-core (3.6.0)
rspec-support (~> 3.6.0)
rspec-expectations (3.6.0)
diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.8.0)
rspec-mocks (3.8.1)
rspec-support (~> 3.6.0)
rspec-mocks (3.6.0)
diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.8.0)
rspec-support (3.8.2)
rspec-support (~> 3.6.0)
rspec-support (3.6.0)
simplecov (0.15.0)
docile (~> 1.1.0)
json (>= 1.8, < 3)
simplecov-html (~> 0.10.0)
simplecov-html (0.10.2)
syntax (1.2.2)
yard (0.9.20)
yard (0.9.12)
PLATFORMS
ruby
@@ -36,11 +34,9 @@ DEPENDENCIES
json
rake
rdoc
redcarpet
rspec
simplecov
syntax
yard
BUNDLED WITH
1.17.3
1.16.2
+2 -2
View File
@@ -1,4 +1,4 @@
Copyright (c) 2013-2019 Josh Holtrop
Copyright (c) 2013 Josh Holtrop
MIT License
@@ -19,4 +19,4 @@ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+587 -4
View File
@@ -1,8 +1,591 @@
# Rscons
Rscons (https://github.com/holtrop/rscons) is an open-source build system
for developers.
Rscons (https://github.com/holtrop/rscons) is a software construction framework
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 "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]
task :build_dist do
@@ -39,57 +39,6 @@ end
YARD::Rake::YardocTask.new do |yard|
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
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
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
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;
}
+5 -5
View File
@@ -7,10 +7,10 @@ build do
env.depends("${build_root}/program.o", "${inc_h}")
env.Program("program.exe", ["program.c", "inc.c"])
inc_c = env.Command("inc.c",
[],
"CMD" => %w[ruby gen.rb ${_TARGET}],
"CMD_DESC" => "Generating")
inc_c.produces("inc.h")
env.Command("inc.c",
[],
"CMD" => %w[ruby gen.rb ${_TARGET}],
"CMD_DESC" => "Generating")
env.produces("inc.c", "inc.h")
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
Environment.new(echo: :command) do |env|
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
Environment.new do |env|
env["LIBS"] += ["m"]
env["LIBS"] += ["c"]
env.Program('simple.exe', Dir['*.c'])
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
Environment.new do |env|
env["CPPPATH"] += glob("src/**")
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")
env.Install("inst.exe", "install.rb")
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,
:Disassemble,
:Install,
:InstallDirectory,
:Library,
:Object,
:Preprocess,
@@ -146,9 +145,9 @@ require_relative "rscons/builders/mixins/program"
# default builders
require_relative "rscons/builders/cfile"
require_relative "rscons/builders/command"
require_relative "rscons/builders/copy"
require_relative "rscons/builders/directory"
require_relative "rscons/builders/disassemble"
require_relative "rscons/builders/install"
require_relative "rscons/builders/library"
require_relative "rscons/builders/object"
require_relative "rscons/builders/preprocess"
-1
View File
@@ -3,7 +3,6 @@ module Rscons
module Ansi
class << self
# ANSI terminal reset sequence.
RESET = "\e[0m"
# Write a message to an IO with ANSI escape codes.
+45 -111
View File
@@ -1,5 +1,3 @@
require "set"
module Rscons
# Functionality for an instance of the rscons application invocation.
@@ -25,19 +23,6 @@ module Rscons
def initialize
@n_threads = Util.determine_n_threads
@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
# Run the specified operation.
@@ -48,72 +33,34 @@ module Rscons
# The script.
# @param operation_options [Hash]
# 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]
# Process exit code (0 on success).
def run(operation, script, operation_options, options = {})
@start_time = Time.new
def run(operation, script, operation_options)
@script = script
@operations << operation
puts "Starting '#{operation}' at #{Time.new}" if verbose
rv =
case operation
when "build"
rv = 0
unless Cache.instance["configuration_data"]["configured"]
rv =
if @script.autoconf
run("configure", script, operation_options, sub_op: false)
else
$stderr.puts "Project must be configured first, and autoconf is disabled"
1
end
case operation
when "build"
unless Cache.instance["configuration_data"]["configured"]
if @script.autoconf
rv = run("configure", script, operation_options)
if rv != 0
return rv
end
else
$stderr.puts "Project must be configured first, and autoconf is disabled"
return 1
end
if rv == 0
build(operation_options)
end
when "clean"
clean
when "configure"
configure(operation_options)
when "distclean"
distclean
when "install"
run("build", script, operation_options, sub_op: false)
when "uninstall"
uninstall
else
$stderr.puts "Unknown operation: #{operation}"
1
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
build(operation_options)
when "clean"
clean
when "configure"
configure(operation_options)
when "distclean"
distclean
else
$stderr.puts "Unknown operation: #{operation}"
1
end
end
@@ -147,19 +94,17 @@ module Rscons
def clean
cache = Cache.instance
# remove all built files
cache.targets(false).each do |target|
cache.remove_target(target)
cache.targets.each do |target|
FileUtils.rm_f(target)
end
# remove all created directories if they are empty
cache.directories(false).sort {|a, b| b.size <=> a.size}.each do |directory|
cache.remove_directory(directory)
cache.directories.sort {|a, b| b.size <=> a.size}.each do |directory|
next unless File.directory?(directory)
if (Dir.entries(directory) - ['.', '..']).empty?
Dir.rmdir(directory) rescue nil
end
end
cache.write
cache.clear
0
end
@@ -171,9 +116,7 @@ module Rscons
cache = Cache.instance
build_dir = cache["configuration_data"]["build_dir"]
clean
if build_dir
FileUtils.rm_rf(build_dir)
end
FileUtils.rm_rf(build_dir)
cache.clear
0
end
@@ -186,41 +129,32 @@ module Rscons
# @return [Integer]
# Exit code.
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
options = options.merge(project_name: @script.project_name)
co = ConfigureOp.new(options)
co = ConfigureOp.new("#{options[:build_dir]}/configure")
begin
@script.configure(co)
rescue ConfigureOp::ConfigureFailure
rv = 1
end
co.close(rv == 0)
rv
end
# 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
co.close
cache["configuration_data"]["build_dir"] = options[:build_dir]
cache["configuration_data"]["prefix"] = options[:prefix]
cache["configuration_data"]["configured"] = rv == 0
cache.write
0
rv
end
end
+15 -63
View File
@@ -4,52 +4,27 @@ module Rscons
# Create a BasicEnvironment object.
#
# @api private
#
# @param options [Hash]
# 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 = {})
@varset = VarSet.new(Rscons::DEFAULT_CONSTRUCTION_VARIABLES)
load_configuration_data!(options)
end
# Access the value of a construction variable.
# Get a construction variable's value.
#
# @param key [String, Symbol]
# The construction variable name.
#
# @return [Object]
# The construction variable's value.
def [](key)
@varset[key]
# @see VarSet#[]
def [](*args)
@varset.__send__(:[], *args)
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
# 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 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
# @see VarSet#[]=
def []=(*args)
@varset.__send__(:[]=, *args)
end
# Add a set of construction variables to the BasicEnvironment.
@@ -133,7 +108,7 @@ module Rscons
append["LDFLAGS", ["-arch", val]]
end
skip = true
elsif word =~ /^#{get_var("CPPDEFPREFIX")}(.*)$/
elsif word =~ /^#{self["CPPDEFPREFIX"]}(.*)$/
handle["CPPDEFINES", $1]
elsif word == "-include"
if val = words[i + 1]
@@ -146,11 +121,11 @@ module Rscons
append["LDFLAGS", ["-isysroot", val]]
end
skip = true
elsif word =~ /^#{get_var("INCPREFIX")}(.*)$/
elsif word =~ /^#{self["INCPREFIX"]}(.*)$/
handle["CPPPATH", $1]
elsif word =~ /^#{get_var("LIBLINKPREFIX")}(.*)$/
elsif word =~ /^#{self["LIBLINKPREFIX"]}(.*)$/
handle["LIBS", $1]
elsif word =~ /^#{get_var("LIBDIRPREFIX")}(.*)$/
elsif word =~ /^#{self["LIBDIRPREFIX"]}(.*)$/
handle["LIBPATH", $1]
elsif word == "-mno-cygwin"
append["CCFLAGS", [word]]
@@ -199,7 +174,7 @@ module Rscons
# @return [void]
def merge_flags(flags)
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
else
self[key] = val
@@ -248,28 +223,5 @@ module Rscons
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
+2 -29
View File
@@ -47,14 +47,6 @@ module Rscons
# Construction variables used to perform the build operation.
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.
#
# @param options [Hash]
@@ -75,7 +67,6 @@ module Rscons
@cache = options[:cache]
@env = options[:env]
@vars = options[:vars]
@side_effects = Set.new
end
# Return the name of the builder.
@@ -87,14 +78,6 @@ module Rscons
self.class.name
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.
#
# @param user_deps [Array<String>]
@@ -105,18 +88,6 @@ module Rscons
@env.depends(@target, *user_deps)
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.
#
# @param options [Hash]
@@ -201,6 +172,8 @@ module Rscons
# Register build results from a {Command} with the cache.
#
# @since 1.10.0
#
# @param options [Hash]
# Builder finalize options.
# @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
# be returned as ready to run if any of its dependencies are still
# building.
# @param side_effects [Set]
# Set of side-effect files. A builder will not be returned as ready to
# run if any of its dependencies is a side-effect of another target that
# has not yet been built.
# @param side_effects [Hash]
# Hash mapping targets to a set of side-effect files. A builder will not
# be returned as ready to run if any of its dependencies is a side-effect
# of another target that has not yet been built.
def initialize(build_dependencies, side_effects)
super()
@build_dependencies = build_dependencies
@@ -31,16 +31,6 @@ module Rscons
self[builder.target] << builder
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.
#
# This method will remove the builder from the BuilderSet.
@@ -53,24 +43,22 @@ module Rscons
# @return [nil, Builder]
# The next builder to run.
def get_next_builder_to_run(targets_still_building)
to_build = self.find do |target, builders|
deps = builders.first.sources + (@build_dependencies[target] || []).to_a
# All dependencies must have been built for this target to be ready to
# build.
deps.all? do |dep|
!(targets_still_building.include?(dep) ||
self.include?(dep) ||
@side_effects.include?(dep))
end
not_built_yet = targets_still_building + self.keys
not_built_yet += not_built_yet.reduce([]) do |result, target|
result + (@side_effects[target] || [])
end
if to_build
target, builders = *to_build
builder = builders.first
if builders.size > 1
builders.slice!(0)
target_to_build = self.keys.find do |target|
deps = self[target][0].sources + (@build_dependencies[target] || []).to_a
!deps.find {|dep| not_built_yet.include?(dep)}
end
if target_to_build
builder = self[target_to_build][0]
if self[target_to_build].size > 1
self[target_to_build].slice!(0)
else
self.delete(target)
self.delete(target_to_build)
end
return builder
end
+1 -1
View File
@@ -26,7 +26,7 @@ module Rscons
raise "Unknown source file #{@sources.first.inspect} for CFile builder"
end
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
+3 -1
View File
@@ -3,6 +3,8 @@ module Rscons
# A builder to execute an arbitrary command that will produce the given
# target based on the given sources.
#
# @since 1.8.0
#
# Example:
# env.Command("docs.html", "docs.md",
# CMD => %w[pandoc -fmarkdown -thtml -o${_TARGET} ${_SOURCES}])
@@ -21,7 +23,7 @@ module Rscons
if @vars["CMD_STDOUT"]
options[:stdout] = @env.expand_varref("${CMD_STDOUT}", @vars)
end
standard_command("#{cmd_desc} <target>#{@target}<reset>", command, options)
standard_command("#{cmd_desc} => #{@target}", command, options)
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
+7 -32
View File
@@ -1,47 +1,22 @@
module Rscons
module Builders
# The Directory builder creates a directory.
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.
def run(options)
if @nop
if File.directory?(@target)
true
elsif File.exists?(@target)
Ansi.write($stderr, :red, "Error: `#{@target}' already exists and is not a directory", :reset, "\n")
false
else
if File.directory?(@target)
true
elsif File.exists?(@target)
Ansi.write($stderr, :red, "Error: `#{@target}' already exists and is not a directory", :reset, "\n")
false
else
print_run_message("Creating directory <target>#{@target}<reset>", nil)
@cache.mkdir_p(@target, install: @install_builder)
true
end
print_run_message("Creating directory => #{@target}", nil)
@cache.mkdir_p(@target)
true
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
+1 -1
View File
@@ -10,7 +10,7 @@ module Rscons
else
@vars["_SOURCES"] = @sources
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
+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["_SOURCES"] = @objects
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
+1 -1
View File
@@ -9,7 +9,7 @@ module Rscons
def finalize_command_with_depfile
deps = @sources
if File.exists?(@vars["_DEPFILE"])
deps += Util.parse_dependency_file(@vars["_DEPFILE"])
deps += Util.parse_makefile_deps(@vars["_DEPFILE"])
end
@cache.register_build(@target, @command, deps.uniq, @env)
true
+3 -4
View File
@@ -8,7 +8,6 @@ module Rscons
module Builders
module Mixins
# Mixin for builders needing to build object files.
module Object
include Depfile
@@ -82,11 +81,11 @@ module Rscons
end
@cache.mkdir_p(File.dirname(@vars["_DEPFILE"]))
command = @env.build_command(@command_template, @vars)
self.produces(@vars["_DEPFILE"])
@env.produces(@target, @vars["_DEPFILE"])
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
message = "#{@short_description} <source>#{Util.short_format_paths(@sources)}<reset>"
message = "#{@short_description} #{Util.short_format_paths(@sources)}"
end
standard_command(message, command)
end
+1 -1
View File
@@ -27,7 +27,7 @@ module Rscons
@vars["_SOURCES"] = @objects
@vars["#{ld_var}"] = ld
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
+2 -2
View File
@@ -25,8 +25,8 @@ module Rscons
@vars["_SOURCES"] = @sources
@vars["_DEPFILE"] = Rscons.set_suffix(target, env.expand_varref("${DEPFILESUFFIX}", vars))
command = @env.build_command("${CPP_CMD}", @vars)
self.produces(@vars["_DEPFILE"])
standard_command("Preprocessing <source>#{Util.short_format_paths(@sources)}<reset> => <target>#{@target}<reset>", command)
@env.produces(@target, @vars["_DEPFILE"])
standard_command("Preprocessing #{Util.short_format_paths(@sources)} => #{@target}", command)
end
end
-2
View File
@@ -8,8 +8,6 @@ module Rscons
include Mixins::Program
class << self
# Custom new method which will delegate to the correct class depending
# on the options specified.
def new(options, *more)
unless File.basename(options[:target])["."]
options[:target] += options[:env].expand_varref("${PROGSUFFIX}", options[:vars])
-2
View File
@@ -8,8 +8,6 @@ module Rscons
include Mixins::Program
class << self
# Custom new method which will delegate to the correct class depending
# on the options specified.
def new(options, *more)
libprefix = options[:env].expand_varref("${SHLIBPREFIX}", options[:vars])
unless File.basename(options[:target]).start_with?(libprefix)
-2
View File
@@ -7,8 +7,6 @@ module Rscons
include Mixins::Object
class << self
# Content component to add to build path to separate objects built
# using this builder from others.
def extra_path
"_shared"
end
+2
View File
@@ -1,6 +1,8 @@
module Rscons
module Builders
# A Generic builder which has its operation is defined at instantiation.
#
# @since 1.8.0
class SimpleBuilder < Builder
# @return [String]
+18 -23
View File
@@ -100,6 +100,7 @@ module Rscons
# @return [void]
def write
@cache["version"] = VERSION
validate_json_object(@cache)
File.open(CACHE_FILE, "w") do |fh|
fh.puts(JSON.dump(@cache))
end
@@ -238,18 +239,11 @@ module Rscons
# @param options [Hash] Optional arguments.
# @option options [Boolean] :install
# 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]
def register_build(targets, command, deps, env, options = {})
Array(targets).each do |target|
target_checksum =
if options[:side_effect] or Rscons.phony_target?(target)
""
else
calculate_checksum(target)
end
target_checksum = Rscons.phony_target?(target) ? "" : calculate_checksum(target)
@cache["targets"][get_cache_key(target)] = {
"command" => Digest::MD5.hexdigest(command.inspect),
"checksum" => target_checksum,
@@ -281,7 +275,7 @@ module Rscons
def targets(install)
install = !!install
@cache["targets"].select do |key, target|
target["install"] == install
target[:install] == install
end.map(&:first)
end
@@ -326,20 +320,6 @@ module Rscons
end.map(&:first)
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
# 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 ""
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
+8 -12
View File
@@ -1,12 +1,11 @@
require "rscons"
require "optparse"
# CLI usage string.
USAGE = <<EOF
Usage: #{$0} [global options] [operation] [operation 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
-h, --help Show rscons help and exit
-r COLOR, --color=COLOR Set color mode (off, auto, force)
@@ -122,24 +121,21 @@ module Rscons
rsconscript = DEFAULT_RSCONSCRIPTS.find do |f|
File.exists?(f)
end
unless rsconscript
$stderr.puts "Could not find the Rsconscript to execute."
$stderr.puts "Looked for: #{DEFAULT_RSCONSCRIPTS.join(", ")}"
exit 1
end
end
if rsconscript
script = Script.new
script.load(rsconscript)
end
script = Script.new
script.load(rsconscript)
if do_help
puts USAGE
exit 0
end
unless rsconscript
$stderr.puts "Could not find the Rsconscript to execute."
$stderr.puts "Looked for: #{DEFAULT_RSCONSCRIPTS.join(", ")}"
exit 1
end
operation_options = parse_operation_args(operation, argv) || {}
exit Rscons.application.run(operation, script, operation_options)
+112 -228
View File
@@ -10,48 +10,20 @@ module Rscons
# Create a ConfigureOp.
#
# @param options [Hash]
# Optional parameters.
# @option options [String] :build_dir
# Build directory.
# @option options [String] :prefix
# Install prefix.
# @option options [String] :project_name
# Project name.
def initialize(options)
# Default options.
options[:build_dir] ||= "build"
options[:prefix] ||= "/usr/local"
@work_dir = "#{options[:build_dir]}/configure"
# @param work_dir [String]
# Work directory for configure operation.
def initialize(work_dir)
@work_dir = work_dir
FileUtils.mkdir_p(@work_dir)
@log_fh = File.open("#{@work_dir}/config.log", "wb")
cache = Cache.instance
cache["failed_commands"] = []
cache["configuration_data"] = {}
cache["configuration_data"]["build_dir"] = options[:build_dir]
cache["configuration_data"]["prefix"] = options[:prefix]
if project_name = options[:project_name]
Ansi.write($stdout, "Configuring ", :cyan, project_name, :reset, "...\n")
else
$stdout.puts "Configuring project..."
end
Ansi.write($stdout, "Setting build directory... ", :green, options[:build_dir], :reset, "\n")
Ansi.write($stdout, "Setting prefix... ", :green, options[:prefix], :reset, "\n")
store_merge("prefix" => options[:prefix])
end
# Close the log file handle.
#
# @param success [Boolean]
# Whether all configure operations were successful.
#
# @return [void]
def close(success)
def close
@log_fh.close
@log_fh = nil
cache = Cache.instance
cache["configuration_data"]["configured"] = success
cache.write
end
# Check for a working C compiler.
@@ -69,7 +41,7 @@ module Rscons
cc = ccc.find do |cc|
test_c_compiler(cc)
end
complete(cc ? 0 : 1, success_message: cc)
common_config_checks(cc ? 0 : 1, success_message: cc)
end
# Check for a working C++ compiler.
@@ -87,7 +59,7 @@ module Rscons
cc = ccc.find do |cc|
test_cxx_compiler(cc)
end
complete(cc ? 0 : 1, success_message: cc)
common_config_checks(cc ? 0 : 1, success_message: cc)
end
# Check for a working D compiler.
@@ -105,7 +77,7 @@ module Rscons
dc = cdc.find do |dc|
test_d_compiler(dc)
end
complete(dc ? 0 : 1, success_message: dc)
common_config_checks(dc ? 0 : 1, success_message: dc)
end
# Check for a package or configure program output.
@@ -122,12 +94,11 @@ module Rscons
if status == 0
store_parse(stdout, options)
end
complete(status, options)
common_config_checks(status, options)
end
# Check for a C header.
def check_c_header(header_name, options = {})
check_cpppath = [nil] + (options[:check_cpppath] || [])
Ansi.write($stdout, "Checking for C header '", :cyan, header_name, :reset, "'... ")
File.open("#{@work_dir}/cfgtest.c", "wb") do |fh|
fh.puts <<-EOF
@@ -140,30 +111,15 @@ module Rscons
vars = {
"LD" => "${CC}",
"_SOURCES" => "#{@work_dir}/cfgtest.c",
"_TARGET" => "#{@work_dir}/cfgtest.o",
"_DEPFILE" => "#{@work_dir}/cfgtest.mf",
"_TARGET" => "#{@work_dir}/cfgtest.exe",
}
status = 1
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)
if status == 0
if cpppath
store_append({"CPPPATH" => Array(cpppath)}, options)
end
break
end
end
complete(status, options)
command = BasicEnvironment.new.build_command("${LDCMD}", vars)
_, _, status = log_and_test_command(command)
common_config_checks(status, options)
end
# Check for a C++ header.
def check_cxx_header(header_name, options = {})
check_cpppath = [nil] + (options[:check_cpppath] || [])
Ansi.write($stdout, "Checking for C++ header '", :cyan, header_name, :reset, "'... ")
File.open("#{@work_dir}/cfgtest.cxx", "wb") do |fh|
fh.puts <<-EOF
@@ -176,30 +132,15 @@ module Rscons
vars = {
"LD" => "${CXX}",
"_SOURCES" => "#{@work_dir}/cfgtest.cxx",
"_TARGET" => "#{@work_dir}/cfgtest.o",
"_DEPFILE" => "#{@work_dir}/cfgtest.mf",
"_TARGET" => "#{@work_dir}/cfgtest.exe",
}
status = 1
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)
if status == 0
if cpppath
store_append({"CPPPATH" => Array(cpppath)}, options)
end
break
end
end
complete(status, options)
command = BasicEnvironment.new.build_command("${LDCMD}", vars)
_, _, status = log_and_test_command(command)
common_config_checks(status, options)
end
# Check for a D import.
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, "'... ")
File.open("#{@work_dir}/cfgtest.d", "wb") do |fh|
fh.puts <<-EOF
@@ -212,30 +153,15 @@ module Rscons
vars = {
"LD" => "${DC}",
"_SOURCES" => "#{@work_dir}/cfgtest.d",
"_TARGET" => "#{@work_dir}/cfgtest.o",
"_DEPFILE" => "#{@work_dir}/cfgtest.mf",
"_TARGET" => "#{@work_dir}/cfgtest.exe",
}
status = 1
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)
if status == 0
if d_import_path
store_append({"D_IMPORT_PATH" => Array(d_import_path)}, options)
end
break
end
end
complete(status, options)
command = BasicEnvironment.new.build_command("${LDCMD}", vars)
_, _, status = log_and_test_command(command)
common_config_checks(status, options)
end
# Check for a library.
def check_lib(lib, options = {})
check_libpath = [nil] + (options[:check_libpath] || [])
Ansi.write($stdout, "Checking for library '", :cyan, lib, :reset, "'... ")
File.open("#{@work_dir}/cfgtest.c", "wb") do |fh|
fh.puts <<-EOF
@@ -250,143 +176,19 @@ module Rscons
"_SOURCES" => "#{@work_dir}/cfgtest.c",
"_TARGET" => "#{@work_dir}/cfgtest.exe",
}
status = 1
check_libpath.each do |libpath|
env = BasicEnvironment.new
if libpath
env["LIBPATH"] += Array(libpath)
end
command = env.build_command("${LDCMD}", vars)
_, _, status = log_and_test_command(command)
if status == 0
if libpath
store_append({"LIBPATH" => Array(libpath)}, options)
end
break
end
end
command = BasicEnvironment.new.build_command("${LDCMD}", vars)
_, _, status = log_and_test_command(command)
if status == 0
store_append({"LIBS" => [lib]}, options)
end
complete(status, options)
common_config_checks(status, options)
end
# Check for a executable program.
def check_program(program, options = {})
Ansi.write($stdout, "Checking for program '", :cyan, program, :reset, "'... ")
path = Util.find_executable(program)
complete(path ? 0 : 1, options.merge(success_message: path))
end
# Execute a test command and log the result.
#
# @return [String, String, Process::Status]
# stdout, stderr, status
def log_and_test_command(command)
begin
@log_fh.puts("Command: #{command.join(" ")}")
stdout, stderr, status = Open3.capture3(*command)
@log_fh.puts("Exit status: #{status.to_i}")
@log_fh.write(stdout)
@log_fh.write(stderr)
[stdout, stderr, status]
rescue Errno::ENOENT
["", "", 127]
end
end
# Store construction variables for merging into the Cache.
#
# @param vars [Hash]
# Hash containing the variables to merge.
# @param options [Hash]
# Options.
# @option options [String] :use
# A 'use' name. If specified, the construction variables are only applied
# to an Environment if the Environment is constructed with a matching
# `:use` value.
def store_merge(vars, options = {})
store_vars = store_common(options)
store_vars["merge"] ||= {}
vars.each_pair do |key, value|
store_vars["merge"][key] = value
end
end
# Store construction variables for appending into the Cache.
#
# @param vars [Hash]
# Hash containing the variables to append.
# @param options [Hash]
# Options.
# @option options [String] :use
# A 'use' name. If specified, the construction variables are only applied
# to an Environment if the Environment is constructed with a matching
# `:use` value.
def store_append(vars, options = {})
store_vars = store_common(options)
store_vars["append"] ||= {}
vars.each_pair do |key, value|
if store_vars["append"][key].is_a?(Array) and value.is_a?(Array)
store_vars["append"][key] += value
else
store_vars["append"][key] = value
end
end
end
# Store flags to be parsed into the Cache.
#
# @param flags [String]
# String containing the flags to parse.
# @param options [Hash]
# Options.
# @option options [String] :use
# A 'use' name. If specified, the construction variables are only applied
# to an Environment if the Environment is constructed with a matching
# `:use` value.
def store_parse(flags, options = {})
store_vars = store_common(options)
store_vars["parse"] ||= []
store_vars["parse"] << flags
end
# Perform processing common to several configure checks.
#
# @param status [Process::Status, Integer]
# Process exit code. 0 for success, non-zero for error.
# @param options [Hash]
# Common check options.
# @option options [Boolean] :fail
# Whether to fail configuration if the requested item is not found.
# This defaults to true if the :set_define option is not specified,
# otherwise defaults to false if :set_define option is specified.
# @option options [String] :set_define
# A define to set (in CPPDEFINES) if the requested item is found.
# @option options [String] :success_message
# Message to print on success (default "found").
def complete(status, options)
success_message = options[:success_message] || "found"
fail_message = options[:fail_message] || "not found"
if status == 0
Ansi.write($stdout, :green, "#{success_message}\n")
if options[:set_define]
store_append("CPPDEFINES" => [options[:set_define]])
end
else
should_fail =
if options.has_key?(:fail)
options[:fail]
else
!options[:set_define]
end
if should_fail
Ansi.write($stdout, :red, "#{fail_message}\n")
raise ConfigureFailure.new
else
Ansi.write($stdout, :yellow, "#{fail_message}\n")
end
end
common_config_checks(path ? 0 : 1, options.merge(success_message: path))
end
private
@@ -479,12 +281,7 @@ module Rscons
"DC" => dc,
"DCCMD" => env["DCCMD"].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
_, _, status = log_and_test_command(command)
if status == 0
@@ -497,6 +294,64 @@ module Rscons
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.
#
# @param options [Hash]
@@ -520,5 +375,34 @@ module Rscons
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
+54 -56
View File
@@ -59,9 +59,6 @@ module Rscons
# :command, :short, or :off (default :short)
# @option options [Boolean] :exclude_builders
# 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
# when the block returns, the {#process} method is automatically called.
@@ -75,8 +72,7 @@ module Rscons
# Hash of Thread object => {Command} or {Builder}.
@threads = {}
@registered_build_dependencies = {}
# Set of side-effect files that have not yet been built.
@side_effects = Set.new
@side_effects = {}
@builder_sets = []
@build_targets = {}
@user_deps = {}
@@ -169,6 +165,8 @@ module Rscons
# Create a new {Builders::SimpleBuilder} instance and add it to the
# environment.
#
# @since 1.8.0
#
# @param name [String,Symbol]
# The name of the builder to add.
#
@@ -218,6 +216,8 @@ module Rscons
# build operation succeeded. Post-build hooks can register new build
# targets.
#
# @since 1.7.0
#
# @yield [build_op]
# Invoke the given block with the current build operation.
# @yieldparam build_op [Hash]
@@ -261,7 +261,6 @@ module Rscons
#
# @return [void]
def process
Cache.instance.clear_checksum_cache!
@process_failures = []
@process_blocking_wait = false
@process_commands_waiting_to_run = []
@@ -287,7 +286,7 @@ module Rscons
unless @process_failures.empty?
msg = @process_failures.join("\n")
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
raise BuildError.new(msg)
end
@@ -375,6 +374,8 @@ module Rscons
# described:
# env.build_after("program.o", "gen.c")
#
# @since 1.10.0
#
# @param targets [String, Array<String>]
# Target files to wait to build until the prerequisites are finished
# building.
@@ -401,6 +402,8 @@ module Rscons
# Manually record the given side effect file(s) as being produced when the
# named target is produced.
#
# @since 1.13.0
#
# @param target [String]
# Target of a build operation.
# @param side_effects [Array<String>]
@@ -409,25 +412,11 @@ module Rscons
# @return [void]
def produces(target, *side_effects)
target = expand_path(expand_varref(target))
@builder_sets.reverse.each do |builder_set|
if builders = builder_set[target]
builders.last.produces(*side_effects)
return
end
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
side_effects = Array(side_effects).map do |side_effect|
expand_path(expand_varref(side_effect))
end.flatten
@side_effects[target] ||= []
@side_effects[target] += side_effects
end
# Return the list of user dependencies for a given target.
@@ -490,6 +479,29 @@ module Rscons
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.
#
# @param builder [Builder]
@@ -514,12 +526,7 @@ module Rscons
when :short
message = short_description if short_description
end
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
Ansi.write($stdout, :cyan, message, :reset, "\n") if message
end
# Get the Builder for a target.
@@ -540,16 +547,6 @@ module Rscons
@builder_sets << build_builder_set
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
# Build a BuilderSet.
@@ -567,9 +564,9 @@ module Rscons
#
# @return [void]
def run_builder(builder)
unless builder.nop?
builder.build_step ||= Rscons.application.get_next_build_step
end
# TODO: have Cache determine when checksums may be invalid based on
# file size and/or timestamp.
Cache.instance.clear_checksum_cache!
case result = builder.run({})
when Array
result.each do |waititem|
@@ -591,9 +588,8 @@ module Rscons
when true
# Register side-effect files as build targets so that a Cache
# clean operation will remove them.
builder.side_effects.each do |side_effect|
Cache.instance.register_build(side_effect, nil, [], self, side_effect: true)
@side_effects.delete(side_effect)
(@side_effects[builder.target] || []).each do |side_effect_file|
Cache.instance.register_build(side_effect_file, nil, [], self)
end
@build_hooks[:post].each do |build_hook_block|
build_hook_block.call(builder)
@@ -638,7 +634,7 @@ module Rscons
if thread
# We found a completed thread.
process_remove_wait(thread)
unblocked_builder = builder_for_thread(thread)
builder = builder_for_thread(thread)
completed_command = @threads[thread]
@threads.delete(thread)
if completed_command.is_a?(Command)
@@ -646,7 +642,7 @@ module Rscons
completed_command.status = thread.value
unless completed_command.status
Cache.instance["failed_commands"] << completed_command.command
@process_failures << "Failed to build #{unblocked_builder.target}."
@process_failures << "Failed to build #{builder.target}."
return
end
end
@@ -671,13 +667,15 @@ module Rscons
result << builder_for_thread(thread).target
end
if @builder_sets.size > 0
if builder = @builder_sets[0].get_next_builder_to_run(targets_still_building)
builder.vars = @varset.merge(builder.vars)
@build_hooks[:pre].each do |build_hook_block|
build_hook_block.call(builder)
end
return run_builder(builder)
builder = @builder_sets[0].get_next_builder_to_run(targets_still_building)
end
if builder
builder.vars = @varset.merge(builder.vars)
@build_hooks[:pre].each do |build_hook_block|
build_hook_block.call(builder)
end
return run_builder(builder)
end
if @threads.size > 0
-15
View File
@@ -3,7 +3,6 @@ module Rscons
# The Script class encapsulates the state of a build script.
class Script
# DSL available to the Rsconscript.
class Dsl
# Create a Dsl.
def initialize(script)
@@ -58,7 +57,6 @@ module Rscons
end
end
# DSL available to the 'configure' block.
class ConfigureDsl
# Create a ConfigureDsl.
#
@@ -83,19 +81,6 @@ module Rscons
@configure_op.__send__(method_name, *args)
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
# @return [String, nil]
+13 -65
View File
@@ -1,5 +1,4 @@
module Rscons
# A collection of stand-alone utility methods.
module Util
class << self
@@ -16,30 +15,6 @@ module Rscons
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.
#
# @param command [Array<String>]
@@ -120,27 +95,6 @@ module Rscons
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.
#
# @param path [String]
@@ -160,7 +114,7 @@ module Rscons
end
end
# Parse dependencies from a Makefile or ldc2 dependency file.
# Parse dependencies from a Makefile.
#
# This method is used internally by Rscons builders.
#
@@ -169,7 +123,7 @@ module Rscons
#
# @return [Array<String>]
# Paths of dependency files.
def parse_dependency_file(mf_fname)
def parse_makefile_deps(mf_fname)
deps = []
buildup = ''
File.read(mf_fname).each_line do |line|
@@ -177,11 +131,7 @@ module Rscons
buildup += ' ' + $1
else
buildup += ' ' + line
if buildup =~ /^[^:]+\(\S+\)\s*:.*?:[^:]+\((\S+)\)/
# ldc2-style dependency line
deps << $1
elsif buildup =~ /^.*: (.*)$/
# Makefile-style dependency line
if buildup =~ /^.*: (.*)$/
mf_deps = $1
deps += mf_deps.split(' ').map(&:strip)
end
@@ -204,18 +154,16 @@ module Rscons
File.file?(path) and File.executable?(path)
end
if RbConfig::CONFIG["host_os"] =~ /mswin|windows|mingw/i
if File.directory?(path_entry)
executable = executable.downcase
dir_entries = Dir.entries(path_entry)
dir_entries.find do |entry|
path = "#{path_entry}/#{entry}"
entry = entry.downcase
if ((entry == executable) or
(entry == "#{executable}.exe") or
(entry == "#{executable}.com") or
(entry == "#{executable}.bat")) and is_executable[path]
return path
end
executable = executable.downcase
dir_entries = Dir.entries(path_entry)
dir_entries.find do |entry|
path = "#{path_entry}/#{entry}"
entry = entry.downcase
if ((entry == executable) or
(entry == "#{executable}.exe") or
(entry == "#{executable}.com") or
(entry == "#{executable}.bat")) and is_executable[path]
return path
end
end
else
+23 -50
View File
@@ -11,13 +11,11 @@ module Rscons
append(vars)
end
# Access the value of a variable.
# Access the value of variable.
#
# @param key [String, Symbol]
# The variable name.
# @param key [String, Symbol] The variable name.
#
# @return [Object]
# The variable's value.
# @return [Object] The variable's value.
def [](key)
if @my_vars.include?(key)
@my_vars[key]
@@ -32,38 +30,11 @@ module Rscons
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.
#
# @param key [String, Symbol]
# The variable name.
# @param key [String, Symbol] The variable name.
#
# @param val [Object]
# The value to set.
# @param val [Object] The value to set.
def []=(key, val)
@my_vars[key] = val
end
@@ -124,27 +95,29 @@ module Rscons
# Expanded value with "$!{var}" variable references replaced.
def expand_varref(varref, lambda_args)
case varref
when Symbol, true, false, nil
varref
when String
if varref =~ /^(.*)\$\{([^}]+)\}(.*)$/
prefix, varname, suffix = $1, $2, $3
prefix = expand_varref(prefix, lambda_args) unless prefix.empty?
varval = expand_varref(get_var(varname), lambda_args)
varval = expand_varref(self[varname], lambda_args)
# suffix needs no expansion since the regex matches the last occurence
case varval
when Symbol, true, false, nil, String
if prefix.is_a?(Array)
prefix.map {|p| "#{p}#{varval}#{suffix}"}
else
"#{prefix}#{varval}#{suffix}"
end
when Array
if prefix.is_a?(Array)
varval.map {|vv| prefix.map {|p| "#{p}#{vv}#{suffix}"}}.flatten
else
varval.map {|vv| "#{prefix}#{vv}#{suffix}"}
end
when String, Symbol, true, false, nil
if prefix.is_a?(Array)
prefix.map {|p| "#{p}#{varval}#{suffix}"}
else
"#{prefix}#{varval}#{suffix}"
end
else
raise "Unknown construction variable type: #{varval.class} (from #{varname.inspect} => #{get_var(varname).inspect})"
raise "Unknown construction variable type: #{varval.class} (from #{varname.inspect} => #{self[varname].inspect})"
end
else
varref
@@ -153,8 +126,6 @@ module Rscons
varref.map do |ent|
expand_varref(ent, lambda_args)
end.flatten
when Symbol, true, false, nil
varref
when Proc
expand_varref(varref[*lambda_args], lambda_args)
else
@@ -164,6 +135,8 @@ module Rscons
# 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
# debugging code to dump out a VarSet's variables.
#
@@ -221,16 +194,16 @@ module Rscons
#
# @return [Object] Deep copied value.
def deep_dup(obj)
case obj
when String
obj.dup
when Array
obj.map { |v| deep_dup(v) }
when Hash
obj_class = obj.class
if obj_class == Hash
obj.reduce({}) do |result, (k, v)|
result[k] = deep_dup(v)
result
end
elsif obj_class == Array
obj.map { |v| deep_dup(v) }
elsif obj_class == String
obj.dup
else
obj
end
+1 -1
View File
@@ -1,4 +1,4 @@
module Rscons
# Project version.
VERSION = "2.0.0beta2"
VERSION = "1.16.0"
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
require "fileutils"
require "digest/md5"
if File.read("lib/rscons/version.rb") =~ /VERSION = "(.+)"/
VERSION = $1
else
raise "Could not determine version."
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"
START_FILE = "bin/#{PROG_NAME}"
LIB_DIR = "lib"
@@ -59,22 +63,19 @@ combined_file.each do |line|
end
require "zlib"
require "base64"
compressed_script = Zlib::Deflate.deflate(stripped_comments.join)
encoded_compressed_script = Base64.encode64(compressed_script).gsub("\n", "")
hash = Digest::MD5.hexdigest(encoded_compressed_script)
escaped_compressed_script = compressed_script.gsub("#", "#1").gsub("\n", "#2").gsub("\r", "#3").gsub("\0", "#4")
FileUtils.mkdir_p(DIST)
File.open("#{DIST}/#{PROG_NAME}", "wb", 0755) do |fh|
fh.write(<<EOF)
#!/usr/bin/env ruby
script = File.join(File.dirname(__FILE__), ".rscons-#{VERSION}-#{hash}.rb")
script = ".rscons-#{VERSION}-#{GIT}.rb"
unless File.exists?(script)
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 "base64"
encoded_compressed = $1
unescaped_compressed = Base64.decode64(encoded_compressed)
inflated = Zlib::Inflate.inflate(unescaped_compressed)
File.open(script, "wb") do |fh|
fh.write(inflated)
@@ -87,6 +88,6 @@ load script
if __FILE__ == $0
Rscons::Cli.run(ARGV)
end
#==>#{encoded_compressed_script}
#==>#{escaped_compressed_script}
EOF
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)
+279 -693
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
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
it "makes directories and records any created in the cache" do
_cache = {}
@@ -28,7 +35,7 @@ module Rscons
expect(FileUtils).to receive(:mkdir_p).with("one/two/four")
cache.mkdir_p("one/two/three")
cache.mkdir_p("one\\two\\four")
expect(cache.directories(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
it "handles absolute paths" do
@@ -38,7 +45,14 @@ module Rscons
expect(File).to receive(:exists?).with("/one/two").and_return(false)
expect(FileUtils).to receive(:mkdir_p).with("/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
@@ -65,5 +79,12 @@ module Rscons
expect(build_from({}).send(:calculate_checksum, "fname")).to eq "the_checksum"
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
+4 -40
View File
@@ -29,12 +29,6 @@ module Rscons
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
context "when specified by environment variable" do
before(:each) do
@@ -121,10 +115,7 @@ EOF
before(:each) do
stub_const("File::PATH_SEPARATOR", ";")
stub_const("RbConfig::CONFIG", "host_os" => "mingw")
expect(ENV).to receive(:[]).with("PATH").and_return("C:\\none;C:\\bin;C:\\Windows")
allow(File).to receive(:directory?).with("C:\\none").and_return(false)
allow(File).to receive(:directory?).with("C:\\bin").and_return(true)
allow(File).to receive(:directory?).with("C:\\Windows").and_return(true)
expect(ENV).to receive(:[]).with("PATH").and_return("C:\\bin;C:\\Windows")
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])
end
@@ -160,20 +151,6 @@ EOF
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
context "when passed a relative path" do
it "returns the path itself" do
@@ -200,12 +177,12 @@ EOF
end
end
describe ".parse_dependency_file" do
describe ".parse_makefile_deps" do
it 'handles dependencies on one line' do
expect(File).to receive(:read).with('makefile').and_return(<<EOS)
module.o: source.cc
EOS
expect(Util.parse_dependency_file('makefile')).to eq ['source.cc']
expect(Util.parse_makefile_deps('makefile')).to eq ['source.cc']
end
it 'handles dependencies split across many lines' do
@@ -214,22 +191,9 @@ module.o: module.c \\
module.h \\
other.h
EOS
expect(Util.parse_dependency_file('makefile')).to eq [
expect(Util.parse_makefile_deps('makefile')).to eq [
'module.c', 'module.h', 'other.h']
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
+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/
end
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).and_call_original
expect { v.expand_varref("${bad}", :lambda_args) }.to raise_error /Unknown construction variable type: Hash/