Compare commits

...

45 Commits

Author SHA1 Message Date
af7aad716d Use absolute paths for Cache targets and dependencies 2026-01-28 10:58:01 -05:00
13bd827d57 Store build targets, sources, and user dependencies internally as absolute paths 2026-01-28 10:44:03 -05:00
a3d739c472 Add Barrier build test 2026-01-28 08:35:01 -05:00
319432bc61 Add Util.absolute_path() 2026-01-28 08:18:25 -05:00
d38cd2e8fb Show focused test count from build tests when focusing 2026-01-28 08:12:58 -05:00
72d2802d1e Colorize build test results similar to RSpec tests 2026-01-28 07:56:36 -05:00
e6317ae236 Fix SimpleCov coverage results for multiple invocations from same build test 2026-01-27 23:50:04 -05:00
baf4e036fb Fix up dspec task for parallelization 2026-01-27 23:50:04 -05:00
9d6321685e Clean up simplecov reporting 2026-01-27 23:50:04 -05:00
3a5db626ac Parallelize build tests 2026-01-27 23:50:04 -05:00
94a86e3433 Add Barrier builder 2026-01-16 22:54:22 -05:00
fc18c9f123 Test phony dependencies 2026-01-16 20:21:13 -05:00
1d020d1aef Update gems 2026-01-15 21:07:18 -05:00
19c5c8cfba Update gems 2025-11-22 00:14:26 -05:00
4fc7ab6b25 v3.3.0 2025-06-10 20:14:15 -04:00
161da80ffe Update CHANGELOG for v3.3.0 2025-06-10 20:13:37 -04:00
8d9f19cb34 Add github workflow to run RScons tests
Fix up some macOS test cases
2025-06-08 20:46:09 -04:00
18a2a075c1 Remove dependency on base64 - close #177 2025-06-08 02:48:03 -04:00
1a280a8994 Update gems 2025-06-07 20:30:48 -04:00
a19c2e908e Update gems 2025-03-24 09:48:10 -04:00
5223dfe211 Add base64 to Gemfile 2025-01-15 20:18:41 -05:00
d0c1b01598 Update gems 2024-03-28 09:11:07 -04:00
57db93c59b Add LLVM support to documentation 2024-03-24 11:00:15 -04:00
ba71e8d5e3 Add support for building object files from LLVM assembly sources - close #175 2024-03-23 22:24:45 -04:00
6e7139abb1 v3.2.0 2024-03-18 22:05:52 -04:00
896a3e270f Indent docstrings under @overload 2024-03-18 22:05:10 -04:00
3ee0015cef Update UPGRADING notes for v3.2.0 2024-03-18 21:53:04 -04:00
0315729f69 Update CHANGELOG for v3.2.0 2024-03-18 21:45:43 -04:00
8c865e668b Add upgrading instructions - close #167 2024-03-17 20:36:59 -04:00
150960246b Improve DSL method documentation - close #164 2024-03-17 20:08:06 -04:00
c2277bc0a1 Update gems 2024-03-01 15:11:39 -05:00
4baf26e492 Update ERB call to avoid deprecation warnings 2024-02-28 16:14:21 -05:00
6044b64695 Update gems 2024-02-28 16:11:09 -05:00
415fa424d1 Fix configuration checks performed with ldc2 compiler - close #172 2024-02-04 20:25:01 -05:00
f8c21a9bcc Use top-level build directory as environment build directory for unnamed environments - close #170 2023-09-10 21:41:42 -04:00
6a8647a933 Place object files next to source files for source files in build directory - close #171 2023-09-10 10:00:41 -04:00
3867133e89 Cannot output binary to an environment's build root with the same name as the top-level source directory - close #168 2023-09-10 08:51:03 -04:00
a7b962c11a Output a better error when pkg-config is not found - close #169 2023-09-08 21:05:01 -04:00
b490daa480 Update gems 2023-07-28 14:03:39 -04:00
ed7408367f Replace CFile builder with Yacc and Lex builders - close #166 2023-02-22 20:57:00 -05:00
d4ec07dd7a Fix user guide examples for task dependencies 2022-10-14 16:18:14 -04:00
33b606ccb1 v3.1.0 2022-08-11 14:57:26 -04:00
c48526fd97 Improve configuration error messages - close #162 2022-08-11 14:41:29 -04:00
419e6ed313 builder registered during build hooks should increase build step count - close #161 2022-08-05 17:05:13 -04:00
132d22e886 Ruby 3.2 compatibility - close #163 2022-08-05 16:40:48 -04:00
64 changed files with 4393 additions and 3627 deletions

38
.github/workflows/run-tests.yml vendored Normal file
View File

@ -0,0 +1,38 @@
name: Run RScons Tests
on:
push:
branches:
- master
pull_request:
jobs:
test:
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest, macos-latest]
ruby-version: ['2.7', '3.0', '3.4']
steps:
- name: Install dependencies (Linux)
if: runner.os == 'Linux'
run: sudo apt-get update && sudo apt-get install -y gcc gdc ldc clang flex bison
- name: Install dependencies (macOS)
if: runner.os == 'macOS'
run: brew install gcc ldc flex bison
- name: Checkout repository
uses: actions/checkout@v4
- name: Set up Ruby
uses: ruby/setup-ruby@v1
with:
ruby-version: ${{ matrix.ruby-version }}
- name: Install dependencies
run: bundle install
- name: Run tests
run: rake all

2
.gitignore vendored
View File

@ -7,5 +7,5 @@
/gen/ /gen/
/large_project/ /large_project/
/pkg/ /pkg/
/test/ /test_run/
/yard/ /yard/

View File

@ -1,3 +1,39 @@
## v3.3.0
### New Features
- Add support for Ruby 3.4.
- Fix up some macOS test cases.
- #177 - Remove dependency on base64
- #175 - Add support for building object files from LLVM assembly sources
## v3.2.0
### New Features
- #171 - Place object files next to source files for source files in build directory
- #170 - Use top-level build directory as environment build directory for unnamed environments
- #167 - Add upgrading instructions
- #166 - Replace CFile builder with Yacc and Lex builders
### Fixes
- #172 - Fix configuration checks performed with ldc2 compiler
- #169 - Output a better error when pkg-config is not found
- #168 - Cannot output binary to an environment's build root with the same name as the top-level source directory
- #164 - Improve DSL method documentation
## v3.1.0
### New Features
- #162 - Improve configuration error messages
- #163 - Ruby 3.2 compatibility
### Fixes
- #161 - builder registered during build hooks should increase build step count
## v3.0.2 ## v3.0.2
### Fixes ### Fixes

View File

@ -9,6 +9,6 @@ To run the rscons specs, the following commands must be available:
* g++ * g++
* clang++ * clang++
* gdc * gdc
* ldc * ldc2
* flex * flex
* bison * bison

View File

@ -1,8 +1,10 @@
source 'https://rubygems.org' source 'https://rubygems.org'
gem "base64"
gem "rspec" gem "rspec"
gem "rake" gem "rake"
gem "simplecov", "~> 0.15.0" gem "simplecov"
gem "openssl"
if RbConfig::CONFIG["host"]["msys"] if RbConfig::CONFIG["host"]["msys"]
gem "json", "2.1.0" gem "json", "2.1.0"
else else

View File

@ -1,52 +1,61 @@
GEM GEM
remote: https://rubygems.org/ remote: https://rubygems.org/
specs: specs:
diff-lcs (1.5.0) base64 (0.3.0)
docile (1.1.5) date (3.5.1)
json (2.6.1) diff-lcs (1.6.2)
psych (4.0.3) docile (1.4.1)
erb (6.0.1)
json (2.18.0)
openssl (4.0.0)
psych (5.3.1)
date
stringio stringio
rake (13.0.6) rake (13.3.1)
rdoc (6.4.0) rdoc (7.1.0)
erb
psych (>= 4.0.0) psych (>= 4.0.0)
redcarpet (3.5.1) tsort
rspec (3.11.0) redcarpet (3.6.1)
rspec-core (~> 3.11.0) rspec (3.13.2)
rspec-expectations (~> 3.11.0) rspec-core (~> 3.13.0)
rspec-mocks (~> 3.11.0) rspec-expectations (~> 3.13.0)
rspec-core (3.11.0) rspec-mocks (~> 3.13.0)
rspec-support (~> 3.11.0) rspec-core (3.13.6)
rspec-expectations (3.11.0) rspec-support (~> 3.13.0)
rspec-expectations (3.13.5)
diff-lcs (>= 1.2.0, < 2.0) diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.11.0) rspec-support (~> 3.13.0)
rspec-mocks (3.11.0) rspec-mocks (3.13.7)
diff-lcs (>= 1.2.0, < 2.0) diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.11.0) rspec-support (~> 3.13.0)
rspec-support (3.11.0) rspec-support (3.13.6)
simplecov (0.15.1) simplecov (0.22.0)
docile (~> 1.1.0) docile (~> 1.1)
json (>= 1.8, < 3) simplecov-html (~> 0.11)
simplecov-html (~> 0.10.0) simplecov_json_formatter (~> 0.1)
simplecov-html (0.10.2) simplecov-html (0.13.2)
stringio (3.0.1) simplecov_json_formatter (0.1.4)
stringio (3.2.0)
syntax (1.2.2) syntax (1.2.2)
webrick (1.7.0) tsort (0.2.0)
yard (0.9.27) yard (0.9.38)
webrick (~> 1.7.0)
PLATFORMS PLATFORMS
ruby ruby
x86-mingw32 x86-mingw32
DEPENDENCIES DEPENDENCIES
base64
json json
openssl
rake rake
rdoc rdoc
redcarpet redcarpet
rspec rspec
simplecov (~> 0.15.0) simplecov
syntax syntax
yard yard
BUNDLED WITH BUNDLED WITH
2.2.31 2.6.2

View File

@ -1,4 +1,4 @@
Copyright (c) 2013-2022 Josh Holtrop Copyright (c) 2013-2025 Josh Holtrop
MIT License MIT License

View File

@ -9,7 +9,7 @@ It supports the following features:
* multi-threaded job execution * multi-threaded job execution
* auto-configuration * auto-configuration
* built-in builders for several common operations * built-in builders for several common operations
* out-of-the-box support for C, C++, and D languages * out-of-the-box support for Assembly, C, C++, D, and LLVM
* extensibility for other languages or custom builders * extensibility for other languages or custom builders
* compatible with Windows, Linux, OS X, and FreeBSD * compatible with Windows, Linux, OS X, and FreeBSD
* colorized output with build progress * colorized output with build progress

View File

@ -8,8 +8,10 @@ end
require "rspec/core/rake_task" require "rspec/core/rake_task"
require "rake/clean" require "rake/clean"
require "fileutils" require "fileutils"
require "simplecov"
require "stringio"
CLEAN.include %w[build_test_run .yardoc yard coverage test] CLEAN.include %w[build_test_run .yardoc yard coverage test_run]
CLOBBER.include %w[dist gen large_project pkg] CLOBBER.include %w[dist gen large_project pkg]
task :build_dist do task :build_dist do
@ -19,24 +21,43 @@ end
RSpec::Core::RakeTask.new(:spec, :example_string) do |task, args| RSpec::Core::RakeTask.new(:spec, :example_string) do |task, args|
ENV["specs"] = "1" ENV["specs"] = "1"
if args.example_string if args.example_string
ENV["partial_specs"] = "1"
task.rspec_opts = %W[-e "#{args.example_string}" -f documentation] task.rspec_opts = %W[-e "#{args.example_string}" -f documentation]
end end
end end
task :spec => :build_dist
task :spec do task :spec do
ENV.delete("specs") ENV.delete("specs")
end end
task :spec => :build_tests
task :spec do
unless ENV["rscons_dist_specs"]
original_stdout = $stdout
sio = StringIO.new
$stdout = sio
SimpleCov.collate Dir["coverage/.resultset.json", "coverage/bt*/.resultset.json"]
$stdout = original_stdout
sio.string.lines.each do |line|
$stdout.write(line) unless line =~ /Coverage report generated for/
end
end
end
task :build_tests do |task, args|
ENV["specs"] = "1"
sh "ruby -Ilib build_tests/build_tests.rb"
ENV.delete("specs")
end
# dspec task is useful to test the distributable release script, but is not # dspec task is useful to test the distributable release script, but is not
# useful for coverage information. # useful for coverage information.
desc "Dist Specs" desc "Dist Specs"
task :dspec, [:example_string] => :build_dist do |task, args| task :dspec, [:example_string] => :build_dist do |task, args|
FileUtils.mkdir_p("test") FileUtils.rm_rf("test_run")
FileUtils.cp("dist/rscons", "test/rscons.rb") FileUtils.mkdir_p("test_run")
ENV["dist_specs"] = "1" FileUtils.cp("dist/rscons", "test_run/rscons.rb")
ENV["rscons_dist_specs"] = "1"
Rake::Task["spec"].execute(args) Rake::Task["spec"].execute(args)
ENV.delete("dist_specs") Rake::Task["build_tests"].execute(args)
ENV.delete("rscons_dist_specs")
FileUtils.rm_f(Dir.glob(".rscons-*")) FileUtils.rm_f(Dir.glob(".rscons-*"))
end end

10
UPGRADING.md Normal file
View File

@ -0,0 +1,10 @@
# Upgrading
## v3.2.0
- Replace any calls to `env.CFile()` builder with `env.Lex()` or `env.Yacc()` as required.
## v3.0.0
- Move `build` block contents outside of `build` block and remove `build` call.
- Replace `Environment.new()` calls with `env()`.

3568
build_tests/build_tests.rb Normal file

File diff suppressed because it is too large Load Diff

View File

@ -1,4 +0,0 @@
env do |env|
env.CFile("lexer.c", "lexer.l")
env.CFile("parser.c", "parser.y")
end

View File

@ -1,3 +0,0 @@
env do |env|
env.CFile("file.c", "foo.bar")
end

View File

@ -1,10 +1,10 @@
debug = env(echo: :command) do |env| debug = env("dbg", echo: :command) do |env|
env['CFLAGS'] = '-O2' env['CFLAGS'] = '-O2'
env['CPPFLAGS'] = '-DSTRING="Debug Version"' env['CPPFLAGS'] = '-DSTRING="Debug Version"'
env.Program('program-debug.exe', Dir['src/*.c']) env.Program('program-debug.exe', Dir['src/*.c'])
end end
release = debug.clone do |env| release = debug.clone("rls") do |env|
env["CPPFLAGS"] = '-DSTRING="Release Version"' env["CPPFLAGS"] = '-DSTRING="Release Version"'
env.Program('program-release.exe', Dir['src/*.c']) env.Program('program-release.exe', Dir['src/*.c'])
end end

View File

@ -1,4 +1,4 @@
env1 = env(echo: :command) do |env| env1 = env("e1", echo: :command) do |env|
env['CFLAGS'] = '-O2' env['CFLAGS'] = '-O2'
env.add_build_hook do |builder| env.add_build_hook do |builder|
builder.vars['CPPFLAGS'] = '-DSTRING="Hello"' builder.vars['CPPFLAGS'] = '-DSTRING="Hello"'
@ -9,6 +9,6 @@ env1 = env(echo: :command) do |env|
env.Program('program.exe', Dir['src/*.c']) env.Program('program.exe', Dir['src/*.c'])
end end
env2 = env1.clone do |env| env2 = env1.clone("e2") do |env|
env.Program('program2.exe', Dir['src/*.c']) env.Program('program2.exe', Dir['src/*.c'])
end end

View File

@ -0,0 +1,5 @@
ENV["PATH"] = ""
configure do
check_cfg package: "mypackage"
end

View File

@ -1,3 +1,3 @@
configure do configure do
check_d_compiler "gdc", "ldc2" check_d_compiler "gdc", "ldc2", "ldc"
end end

View File

@ -0,0 +1,8 @@
configure do
check_d_compiler "ldc2"
check_lib "z"
end
env(echo: :command) do |env|
env.Program("simple.exe", "simple.d")
end

View File

@ -0,0 +1,4 @@
env do |env|
env.Lex("lexer.c", "lexer.l")
env.Yacc("parser.c", "parser.y")
end

View File

@ -0,0 +1,4 @@
env do |env|
env["LLVMAS_FLAGS"] += %w[-Wno-override-module]
env.Program("llvmtest.exe", %w[main.c one.ll])
end

View File

@ -0,0 +1,8 @@
configure do
check_c_compiler "clang"
end
env do |env|
env["LLVMAS_FLAGS"] += %w[-Wno-override-module]
env.Program("llvmtest.exe", %w[one.ll two.ll main2.c], direct: true)
end

6
build_tests/llvm/main.c Normal file
View File

@ -0,0 +1,6 @@
extern int one(void);
int main(int argc, char * argv[])
{
return one();
}

9
build_tests/llvm/main2.c Normal file
View File

@ -0,0 +1,9 @@
extern int one(void);
extern int two(void);
int main(int argc, char * argv[])
{
one();
two();
return 0;
}

9
build_tests/llvm/one.ll Normal file
View File

@ -0,0 +1,9 @@
@str = private unnamed_addr constant [12 x i8] c"hello world\00"
declare i32 @puts(ptr nocapture) nounwind
define i32 @one()
{
call i32 @puts(ptr @str)
ret i32 0
}

9
build_tests/llvm/two.ll Normal file
View File

@ -0,0 +1,9 @@
@str = private unnamed_addr constant [12 x i8] c"hello again\00"
declare i32 @puts(ptr nocapture) nounwind
define i32 @two()
{
call i32 @puts(ptr @str)
ret i32 0
}

View File

@ -0,0 +1,14 @@
class B < Builder
def run(*args)
puts @target
true
end
end
env do |env|
env.add_builder(B)
env.B("one", File.expand_path("two"))
env.B("two")
env.B("three")
env.depends("two", File.expand_path("three"))
end

View File

@ -0,0 +1,15 @@
class B < Builder
def run(*args)
puts "B:#{@target}"
true
end
end
env do |env|
env.add_builder(B)
env.B("one")
env.B("two")
env.B("three")
env.Barrier(:bar, %w[two three])
env.depends("one", :bar)
end

View File

@ -0,0 +1,13 @@
env("e", echo: :command) do |env|
source_file = "#{env.build_root}/src/foo.c"
FileUtils.mkdir_p(File.dirname(source_file))
File.open(source_file, "w") do |fh|
fh.puts(<<-EOF)
int main()
{
return 29;
}
EOF
end
env.Program("foo.exe", source_file)
end

View File

@ -1,7 +1,7 @@
class MyObject < Rscons::Builder class MyObject < Rscons::Builder
def run(options) def run(options)
if @builder if @builder
if File.exists?(@target) if File.exist?(@target)
true true
else else
false false

View File

@ -4,7 +4,7 @@ class TestBuilder < Rscons::Builder
true true
else else
if @target == "two" if @target == "two"
return false unless File.exists?("one") return false unless File.exist?("one")
end end
wait_time = @env.expand_varref("${wait_time}", @vars) wait_time = @env.expand_varref("${wait_time}", @vars)
@command = ["ruby", "-e", "require 'fileutils'; sleep #{wait_time}; FileUtils.touch('#{@target}');"] @command = ["ruby", "-e", "require 'fileutils'; sleep #{wait_time}; FileUtils.touch('#{@target}');"]

View File

@ -3,5 +3,5 @@ env do |env|
env["CFLAGS"] += %w[-S] env["CFLAGS"] += %w[-S]
env.Object("one.ssss", "one.c", "CPPFLAGS" => ["-DONE"]) env.Object("one.ssss", "one.c", "CPPFLAGS" => ["-DONE"])
env.Object("two.sss", "two.c") env.Object("two.sss", "two.c")
env.Program("two_sources.exe", %w[one.ssss two.sss], "ASFLAGS" => env["ASFLAGS"] + %w[-x assembler]) env.Program("two_sources.exe", %w[one.ssss two.sss], "ASFLAGS" => env["ASFLAGS"] + %w[-x assembler-with-cpp])
end end

View File

@ -0,0 +1,4 @@
env "src" do |env|
env["CPPPATH"] += glob("src/**")
env.Program("^/src", glob("src/**/*.c"))
end

View File

@ -3,7 +3,6 @@ env(echo: :command) do |env|
env.add_build_hook do |builder| env.add_build_hook do |builder|
if builder.name == "Object" && builder.sources.first =~ %r{one\.c} if builder.name == "Object" && builder.sources.first =~ %r{one\.c}
builder.vars["CFLAGS"] << "-O1" builder.vars["CFLAGS"] << "-O1"
builder.sources = ['src/two/two.c']
elsif builder.name == "Object" && builder.target =~ %r{two\.o} elsif builder.name == "Object" && builder.target =~ %r{two\.o}
new_vars = builder.vars.clone new_vars = builder.vars.clone
new_vars["CFLAGS"] << "-O2" new_vars["CFLAGS"] << "-O2"

View File

@ -0,0 +1,15 @@
class Custom < Rscons::Builder
def run(options)
print_run_message("#{name} #{target}", nil)
true
end
end
env do |env|
env.add_builder(Custom)
env.Custom("t3", :phony1)
env.Custom(:phony1, "t2")
env.Custom("t2", :phony2)
env.Custom(:phony2, "t1")
env.Custom("t1", [])
end

View File

@ -0,0 +1,13 @@
variant "debug"
variant "release"
with_variants do
env do |env|
if variant("debug")
env["CPPDEFINES"] << "DEBUG"
else
env["CPPDEFINES"] << "NDEBUG"
end
env.Program("^/prog.exe", "prog.c")
end
end

View File

@ -6,7 +6,7 @@ It supports the following features:
* multi-threaded job execution * multi-threaded job execution
* auto-configuration * auto-configuration
* built-in builders for several common operations * built-in builders for several common operations
* out-of-the-box support for C, C++, and D languages * out-of-the-box support for Assembly, C, C++, D, and LLVM
* extensibility for other languages or custom builders * extensibility for other languages or custom builders
* compatible with Windows, Linux, OS X, and FreeBSD * compatible with Windows, Linux, OS X, and FreeBSD
* colorized output with build progress * colorized output with build progress
@ -265,21 +265,17 @@ which they were added.
Example: Example:
```ruby ```ruby
task "build" do proj_env = env do |env|
env do |env| env.Program("^/proj.elf", glob("src/**/*.c"))
env.Program("^^/proj.elf", glob("src/**/*.c"))
end
end end
task "flash", deps: "build" do task "flash" do
sh "nrfjprog", "-f", "NRF52", "--program", env.expand("^^/proj.elf") sh "nrfjprog", "-f", "NRF52", "--program", proj_env.expand("^/proj.elf")
end end
``` ```
In this example, the `flash` task depends on the `build` task. In this example, the `flash` task would first build the proj.elf target and
So if the project had not yet been built, and the user executes then flash it to target with the nrfjprog program.
`./rscons flash`, the project would first be built and then flashed to the
target.
If the `task` method is called again with the name of an already existing task, If the `task` method is called again with the name of an already existing task,
the task is not overwritten, but rather modified. the task is not overwritten, but rather modified.
@ -287,6 +283,34 @@ Any newly specified dependencies are added to the current dependencies.
Any action block is appended to the task's list of action blocks to execute Any action block is appended to the task's list of action blocks to execute
when the task is executed. when the task is executed.
For example, with the `Rsconscript`:
```ruby
task "a" do
puts "A"
end
task "b" do
puts "B"
end
task "c", depends: "a" do
puts "C1"
end
task "c", depends: "b" do
puts "C2"
end
```
The following behavior is observed:
$ ./rscons c
A
B
C1
C2
Note that for a simple project, the build script may not need to define any Note that for a simple project, the build script may not need to define any
tasks at all and could just make use of the Rscons built-in default task (see tasks at all and could just make use of the Rscons built-in default task (see
${#Default Task}). ${#Default Task}).
@ -378,7 +402,7 @@ argument (task name) automatically filled in by the shortcut method.
For example: For example:
```ruby ```ruby
default deps: "unpack_compiler" do default depends: "unpack_compiler" do
puts "default task" puts "default task"
end end
``` ```
@ -386,7 +410,7 @@ end
is equivalent to: is equivalent to:
```ruby ```ruby
task "default", deps: "unpack_compiler" do task "default", depends: "unpack_compiler" do
puts "default task" puts "default task"
end end
``` ```
@ -463,11 +487,11 @@ task "build" do
... ...
end end
task "flash" do task "flash", depends: "build" do
... ...
end end
default deps: "build" default depends: "build"
``` ```
Then when the user runs `./rscons` the "build" task will be executed. Then when the user runs `./rscons` the "build" task will be executed.
@ -901,11 +925,11 @@ There are several default builders that are built-in to Rscons:
* `Command`, which executes a user-defined command to produce the target. * `Command`, which executes a user-defined command to produce the target.
* `Copy`, which copies files or directories to a specified destination. * `Copy`, which copies files or directories to a specified destination.
* `CFile`, which builds a C or C++ source file from a lex or yacc input file.
* `Directory`, which creates a directory. * `Directory`, which creates a directory.
* `Disassemble`, which disassembles an object file to a disassembly listing. * `Disassemble`, which disassembles an object file to a disassembly listing.
* `Install`, which installs files or directories to a specified destination. * `Install`, which installs files or directories to a specified destination.
* `InstallDirectory`, which creates a directory in an install destination. * `InstallDirectory`, which creates a directory in an install destination.
* `Lex`, which builds a source file from a lex input file.
* `Library`, which collects object files into a static library archive file. * `Library`, which collects object files into a static library archive file.
* `Object`, which compiles source files to produce an object file. * `Object`, which compiles source files to produce an object file.
* `Preprocess`, which invokes the C/C++ preprocessor on a source file. * `Preprocess`, which invokes the C/C++ preprocessor on a source file.
@ -915,6 +939,7 @@ There are several default builders that are built-in to Rscons:
* `SharedObject`, which compiles source files to produce an object file, in a * `SharedObject`, which compiles source files to produce an object file, in a
way that is able to be used to create a shared library. way that is able to be used to create a shared library.
* `Size`, which runs the 'size' utility on an executable file. * `Size`, which runs the 'size' utility on an executable file.
* `Yacc`, which builds a source file from a yacc input file.
####> The Command Builder ####> The Command Builder
@ -941,17 +966,6 @@ The `Command` builder supports the following construction variables:
`CMD_STDOUT` is expanded for variable references, so the token `${_TARGET}` `CMD_STDOUT` is expanded for variable references, so the token `${_TARGET}`
can be used, for example. can be used, for example.
####> The CFile Builder
```ruby
env.CFile(target, source)
# Example
env.CFile("^/parser/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.
####> The Copy Builder ####> The Copy Builder
```ruby ```ruby
@ -1024,6 +1038,17 @@ the `InstallDirectory` builder.
The `uninstall` task removes targets created by the `InstallDirectory` builder The `uninstall` task removes targets created by the `InstallDirectory` builder
but not by the `Directory` builder. but not by the `Directory` builder.
####> The Lex Builder
```ruby
env.Lex(target, source)
# Example
env.Lex("^/lexer.c", "lexer.l")
```
The `Lex` builder will generate a source file from a lex (.l, .ll)
input file.
####> The Library Builder ####> The Library Builder
```ruby ```ruby
@ -1154,6 +1179,17 @@ stores its output in the target file.
The size executable can be specified with the `SIZE` construction variable, The size executable can be specified with the `SIZE` construction variable,
and flags can be specified with `SIZEFLAGS`. and flags can be specified with `SIZEFLAGS`.
####> The Yacc Builder
```ruby
env.Yacc(target, source)
# Example
env.Yacc("^/parser.c", "parser.y")
```
The `Yacc` builder will generate a source file from a yacc (.y, .yy)
input file.
###> Phony Targets ###> Phony Targets
Rscons supports phony build targets. Rscons supports phony build targets.
@ -1272,10 +1308,10 @@ It iterates through each enabled variant and calls the given block.
In this example, the block would be called twice, once with the "kde" variant In this example, the block would be called twice, once with the "kde" variant
active, and the second time with the "gnome" variant active. active, and the second time with the "gnome" variant active.
Each `env()` call creates an Environment, so two environments are created. Each `env` method call creates an Environment, so two environments are created.
When an Environment is created within a `with_variants` block, the When an Environment is created within a `with_variants` block, the
Environment's name has the active variant(s) appended to the given Environment Environment's build directory name has the active variant(s) keys appended to
name (if any), and separated by a "-". the given Environment name, and separated by a "-".
In this example, a "prog-kde" Environment would be created with build root In this example, a "prog-kde" Environment would be created with build root
build/prog-kde and -DKDE would be passed to the compiler when compiling each build/prog-kde and -DKDE would be passed to the compiler when compiling each
@ -1284,6 +1320,29 @@ Next a "prog-gnome" Environment would be created with build root
build/prog-gnome and -DGNOME would be passed to the compiler when compiling build/prog-gnome and -DGNOME would be passed to the compiler when compiling
the sources. the sources.
The key for a variant is the variant's name by default but can be overridden by
passing a `:key` value to the `variant` method.
A `nil` value for the `:key` parameter will omit that variant from appearing
in the environment's build directory name.
For example:
```ruby
variant "debug"
variant "release", key: nil
with_variants do
env "prog" do |env|
end
end
```
In this example, one "prog" environment will be created for the "debug" variant
with the build directory "prog-debug", and another "prog" environment will be
created for the "release" variant with the build directory "prog".
The build directory for the release variant of the "prog" environment is just
"prog" instead of "prog-release" because the key for the "release" variant is
set to `nil`.
Variants are enabled by default, but can be disabled by passing a `false` value Variants are enabled by default, but can be disabled by passing a `false` value
to the `:default` option of the `variant` method. to the `:default` option of the `variant` method.
For example: For example:
@ -1558,8 +1617,8 @@ end
The `Object` and `SharedObject` builders that ship with Rscons have an API that The `Object` and `SharedObject` builders that ship with Rscons have an API that
allows the user to register extra languages that can be suppored by the allows the user to register extra languages that can be suppored by the
builders. builders.
In fact, the built-in support for assembly, C, C++, and D compilation all make In fact, the built-in support for assembly, C, C++, D, and LLVM compilation
use of this built-in API. all make use of this built-in API.
To see an example of how this API is used, see the To see an example of how this API is used, see the
`lib/rscons/builders/lang/*.rb` files in the Rscons source repository. `lib/rscons/builders/lang/*.rb` files in the Rscons source repository.
For example, here is how the C++ language is registered: For example, here is how the C++ language is registered:
@ -1868,6 +1927,15 @@ env do |env|
end end
``` ```
### Example: Building a Program from C and LLVM Sources
```ruby
env do |env|
env["CFLAGS"] << "-Wall"
env.Program("program", glob("src/**/*.{c,ll}"))
end
```
### Example: Cloning an Environment ### Example: Cloning an Environment
```ruby ```ruby
@ -1935,7 +2003,7 @@ end
```ruby ```ruby
env do |env| env do |env|
env.CFile("^/parser.tab.cc", "parser.yy") env.Yacc("^/parser.tab.cc", "parser.yy")
end end
``` ```

View File

@ -23,13 +23,14 @@ module Rscons
# Names of the default builders which will be added to all newly created # Names of the default builders which will be added to all newly created
# {Environment} objects. # {Environment} objects.
DEFAULT_BUILDERS = [ DEFAULT_BUILDERS = [
:CFile, :Barrier,
:Command, :Command,
:Copy, :Copy,
:Directory, :Directory,
:Disassemble, :Disassemble,
:Install, :Install,
:InstallDirectory, :InstallDirectory,
:Lex,
:Library, :Library,
:Object, :Object,
:Preprocess, :Preprocess,
@ -37,6 +38,7 @@ module Rscons
:SharedLibrary, :SharedLibrary,
:SharedObject, :SharedObject,
:Size, :Size,
:Yacc,
] ]
# Class to represent a fatal error during an Rscons operation. # Class to represent a fatal error during an Rscons operation.
@ -65,6 +67,13 @@ module Rscons
target.is_a?(Symbol) target.is_a?(Symbol)
end end
# Generate a random phony target name.
#
# @return [Symbol] Phony target name.
def gen_phony_target
("t" + sprintf("%08x", rand(1_000_000..4_000_000_000))).to_sym
end
# Return the system shell and arguments for executing a shell command. # Return the system shell and arguments for executing a shell command.
# #
# @return [Array<String>] The shell and flag. # @return [Array<String>] The shell and flag.
@ -135,11 +144,12 @@ require_relative "rscons/builders/mixins/object_deps"
require_relative "rscons/builders/mixins/program" require_relative "rscons/builders/mixins/program"
# default builders # default builders
require_relative "rscons/builders/cfile" require_relative "rscons/builders/barrier"
require_relative "rscons/builders/command" require_relative "rscons/builders/command"
require_relative "rscons/builders/copy" require_relative "rscons/builders/copy"
require_relative "rscons/builders/directory" require_relative "rscons/builders/directory"
require_relative "rscons/builders/disassemble" require_relative "rscons/builders/disassemble"
require_relative "rscons/builders/lex"
require_relative "rscons/builders/library" require_relative "rscons/builders/library"
require_relative "rscons/builders/object" require_relative "rscons/builders/object"
require_relative "rscons/builders/preprocess" require_relative "rscons/builders/preprocess"
@ -148,12 +158,14 @@ require_relative "rscons/builders/shared_library"
require_relative "rscons/builders/shared_object" require_relative "rscons/builders/shared_object"
require_relative "rscons/builders/simple_builder" require_relative "rscons/builders/simple_builder"
require_relative "rscons/builders/size" require_relative "rscons/builders/size"
require_relative "rscons/builders/yacc"
# language support # language support
require_relative "rscons/builders/lang/asm" require_relative "rscons/builders/lang/asm"
require_relative "rscons/builders/lang/c" require_relative "rscons/builders/lang/c"
require_relative "rscons/builders/lang/cxx" require_relative "rscons/builders/lang/cxx"
require_relative "rscons/builders/lang/d" require_relative "rscons/builders/lang/d"
require_relative "rscons/builders/lang/llvm_asm"
# Unbuffer $stdout # Unbuffer $stdout
$stdout.sync = true $stdout.sync = true

View File

@ -237,7 +237,7 @@ module Rscons
cache = Cache.instance cache = Cache.instance
cache.targets(true).each do |target| cache.targets(true).each do |target|
cache.remove_target(target) cache.remove_target(target)
next unless File.exists?(target) next unless File.exist?(target)
puts "Removing #{target}" if verbose puts "Removing #{target}" if verbose
FileUtils.rm_f(target) FileUtils.rm_f(target)
end end
@ -258,6 +258,14 @@ module Rscons
# #
# @param name [String] # @param name [String]
# Variant name. # Variant name.
# @param options [Hash]
# Optional parameters.
# @option options [String] :default
# Whether the variant is enabled by default (default: true).
# @option options [String] :key
# Variant key, used to name an Environment's build directory. If nil,
# this variant will not contribute to the Environment's build directory
# name.
def variant(name, options = {}) def variant(name, options = {})
if @active_variants if @active_variants
!!@active_variants.find {|variant| variant[:name] == name} !!@active_variants.find {|variant| variant[:name] == name}
@ -294,6 +302,15 @@ module Rscons
end end
# Create a variant group. # Create a variant group.
#
# @overload variant_group(name, options = {})
# @param name [String]
# Variant group name (optional).
# @param options [Hash]
# Optional variant group parameters.
# @overload variant_group(options = {})
# @param options [Hash]
# Optional variant group parameters.
def variant_group(*args, &block) def variant_group(*args, &block)
if args.first.is_a?(String) if args.first.is_a?(String)
name = args.slice!(0) name = args.slice!(0)

View File

@ -29,19 +29,27 @@ module Rscons
# @return [String, Symbol] # @return [String, Symbol]
# Target file name. # Target file name.
attr_accessor :target attr_reader :target
# @return [Array<String>] # @return [String, Symbol]
# Source file name(s). # Absolute target file name.
attr_accessor :sources attr_reader :abstarget
# @return [Array<String, Symbol>]
# Source file names.
attr_reader :sources
# @return [Array<String, Symbol>]
# Absolute source file names.
attr_reader :abssources
# @return [Cache] # @return [Cache]
# Cache instance. # Cache instance.
attr_accessor :cache attr_reader :cache
# @return [Environment] # @return [Environment]
# The {Environment} performing the build operation. # The {Environment} performing the build operation.
attr_accessor :env attr_reader :env
# @return [Hash, VarSet] # @return [Hash, VarSet]
# Construction variables used to perform the build operation. # Construction variables used to perform the build operation.
@ -49,7 +57,7 @@ module Rscons
# @return [Set<String>] # @return [Set<String>]
# Side effect file(s) produced when this builder runs. # Side effect file(s) produced when this builder runs.
attr_accessor :side_effects attr_reader :side_effects
# @return [Integer] # @return [Integer]
# Build step. # Build step.
@ -71,7 +79,11 @@ module Rscons
# Extra construction variables. # Extra construction variables.
def initialize(options) def initialize(options)
@target = options[:target] @target = options[:target]
@sources = options[:sources] @abstarget = Util.absolute_path(@target)
@sources = Array(options[:sources])
@abssources = @sources.map do |source|
Util.absolute_path(source)
end
@cache = options[:cache] @cache = options[:cache]
@env = options[:env] @env = options[:env]
@vars = options[:vars] @vars = options[:vars]
@ -103,7 +115,7 @@ module Rscons
# @return [void] # @return [void]
def produces(*side_effects) def produces(*side_effects)
side_effects.each do |side_effect| side_effects.each do |side_effect|
side_effect_expanded = @env.expand(side_effect) side_effect_expanded = Util.absolute_path(@env.expand(side_effect))
@env.register_side_effect(side_effect_expanded) @env.register_side_effect(side_effect_expanded)
@side_effects << side_effect_expanded @side_effects << side_effect_expanded
end end

View File

@ -27,8 +27,8 @@ module Rscons
# env.Directory("dest") # env.Directory("dest")
# env.Install("dest", "bin") # env.Install("dest", "bin")
# env.Install("dest", "share") # env.Install("dest", "share")
self[builder.target] ||= [] self[builder.abstarget] ||= []
self[builder.target] << builder self[builder.abstarget] << builder
end end
# Return the number of remaining build steps. # Return the number of remaining build steps.
@ -37,7 +37,7 @@ module Rscons
# The number of remaining build steps. # The number of remaining build steps.
def build_steps_remaining def build_steps_remaining
self.reduce(0) do |result, (target, builders)| self.reduce(0) do |result, (target, builders)|
result + builders.size result + builders.count {|b| !b.is_a?(Rscons::Builders::Barrier)}
end end
end end
@ -54,7 +54,7 @@ module Rscons
# The next builder to run. # The next builder to run.
def get_next_builder_to_run(targets_still_building) def get_next_builder_to_run(targets_still_building)
to_build = self.find do |target, builders| to_build = self.find do |target, builders|
deps = builders.first.sources + (@build_dependencies[target] || []).to_a deps = builders.first.abssources + (@build_dependencies[target] || []).to_a
# All dependencies must have been built for this target to be ready to # All dependencies must have been built for this target to be ready to
# build. # build.
deps.all? do |dep| deps.all? do |dep|
@ -79,7 +79,7 @@ module Rscons
# not find a builder to run above, then there might be a circular # not find a builder to run above, then there might be a circular
# dependency introduced by the user. # dependency introduced by the user.
if (self.size > 0) and targets_still_building.empty? if (self.size > 0) and targets_still_building.empty?
raise "Could not find a runnable builder. Possible circular dependency for #{self.keys.first}" raise "Could not find a runnable builder. Possible circular dependency for #{self.first[1].first.target}"
end end
end end

View File

@ -0,0 +1,15 @@
module Rscons
module Builders
# The Barrier builder does not perform any action. It exists as a builder
# on which to place dependencies to ensure that each of its sources are
# built before any build targets which depend on the barrier build target.
class Barrier < Builder
# Run the builder.
def run(options)
true
end
end
end
end

View File

@ -1,35 +0,0 @@
module Rscons
module Builders
# Build a C or C++ source file given a lex (.l, .ll) or yacc (.y, .yy)
# input file.
#
# Examples::
# env.CFile("parser.tab.cc", "parser.yy")
# env.CFile("lex.yy.cc", "parser.ll")
class CFile < Builder
# Run the builder to produce a build target.
def run(options)
if @command
finalize_command
else
@vars["_TARGET"] = @target
@vars["_SOURCES"] = @sources
case
when @sources.first.end_with?(*@env.expand_varref("${LEXSUFFIX}"))
cmd = "LEX"
message = "Generating lexer"
when @sources.first.end_with?(*@env.expand_varref("${YACCSUFFIX}"))
cmd = "YACC"
message = "Generating parser"
else
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)
end
end
end
end
end

View File

@ -14,14 +14,14 @@ module Rscons
# Run the builder to produce a build target. # Run the builder to produce a build target.
def run(options) def run(options)
target_is_dir = (@sources.length > 1) || target_is_dir = (@sources.length > 1) ||
Dir.exists?(@sources.first) || Dir.exist?(@sources.first) ||
Dir.exists?(@target) Dir.exist?(@target)
outdir = target_is_dir ? @target : File.dirname(@target) outdir = target_is_dir ? @target : File.dirname(@target)
# Collect the list of files to copy over. # Collect the list of files to copy over.
file_map = {} file_map = {}
if target_is_dir if target_is_dir
@sources.each do |src| @sources.each do |src|
if Dir.exists? src if Dir.exist? src
Dir.glob("#{src}/**/*", File::FNM_DOTMATCH).select do |f| Dir.glob("#{src}/**/*", File::FNM_DOTMATCH).select do |f|
File.file?(f) File.file?(f)
end.each do |subfile| end.each do |subfile|
@ -50,7 +50,7 @@ module Rscons
end end
@cache.register_build(dest, :Copy, [src], @env, install: @install_builder) @cache.register_build(dest, :Copy, [src], @env, install: @install_builder)
end end
(target_is_dir ? Dir.exists?(@target) : File.exists?(@target)) ? true : false (target_is_dir ? Dir.exist?(@target) : File.exist?(@target)) ? true : false
end end
end end

View File

@ -14,7 +14,7 @@ module Rscons
def run(options) def run(options)
if File.directory?(@target) if File.directory?(@target)
true true
elsif File.exists?(@target) elsif File.exist?(@target)
Ansi.write($stderr, :red, "Error: `#{@target}' already exists and is not a directory", :reset, "\n") Ansi.write($stderr, :red, "Error: `#{@target}' already exists and is not a directory", :reset, "\n")
false false
else else

View File

@ -0,0 +1,10 @@
Rscons::Builders::Object.register(
command: "${LLVMAS_CMD}",
direct_command: "${LLVMAS_CMD:direct}",
suffix: "${LLVMAS_SUFFIX}",
short_description: "Assembling")
Rscons::Builders::SharedObject.register(
command: "${LLVMAS_CMD}",
direct_command: "${LLVMAS_CMD:direct}",
suffix: "${LLVMAS_SUFFIX}",
short_description: "Assembling")

View File

@ -0,0 +1,24 @@
module Rscons
module Builders
# Build a source file given a lex input file.
#
# Examples::
# env.Lex("lex.c", "parser.l")
# env.Lex("lex.cc", "parser.ll")
class Lex < Builder
# Run the builder to produce a build target.
def run(options)
if @command
finalize_command
else
@vars["_TARGET"] = @target
@vars["_SOURCES"] = @sources
command = @env.build_command("${LEX_CMD}", @vars)
standard_command("Generating lexer source from <source>#{Util.short_format_paths(@sources)}<reset> => <target>#{@target}<reset>", command)
end
end
end
end
end

View File

@ -8,7 +8,7 @@ module Rscons
# dependency file. # dependency file.
def finalize_command_with_depfile def finalize_command_with_depfile
deps = @sources deps = @sources
if File.exists?(@vars["_DEPFILE"]) if File.exist?(@vars["_DEPFILE"])
deps += Util.parse_dependency_file(@vars["_DEPFILE"]) deps += Util.parse_dependency_file(@vars["_DEPFILE"])
end end
@cache.register_build(@target, @command, deps.uniq, @env) @cache.register_build(@target, @command, deps.uniq, @env)

View File

@ -0,0 +1,24 @@
module Rscons
module Builders
# Build a source file given a yacc input file.
#
# Examples::
# env.Yacc("parser.c", "parser.y")
# env.Yacc("parser.cc", "parser.yy")
class Yacc < Builder
# Run the builder to produce a build target.
def run(options)
if @command
finalize_command
else
@vars["_TARGET"] = @target
@vars["_SOURCES"] = @sources
command = @env.build_command("${YACC_CMD}", @vars)
standard_command("Generating parser source from <source>#{Util.short_format_paths(@sources)}<reset> => <target>#{@target}<reset>", command)
end
end
end
end
end

View File

@ -142,12 +142,14 @@ module Rscons
# - each cached dependency file's current checksum matches the checksum # - each cached dependency file's current checksum matches the checksum
# stored in the cache file # stored in the cache file
def up_to_date?(targets, command, deps, env, options = {}) def up_to_date?(targets, command, deps, env, options = {})
deps = deps.map {|dep| Util.absolute_path(dep)}
Array(targets).each do |target| Array(targets).each do |target|
cache_key = get_cache_key(target) abstarget = Util.absolute_path(target)
cache_key = get_cache_key(abstarget)
unless Rscons.phony_target?(target) unless Rscons.phony_target?(abstarget)
# target file must exist on disk # target file must exist on disk
unless File.exists?(target) unless File.exist?(abstarget)
if options[:debug] if options[:debug]
puts "Target #{target} needs rebuilding because it does not exist on disk" puts "Target #{target} needs rebuilding because it does not exist on disk"
end end
@ -163,9 +165,9 @@ module Rscons
return false return false
end end
unless Rscons.phony_target?(target) unless Rscons.phony_target?(abstarget)
# target must have the same checksum as when it was built last # target must have the same checksum as when it was built last
unless @cache["targets"][cache_key]["checksum"] == lookup_checksum(target) unless @cache["targets"][cache_key]["checksum"] == lookup_checksum(abstarget)
if options[:debug] if options[:debug]
puts "Target #{target} needs rebuilding because it has been changed on disk since being built last" puts "Target #{target} needs rebuilding because it has been changed on disk since being built last"
end end
@ -202,7 +204,7 @@ module Rscons
end end
# set of user dependencies must match # set of user dependencies must match
user_deps = env.get_user_deps(target) || [] user_deps = env.get_user_deps(abstarget) || []
cached_user_deps = @cache["targets"][cache_key]["user_deps"] || [] cached_user_deps = @cache["targets"][cache_key]["user_deps"] || []
cached_user_deps_fnames = cached_user_deps.map { |dc| dc["fname"] } cached_user_deps_fnames = cached_user_deps.map { |dc| dc["fname"] }
unless user_deps == cached_user_deps_fnames unless user_deps == cached_user_deps_fnames
@ -247,12 +249,16 @@ module Rscons
# @return [void] # @return [void]
def register_build(targets, command, deps, env, options = {}) def register_build(targets, command, deps, env, options = {})
Array(targets).each do |target| Array(targets).each do |target|
target = Util.absolute_path(target)
target_checksum = target_checksum =
if options[:side_effect] or Rscons.phony_target?(target) if options[:side_effect] or Rscons.phony_target?(target)
"" ""
else else
calculate_checksum(target) calculate_checksum(target)
end end
deps = deps.map do |dep|
Util.absolute_path(dep)
end.uniq
@cache["targets"][get_cache_key(target)] = { @cache["targets"][get_cache_key(target)] = {
"command" => Digest::MD5.hexdigest(command.inspect), "command" => Digest::MD5.hexdigest(command.inspect),
"checksum" => target_checksum, "checksum" => target_checksum,
@ -307,7 +313,7 @@ module Rscons
parts.each_index do |i| parts.each_index do |i|
next if parts[i] == "" next if parts[i] == ""
subpath = File.join(*parts[0, i + 1]) subpath = File.join(*parts[0, i + 1])
unless File.exists?(subpath) unless File.exist?(subpath)
FileUtils.mkdir_p(subpath) FileUtils.mkdir_p(subpath)
@cache["directories"][subpath] = !!options[:install] @cache["directories"][subpath] = !!options[:install]
end end

View File

@ -118,13 +118,13 @@ module Rscons
# Find the build script. # Find the build script.
if rsconscript if rsconscript
unless File.exists?(rsconscript) unless File.exist?(rsconscript)
$stderr.puts "Cannot read #{rsconscript}" $stderr.puts "Cannot read #{rsconscript}"
return 1 return 1
end end
else else
rsconscript = DEFAULT_RSCONSCRIPTS.find do |f| rsconscript = DEFAULT_RSCONSCRIPTS.find do |f|
File.exists?(f) File.exist?(f)
end end
end end

View File

@ -1,5 +1,6 @@
require "fileutils" require "fileutils"
require "open3" require "open3"
require "set"
module Rscons module Rscons
# Class to manage a configure operation. # Class to manage a configure operation.
@ -10,9 +11,11 @@ module Rscons
# @param script [Script] # @param script [Script]
# Build script. # Build script.
def initialize(script) def initialize(script)
@tested_compilers = {}
@work_dir = "#{Rscons.application.build_dir}/_configure" @work_dir = "#{Rscons.application.build_dir}/_configure"
FileUtils.mkdir_p(@work_dir) FileUtils.mkdir_p(@work_dir)
@log_fh = File.open("#{@work_dir}/config.log", "wb") @log_file_name = "#{@work_dir}/config.log"
@log_fh = File.open(@log_file_name, "wb")
cache = Cache.instance cache = Cache.instance
cache["failed_commands"] = [] cache["failed_commands"] = []
cache["configuration_data"] = {} cache["configuration_data"] = {}
@ -64,7 +67,13 @@ module Rscons
cc = ccc.find do |cc| cc = ccc.find do |cc|
test_c_compiler(cc, options) test_c_compiler(cc, options)
end end
complete(cc ? 0 : 1, options.merge(success_message: cc)) if cc
@tested_compilers["c"] ||= Set.new
@tested_compilers["c"] << cc
end
complete(cc ? 0 : 1, options.merge(
success_message: cc,
fail_message: "not found (checked #{ccc.join(", ")})"))
end end
# Check for a working C++ compiler. # Check for a working C++ compiler.
@ -86,7 +95,13 @@ module Rscons
cc = ccc.find do |cc| cc = ccc.find do |cc|
test_cxx_compiler(cc, options) test_cxx_compiler(cc, options)
end end
complete(cc ? 0 : 1, options.merge(success_message: cc)) if cc
@tested_compilers["cxx"] ||= Set.new
@tested_compilers["cxx"] << cc
end
complete(cc ? 0 : 1, options.merge(
success_message: cc,
fail_message: "not found (checked #{ccc.join(", ")})"))
end end
# Check for a working D compiler. # Check for a working D compiler.
@ -103,12 +118,18 @@ module Rscons
end end
if cdc.empty? if cdc.empty?
# Default D compiler search array. # Default D compiler search array.
cdc = %w[gdc ldc2] cdc = %w[gdc ldc2 ldc]
end end
dc = cdc.find do |dc| dc = cdc.find do |dc|
test_d_compiler(dc, options) test_d_compiler(dc, options)
end end
complete(dc ? 0 : 1, options.merge(success_message: dc)) if dc
@tested_compilers["d"] ||= Set.new
@tested_compilers["d"] << dc
end
complete(dc ? 0 : 1, options.merge(
success_message: dc,
fail_message: "not found (checked #{cdc.join(", ")})"))
end end
# Check for a package or configure program output. # Check for a package or configure program output.
@ -118,7 +139,12 @@ module Rscons
elsif program = options[:program] elsif program = options[:program]
Ansi.write($stdout, "Checking '", :cyan, program, :reset, "'... ") Ansi.write($stdout, "Checking '", :cyan, program, :reset, "'... ")
end end
program ||= "pkg-config" unless program
program = "pkg-config"
unless Util.find_executable(program)
raise RsconsError.new("Error: executable '#{program}' not found")
end
end
args = options[:args] || %w[--cflags --libs] args = options[:args] || %w[--cflags --libs]
command = [program, *args, package].compact command = [program, *args, package].compact
stdout, _, status = log_and_test_command(command) stdout, _, status = log_and_test_command(command)
@ -240,17 +266,36 @@ module Rscons
def check_lib(lib, options = {}) def check_lib(lib, options = {})
check_libpath = [nil] + (options[:check_libpath] || []) check_libpath = [nil] + (options[:check_libpath] || [])
Ansi.write($stdout, "Checking for library '", :cyan, lib, :reset, "'... ") Ansi.write($stdout, "Checking for library '", :cyan, lib, :reset, "'... ")
File.open("#{@work_dir}/cfgtest.c", "wb") do |fh| if @tested_compilers["d"]
fh.puts <<-EOF source_file = "#{@work_dir}/cfgtest.d"
int main(int argc, char * argv[]) { File.open(source_file, "wb") do |fh|
return 0; fh.puts <<-EOF
} int main() {
EOF return 0;
}
EOF
end
else
source_file = "#{@work_dir}/cfgtest.c"
File.open(source_file, "wb") do |fh|
fh.puts <<-EOF
int main(int argc, char * argv[]) {
return 0;
}
EOF
end
end
ld = "${CC}"
%w[d cxx c].each do |language|
if @tested_compilers[language]
ld = @tested_compilers[language].first
break
end
end end
vars = { vars = {
"LD" => "${CC}", "LD" => ld,
"LIBS" => [lib], "LIBS" => [lib],
"_SOURCES" => "#{@work_dir}/cfgtest.c", "_SOURCES" => source_file,
"_TARGET" => "#{@work_dir}/cfgtest.exe", "_TARGET" => "#{@work_dir}/cfgtest.exe",
} }
status = 1 status = 1
@ -375,6 +420,8 @@ module Rscons
# A define to set (in CPPDEFINES) if the requested item is found. # A define to set (in CPPDEFINES) if the requested item is found.
# @option options [String] :success_message # @option options [String] :success_message
# Message to print on success (default "found"). # Message to print on success (default "found").
# @option options [String] :fail_message
# Message to print on failure (default "not found").
def complete(status, options) def complete(status, options)
success_message = options[:success_message] || "found" success_message = options[:success_message] || "found"
fail_message = options[:fail_message] || "not found" fail_message = options[:fail_message] || "not found"
@ -398,7 +445,7 @@ module Rscons
options[:on_fail].call options[:on_fail].call
end end
if should_fail if should_fail
raise RsconsError.new("Configuration failed") raise RsconsError.new("Configuration failed; log file written to #{@log_file_name}")
end end
end end
end end
@ -482,8 +529,10 @@ module Rscons
env = BasicEnvironment.new env = BasicEnvironment.new
merge = { merge = {
"DC" => dc, "DC" => dc,
"DCCMD" => env["DCCMD"].map {|e| e.sub(/^-o$/, "-of")}, "DC:-o" => "-of",
"LDCMD" => env["LDCMD"].map {|e| e.sub(/^-o$/, "-of")}, "LD:-o" => "-of",
"LIBDIRPREFIX" => "-L-L",
"LIBLINKPREFIX" => "-L-l",
"DDEPGEN" => ["-deps=${_DEPFILE}"], "DDEPGEN" => ["-deps=${_DEPFILE}"],
} }
merge["OBJSUFFIX"] = [ldc_objsuffix] merge["OBJSUFFIX"] = [ldc_objsuffix]

View File

@ -35,8 +35,9 @@ module Rscons
"CXXFLAGS" => [], "CXXFLAGS" => [],
"CXXSUFFIX" => %w[.cc .cpp .cxx .C], "CXXSUFFIX" => %w[.cc .cpp .cxx .C],
"DC" => "gdc", "DC" => "gdc",
"DCCMD" => %w[${DC} -c -o ${_TARGET} ${DDEPGEN} ${INCPREFIX}${D_IMPORT_PATH} ${DFLAGS} ${_SOURCES}], "DC:-o" => "-o",
"DCCMD:direct" => %w[${DC} -o ${_TARGET} ${DDEPGEN} ${INCPREFIX}${D_IMPORT_PATH} ${DFLAGS} ${LDFLAGS} ${_SOURCES} ${LIBDIRPREFIX}${LIBPATH} ${LIBLINKPREFIX}${LIBS}], "DCCMD" => %w[${DC} -c ${DC:-o} ${_TARGET} ${DDEPGEN} ${INCPREFIX}${D_IMPORT_PATH} ${DFLAGS} ${_SOURCES}],
"DCCMD:direct" => %w[${DC} ${DC:-o} ${_TARGET} ${DDEPGEN} ${INCPREFIX}${D_IMPORT_PATH} ${DFLAGS} ${LDFLAGS} ${_SOURCES} ${LIBDIRPREFIX}${LIBPATH} ${LIBLINKPREFIX}${LIBS}],
"DDEPGEN" => %w[-MMD -MF ${_DEPFILE}], "DDEPGEN" => %w[-MMD -MF ${_DEPFILE}],
"DEPFILESUFFIX" => ".mf", "DEPFILESUFFIX" => ".mf",
"DFLAGS" => [], "DFLAGS" => [],
@ -46,10 +47,10 @@ module Rscons
"D_IMPORT_PATH" => [], "D_IMPORT_PATH" => [],
"INCPREFIX" => "-I", "INCPREFIX" => "-I",
"LD" => nil, "LD" => nil,
"LDCMD" => %w[${LD} -o ${_TARGET} ${LDFLAGS} ${_SOURCES} ${LIBDIRPREFIX}${LIBPATH} ${LIBLINKPREFIX}${LIBS}], "LD:-o" => "-o",
"LDCMD" => %w[${LD} ${LD:-o} ${_TARGET} ${LDFLAGS} ${_SOURCES} ${LIBDIRPREFIX}${LIBPATH} ${LIBLINKPREFIX}${LIBS}],
"LDFLAGS" => [], "LDFLAGS" => [],
"LEX" => "flex", "LEX" => "flex",
"LEXSUFFIX" => %w[.l .ll],
"LEX_CMD" => %w[${LEX} ${LEX_FLAGS} -o ${_TARGET} ${_SOURCES}], "LEX_CMD" => %w[${LEX} ${LEX_FLAGS} -o ${_TARGET} ${_SOURCES}],
"LEX_FLAGS" => [], "LEX_FLAGS" => [],
"LIBDIRPREFIX" => "-L", "LIBDIRPREFIX" => "-L",
@ -57,6 +58,11 @@ module Rscons
"LIBPATH" => [], "LIBPATH" => [],
"LIBS" => [], "LIBS" => [],
"LIBSUFFIX" => ".a", "LIBSUFFIX" => ".a",
"LLVMAS" => "clang",
"LLVMAS_CMD" => %w[${LLVMAS} -c -o ${_TARGET} ${LLVMAS_FLAGS} ${_SOURCES}],
"LLVMAS_CMD:direct" => %w[${LLVMAS} -o ${_TARGET} ${LLVMAS_FLAGS} ${LDFLAGS} ${_SOURCES} ${LIBDIRPREFIX}${LIBPATH} ${LIBLINKPREFIX}${LIBS}],
"LLVMAS_FLAGS" => [],
"LLVMAS_SUFFIX" => ".ll",
"OBJDUMP" => "objdump", "OBJDUMP" => "objdump",
"OBJSUFFIX" => %w[.o], "OBJSUFFIX" => %w[.o],
"PROGSUFFIX" => on_windows ? ".exe" : "", "PROGSUFFIX" => on_windows ? ".exe" : "",
@ -84,7 +90,6 @@ module Rscons
"SIZECMD" => %w[${SIZE} ${SIZEFLAGS} ${_SOURCES}], "SIZECMD" => %w[${SIZE} ${SIZEFLAGS} ${_SOURCES}],
"SIZEFLAGS" => [], "SIZEFLAGS" => [],
"YACC" => "bison", "YACC" => "bison",
"YACCSUFFIX" => %w[.y .yy],
"YACC_CMD" => %w[${YACC} ${YACC_FLAGS} -o ${_TARGET} ${_SOURCES}], "YACC_CMD" => %w[${YACC} ${YACC_FLAGS} -o ${_TARGET} ${_SOURCES}],
"YACC_FLAGS" => %w[-d], "YACC_FLAGS" => %w[-d],
} }

View File

@ -74,15 +74,19 @@ module Rscons
# Create an Environment object. # Create an Environment object.
def initialize(*args, &block) def initialize(*args, &block)
@id = self.class.get_id @id = self.class.get_id
if args.first.is_a?(String)
base_name = args.slice!(0)
else
base_name = "e.#{@id}"
end
variant_keys = (Rscons.application.active_variants || []).map do |variant| variant_keys = (Rscons.application.active_variants || []).map do |variant|
variant[:key] variant[:key]
end.compact end.compact
@name = [base_name, *variant_keys].join("-") if args.first.is_a?(String)
base_name = args.slice!(0)
@name = [base_name, *variant_keys].join("-")
@build_root = "#{Rscons.application.build_dir}/#{@name}"
else
if variant_keys.size > 0
raise RsconsError.new("Error: an Environment with active variants must be given a name")
end
@build_root = Rscons.application.build_dir
end
options = args.first || {} options = args.first || {}
unless Cache.instance["configuration_data"]["configured"] unless Cache.instance["configuration_data"]["configured"]
raise "Project must be configured before creating an Environment" raise "Project must be configured before creating an Environment"
@ -114,8 +118,8 @@ module Rscons
else else
:short :short
end end
@build_root = "#{Rscons.application.build_dir}/#{@name}"
@n_threads = Rscons.application.n_threads @n_threads = Rscons.application.n_threads
@build_steps = 0
self.class.register(self) self.class.register(self)
if block if block
Environment.running_environment = self Environment.running_environment = self
@ -276,7 +280,15 @@ module Rscons
if extra_path = builder_class.extra_path if extra_path = builder_class.extra_path
extra_path = "/#{extra_path}" extra_path = "/#{extra_path}"
end end
"#{@build_root}#{extra_path}/#{Util.make_relative_path("#{source_fname}#{suffix}")}".gsub("\\", "/") source_fname = source_fname.gsub("\\", "/")
if extra_path.nil? && source_fname.start_with?("#{@build_root}/")
# If the source file is in the environment's build directory and no
# builder "extra path" is present, then just place the output file
# next to the source file.
"#{source_fname}#{suffix}"
else
"#{@build_root}/o#{extra_path}/#{Util.make_relative_path("#{source_fname}#{suffix}")}"
end
end end
# Build all build targets specified in the Environment. # Build all build targets specified in the Environment.
@ -359,7 +371,8 @@ module Rscons
@builder_sets << build_builder_set @builder_sets << build_builder_set
end end
@builder_sets.last << builder @builder_sets.last << builder
@build_targets[target] = builder @build_steps += 1 unless builder.is_a?(Rscons::Builders::Barrier)
@build_targets[builder.abstarget] = builder
builder builder
else else
super super
@ -383,8 +396,14 @@ module Rscons
end end
expand(ud) expand(ud)
end end
target = Util.absolute_path(target)
@user_deps[target] ||= [] @user_deps[target] ||= []
@user_deps[target] = (@user_deps[target] + user_deps).uniq user_deps.map! {|ud| Util.absolute_path(ud)}
user_deps.each do |ud|
unless Rscons.phony_target?(ud) || @user_deps[target].include?(ud)
@user_deps[target] << ud
end
end
build_after(target, user_deps) build_after(target, user_deps)
end end
@ -415,13 +434,13 @@ module Rscons
targets = Array(targets) targets = Array(targets)
prerequisites = Array(prerequisites) prerequisites = Array(prerequisites)
targets.each do |target| targets.each do |target|
target = expand(target) target = Util.absolute_path(expand(target))
@registered_build_dependencies[target] ||= Set.new @registered_build_dependencies[target] ||= Set.new
prerequisites.each do |prerequisite| prerequisites.each do |prerequisite|
if prerequisite.is_a?(Builder) if prerequisite.is_a?(Builder)
prerequisite = prerequisite.target prerequisite = prerequisite.target
end end
prerequisite = expand(prerequisite) prerequisite = Util.absolute_path(expand(prerequisite))
@registered_build_dependencies[target] << prerequisite @registered_build_dependencies[target] << prerequisite
end end
end end
@ -437,9 +456,9 @@ module Rscons
# #
# @return [void] # @return [void]
def produces(target, *side_effects) def produces(target, *side_effects)
target = expand(target) abstarget = Util.absolute_path(expand(target))
@builder_sets.reverse.each do |builder_set| @builder_sets.reverse.each do |builder_set|
if builders = builder_set[target] if builders = builder_set[abstarget]
builders.last.produces(*side_effects) builders.last.produces(*side_effects)
return return
end end
@ -456,7 +475,7 @@ module Rscons
# @param side_effect [String] # @param side_effect [String]
# Side effect fiel name. # Side effect fiel name.
def register_side_effect(side_effect) def register_side_effect(side_effect)
@side_effects << side_effect @side_effects << Util.absolute_path(side_effect)
end end
# Return the list of user dependencies for a given target. # Return the list of user dependencies for a given target.
@ -467,6 +486,7 @@ module Rscons
# List of user-specified dependencies for the target, or nil if none were # List of user-specified dependencies for the target, or nil if none were
# specified. # specified.
def get_user_deps(target) def get_user_deps(target)
target = Util.absolute_path(target)
@user_deps[target] @user_deps[target]
end end
@ -490,10 +510,11 @@ module Rscons
# @return [String] # @return [String]
# Output file name. # Output file name.
def register_dependency_build(target, source, suffix, vars, builder_class) def register_dependency_build(target, source, suffix, vars, builder_class)
target = Util.absolute_path(target)
output_fname = get_build_fname(source, suffix, builder_class) output_fname = get_build_fname(source, suffix, builder_class)
self.__send__(builder_class.name, output_fname, source, vars) self.__send__(builder_class.name, output_fname, source, vars)
@registered_build_dependencies[target] ||= Set.new @registered_build_dependencies[target] ||= Set.new
@registered_build_dependencies[target] << output_fname @registered_build_dependencies[target] << Util.absolute_path(output_fname)
output_fname output_fname
end end
@ -570,6 +591,7 @@ module Rscons
# @return [Builder, nil] # @return [Builder, nil]
# The {Builder} for target, or +nil+ if none found. # The {Builder} for target, or +nil+ if none found.
def builder_for(target) def builder_for(target)
target = Util.absolute_path(target)
@build_targets[target] @build_targets[target]
end end
@ -610,7 +632,9 @@ module Rscons
# #
# @return [void] # @return [void]
def run_builder(builder) def run_builder(builder)
builder.build_step ||= get_next_build_step unless builder.is_a?(Rscons::Builders::Barrier)
builder.build_step ||= get_next_build_step
end
case result = builder.run({}) case result = builder.run({})
when Array when Array
result.each do |waititem| result.each do |waititem|
@ -636,8 +660,10 @@ module Rscons
Cache.instance.register_build(side_effect, nil, [], self, side_effect: true) Cache.instance.register_build(side_effect, nil, [], self, side_effect: true)
@side_effects.delete(side_effect) @side_effects.delete(side_effect)
end end
@build_hooks[:post].each do |build_hook_block| unless builder.is_a?(Rscons::Builders::Barrier)
build_hook_block.call(builder) @build_hooks[:post].each do |build_hook_block|
build_hook_block.call(builder)
end
end end
process_remove_wait(builder) process_remove_wait(builder)
else else
@ -709,13 +735,15 @@ module Rscons
# If no builder was found to run yet and there are threads available, try # If no builder was found to run yet and there are threads available, try
# to get a runnable builder from the builder set. # to get a runnable builder from the builder set.
targets_still_building = @threads.reduce([]) do |result, (thread, obj)| targets_still_building = @threads.reduce([]) do |result, (thread, obj)|
result << builder_for_thread(thread).target result << builder_for_thread(thread).abstarget
end end
if @builder_sets.size > 0 if @builder_sets.size > 0
if builder = @builder_sets[0].get_next_builder_to_run(targets_still_building) if builder = @builder_sets[0].get_next_builder_to_run(targets_still_building)
builder.vars = @varset.merge(builder.vars) builder.vars = @varset.merge(builder.vars)
@build_hooks[:pre].each do |build_hook_block| unless builder.is_a?(Rscons::Builders::Barrier)
build_hook_block.call(builder) @build_hooks[:pre].each do |build_hook_block|
build_hook_block.call(builder)
end
end end
return run_builder(builder) return run_builder(builder)
end end

View File

@ -289,27 +289,80 @@ module Rscons
end end
# Create or modify a task. # Create or modify a task.
#
# @overload task(name, options = {}, &block)
# @param name [String]
# Task name.
# @param options [Hash]
# Optional task attributes.
# @option options [Boolean] :autoconf
# Whether to automatically configure before running this task
# (default: true).
# @option options [String] :desc
# Description for the task.
# @option options [Array<String>] :depends
# Dependencies of the task.
# @option options [Array<Task::Param>] :params
# Task parameter definitions.
#
# The action block given will be invoked by Rscons when the task
# executes. It will be passed two arguments:
# 1) The Task object.
# 2) A Hash of task parameter names and values.
def task(*args, &block) def task(*args, &block)
Util.task(*args, &block) Util.task(*args, &block)
end end
# Define a variant, or within a with_variants block, query if it is # Define a variant, or within a with_variants block, query if it is
# active. # active.
def variant(*args) #
Rscons.application.variant(*args) # @param name [String]
# Variant name.
# @param options [Hash]
# Optional parameters.
# @option options [String] :default
# Whether the variant is enabled by default (default: true).
# @option options [String] :key
# Variant key, used to name an Environment's build directory. If nil,
# this variant will not contribute to the Environment's build directory
# name.
def variant(name, options = {})
Rscons.application.variant(name, options)
end end
# Check if a variant is enabled. # Check if a variant is enabled.
def variant_enabled?(*args) #
Rscons.application.variant_enabled?(*args) # This can be used, for example, in a configuration block to omit or
# include configuration checks based on which variants have been
# configured.
#
# @param variant_name [String]
# Variant name.
#
# @return [Boolean]
# Whether the requested variant is enabled.
def variant_enabled?(variant_name)
Rscons.application.variant_enabled?(variant_name)
end end
# Create a variant group. # Create a variant group.
#
# @overload variant_group(name, options = {})
# @param name [String]
# Variant group name (optional).
# @param options [Hash]
# Optional variant group parameters.
# @overload variant_group(options = {})
# @param options [Hash]
# Optional variant group parameters.
def variant_group(*args, &block) def variant_group(*args, &block)
Rscons.application.variant_group(*args, &block) Rscons.application.variant_group(*args, &block)
end end
# Iterate through variants. # Iterate through enabled variants.
#
# The given block is called for each combination of enabled variants
# across the defined variant groups.
def with_variants(&block) def with_variants(&block)
Rscons.application.enable_variants Rscons.application.enable_variants
Rscons.application.with_variants(&block) Rscons.application.with_variants(&block)
@ -349,11 +402,17 @@ module Rscons
# Top-level DSL available to the Rsconscript. # Top-level DSL available to the Rsconscript.
class TopLevelDsl < GlobalDsl class TopLevelDsl < GlobalDsl
# Set the project name. # Set the project name.
#
# @param project_name [String]
# Project name.
def project_name(project_name) def project_name(project_name)
@script.project_name = project_name @script.project_name = project_name
end end
# Whether to automatically configure (default true). # Set whether to automatically configure (default true).
#
# @param autoconf [Boolean]
# Whether to automatically configure.
def autoconf(autoconf) def autoconf(autoconf)
@script.autoconf = autoconf @script.autoconf = autoconf
end end

View File

@ -16,6 +16,21 @@ module Rscons
end end
end end
# Return the absolute path for a given target if not a phony target.
#
# @param path [String, Symbol, nil]
# Given target name.
#
# @return [String, Symbol, nil]
# Absolute path of given target.
def absolute_path(path)
if path.is_a?(String)
File.expand_path(path)
else
path
end
end
# Colorize a builder run message. # Colorize a builder run message.
# #
# @param message [String] # @param message [String]

View File

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

View File

@ -1,7 +1,10 @@
#!/usr/bin/env ruby #!/usr/bin/env ruby
require "fileutils" require "base64"
require "digest/md5" require "digest/md5"
require "fileutils"
require "stringio"
require "zlib"
if File.read("lib/rscons/version.rb") =~ /VERSION = "(.+)"/ if File.read("lib/rscons/version.rb") =~ /VERSION = "(.+)"/
VERSION = $1 VERSION = $1
@ -22,7 +25,7 @@ combine_files = lambda do |file|
require_name = $1 require_name = $1
if require_name =~ %r{^#{PROG_NAME}(?:/.*)?$} if require_name =~ %r{^#{PROG_NAME}(?:/.*)?$}
path = "#{LIB_DIR}/#{require_name}.rb" path = "#{LIB_DIR}/#{require_name}.rb"
if File.exists?(path) if File.exist?(path)
unless files_processed[path] unless files_processed[path]
files_processed[path] = true files_processed[path] = true
combine_files[path] combine_files[path]
@ -68,11 +71,15 @@ license = File.read("LICENSE.txt").gsub(/^(.*?)$/) do |line|
end end
end end
require "zlib"
require "base64"
compressed_script = Zlib::Deflate.deflate(stripped.join) compressed_script = Zlib::Deflate.deflate(stripped.join)
hash = Digest::MD5.hexdigest(compressed_script)
encoded_compressed_script = Base64.encode64(compressed_script).gsub("\n", "") encoded_compressed_script = Base64.encode64(compressed_script).gsub("\n", "")
hash = Digest::MD5.hexdigest(encoded_compressed_script) encoded_compressed_script_io = StringIO.new(encoded_compressed_script)
commented_encoded_compressed_script = ""
until encoded_compressed_script_io.eof?
line = encoded_compressed_script_io.read(64)
commented_encoded_compressed_script += "##{line}\n"
end
FileUtils.rm_rf(DIST) FileUtils.rm_rf(DIST)
FileUtils.mkdir_p(DIST) FileUtils.mkdir_p(DIST)
@ -82,25 +89,53 @@ File.open("#{DIST}/#{PROG_NAME}", "wb", 0755) do |fh|
#{license} #{license}
BASE64CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
def base64_decode(s)
out = ""
v = 0
bits = 0
s.each_char do |c|
if cv = BASE64CHARS.index(c)
v = (v << 6) | cv
bits += 6
elsif c == "="
break
end
if bits >= 8
out += (v >> (bits - 8)).chr
v &= 0xFFFFFFFF >> (32 - (bits - 8))
bits -= 8
end
end
out
end
script = File.join(File.dirname(__FILE__), ".rscons-#{VERSION}-#{hash}.rb") script = File.join(File.dirname(__FILE__), ".rscons-#{VERSION}-#{hash}.rb")
unless File.exists?(script) unless File.exist?(script)
if File.read(__FILE__, mode: "rb") =~ /^#==>(.*)/ if File.read(__FILE__, mode: "rb") =~ /^#==>(.*)/m
require "zlib" require "zlib"
require "base64"
encoded_compressed = $1 encoded_compressed = $1
unescaped_compressed = Base64.decode64(encoded_compressed) compressed = base64_decode(encoded_compressed)
inflated = Zlib::Inflate.inflate(unescaped_compressed) if ENV["rscons_dist_specs"]
require "digest/md5"
if Digest::MD5.hexdigest(compressed) != "#{hash}"
raise "Hash mismatch when decompressing rscons executable"
end
end
inflated = Zlib::Inflate.inflate(compressed)
File.open(script, "wb") do |fh| File.open(script, "wb") do |fh|
fh.write(inflated) fh.write(inflated)
end end
else else
raise "Could not decompress." raise "Error expanding rscons executable"
end end
end end
load script load script
if __FILE__ == $0 if __FILE__ == $0
Rscons::Cli.new.run(ARGV) Rscons::Cli.new.run(ARGV)
end end
#==>#{encoded_compressed_script} #==>
#{commented_encoded_compressed_script}
EOF EOF
end end

View File

@ -99,7 +99,7 @@ class Generator
end end
template = File.read("rb/assets/user_guide.html.erb") template = File.read("rb/assets/user_guide.html.erb")
erb = ERB.new(template, nil, "<>") erb = ERB.new(template, trim_mode: "<>")
if multi_page if multi_page
@pages.each_with_index do |page, page_index| @pages.each_with_index do |page, page_index|

File diff suppressed because it is too large Load Diff

View File

@ -17,14 +17,14 @@ module Rscons
it "makes directories and records any created in the cache" do it "makes directories and records any created in the cache" do
_cache = {} _cache = {}
cache = build_from(_cache) cache = build_from(_cache)
expect(File).to receive(:exists?).with("one").and_return(true) expect(File).to receive(:exist?).with("one").and_return(true)
expect(File).to receive(:exists?).with("one/two").and_return(false) expect(File).to receive(:exist?).with("one/two").and_return(false)
expect(FileUtils).to receive(:mkdir_p).with("one/two") expect(FileUtils).to receive(:mkdir_p).with("one/two")
expect(File).to receive(:exists?).with("one/two/three").and_return(false) expect(File).to receive(:exist?).with("one/two/three").and_return(false)
expect(FileUtils).to receive(:mkdir_p).with("one/two/three") expect(FileUtils).to receive(:mkdir_p).with("one/two/three")
expect(File).to receive(:exists?).with("one").and_return(true) expect(File).to receive(:exist?).with("one").and_return(true)
expect(File).to receive(:exists?).with("one/two").and_return(true) expect(File).to receive(:exist?).with("one/two").and_return(true)
expect(File).to receive(:exists?).with("one/two/four").and_return(false) expect(File).to receive(:exist?).with("one/two/four").and_return(false)
expect(FileUtils).to receive(:mkdir_p).with("one/two/four") expect(FileUtils).to receive(:mkdir_p).with("one/two/four")
cache.mkdir_p("one/two/three") cache.mkdir_p("one/two/three")
cache.mkdir_p("one\\two\\four") cache.mkdir_p("one\\two\\four")
@ -34,8 +34,8 @@ module Rscons
it "handles absolute paths" do it "handles absolute paths" do
_cache = {} _cache = {}
cache = build_from(_cache) cache = build_from(_cache)
expect(File).to receive(:exists?).with("/one").and_return(true) expect(File).to receive(:exist?).with("/one").and_return(true)
expect(File).to receive(:exists?).with("/one/two").and_return(false) expect(File).to receive(:exist?).with("/one/two").and_return(false)
expect(FileUtils).to receive(:mkdir_p).with("/one/two") expect(FileUtils).to receive(:mkdir_p).with("/one/two")
cache.mkdir_p("/one/two") cache.mkdir_p("/one/two")
expect(cache.directories(false)).to eq ["/one/two"] expect(cache.directories(false)).to eq ["/one/two"]

View File

@ -138,27 +138,6 @@ module Rscons
end end
end end
describe "#depends" do
it "records the given dependencies in @user_deps" do
env = Environment.new
env.depends("foo", "bar", "baz")
expect(env.instance_variable_get(:@user_deps)).to eq({"foo" => ["bar", "baz"]})
end
it "records user dependencies only once" do
env = Environment.new
env.instance_variable_set(:@user_deps, {"foo" => ["bar"]})
env.depends("foo", "bar", "baz")
expect(env.instance_variable_get(:@user_deps)).to eq({"foo" => ["bar", "baz"]})
end
it "expands arguments for construction variable references" do
env = Environment.new
env["foo"] = "foo.exe"
env["bar"] = "bar.c"
env.depends("${foo}", "${bar}", "a.h")
expect(env.instance_variable_get(:@user_deps)).to eq({"foo.exe" => ["bar.c", "a.h"]})
end
end
describe "#shell" do describe "#shell" do
it "executes the given shell command and returns the results" do it "executes the given shell command and returns the results" do
env = Environment.new env = Environment.new

View File

@ -1,8 +1,12 @@
if ENV["dist_specs"] if ENV["rscons_dist_specs"]
require_relative "../test/rscons" require_relative "../test_run/rscons"
else else
require "simplecov" require "simplecov"
class MyFormatter
def format(*args)
end
end
SimpleCov.start do SimpleCov.start do
add_filter "/spec/" add_filter "/spec/"
add_filter "/.bundle/" add_filter "/.bundle/"
@ -11,14 +15,10 @@ else
else else
command_name "RSpec" command_name "RSpec"
end end
if ENV["dist_specs"] add_filter "test_run/rscons.rb"
add_filter "/bin/"
add_filter "/lib/"
else
add_filter "test/rscons.rb"
end
project_name "Rscons" project_name "Rscons"
merge_timeout 3600 merge_timeout 3600
formatter(MyFormatter)
end end
require "rscons" require "rscons"