Add 'sh' script DSL method - close #142
This commit is contained in:
parent
610b8f1266
commit
b5d5fe7a7b
4
build_tests/sh/sh.rb
Normal file
4
build_tests/sh/sh.rb
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
build do
|
||||||
|
sh "echo", "hi there"
|
||||||
|
sh(["echo 1 2"])
|
||||||
|
end
|
4
build_tests/sh/sh_fail.rb
Normal file
4
build_tests/sh/sh_fail.rb
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
build do
|
||||||
|
sh "foobar42"
|
||||||
|
sh "echo", "continued"
|
||||||
|
end
|
4
build_tests/sh/sh_fail_continue.rb
Normal file
4
build_tests/sh/sh_fail_continue.rb
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
build do
|
||||||
|
sh "foobar42", continue: true
|
||||||
|
sh "echo", "continued"
|
||||||
|
end
|
@ -221,6 +221,144 @@ called `myprog.exe` which is to be built from all C source files found
|
|||||||
|
|
||||||
The `Rsconscript` file is a Ruby script.
|
The `Rsconscript` file is a Ruby script.
|
||||||
|
|
||||||
|
##> Build Script Methods
|
||||||
|
|
||||||
|
`rscons` provides several methods that a build script can use.
|
||||||
|
|
||||||
|
* `glob` (see ${#Finding Files: The glob Method})
|
||||||
|
* `path_append` (see ${#PATH Management})
|
||||||
|
* `path_components` (see ${#PATH Management})
|
||||||
|
* `path_prepend` (see ${#PATH Management})
|
||||||
|
* `path_set` (see ${#PATH Management})
|
||||||
|
* `rscons` (see ${#Using Subsidiary Build Scripts: The rscons Method})
|
||||||
|
* `sh` (see (${#Executing Commands: The sh Method})
|
||||||
|
|
||||||
|
###> Finding Files: The glob Method
|
||||||
|
|
||||||
|
The [`glob`](../yard/Rscons/Script/GlobalDsl.html#glob-instance_method) method can be
|
||||||
|
used to find files matching the patterns specified.
|
||||||
|
It supports a syntax similar to the Ruby [Dir.glob method](https://ruby-doc.org/core-2.5.1/Dir.html#method-c-glob) but operates more deterministically.
|
||||||
|
|
||||||
|
Example use:
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
build do
|
||||||
|
Environment.new do |env|
|
||||||
|
env.Program("mytests", glob("src/**/*.cc", "test/**/*.cc"))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
```
|
||||||
|
|
||||||
|
This example would build the `mytests` executable from all `.cc` source files
|
||||||
|
found recursively under the `src` or `test` directory.
|
||||||
|
|
||||||
|
###> PATH Management
|
||||||
|
|
||||||
|
`rscons` provides methods for management of the `PATH` environment variable.
|
||||||
|
|
||||||
|
The
|
||||||
|
[`path_append`](../yard/Rscons/Script/GlobalDsl.html#path_append-instance_method)
|
||||||
|
and
|
||||||
|
[`path_prepend`](../yard/Rscons/Script/GlobalDsl.html#path_prepend-instance_method)
|
||||||
|
methods can be used to append or prepend a path to the `PATH` environment
|
||||||
|
variable.
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
path_prepend "i686-elf-gcc/bin"
|
||||||
|
```
|
||||||
|
|
||||||
|
The
|
||||||
|
[`path_set`](../yard/Rscons/Script/GlobalDsl.html#path_set-instance_method)
|
||||||
|
method sets the `PATH` environment variable to the given Array or String.
|
||||||
|
|
||||||
|
The
|
||||||
|
[`path_components`](../yard/Rscons/Script/GlobalDsl.html#path_components-instance_method)
|
||||||
|
method returns an Array of the components in the `PATH`
|
||||||
|
environment variable.
|
||||||
|
|
||||||
|
###> Using Subsidiary Build Scripts: The rscons Method
|
||||||
|
|
||||||
|
The
|
||||||
|
[`rscons`](../yard/Rscons/Script/GlobalDsl.html#rscons-instance_method)
|
||||||
|
build script method can be used to invoke an rscons subprocess to
|
||||||
|
perform an operation using a subsidiary rscons build script.
|
||||||
|
This can be used, for example, when a subproject is imported and a top-level
|
||||||
|
`configure` or `build` operation should also perform the same operation in the
|
||||||
|
subproject directory.
|
||||||
|
|
||||||
|
The first argument to the `rscons` method specifies either a directory name, or
|
||||||
|
the path to the subsidiary Rsconscript file to execute.
|
||||||
|
Any additional arguments are passed to `rscons` when it executes the subsidiary
|
||||||
|
script.
|
||||||
|
`rscons` will change working directories to the directory containing the
|
||||||
|
subsidiary script when executing it.
|
||||||
|
|
||||||
|
For example:
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
configure do
|
||||||
|
rscons "subproject", "configure"
|
||||||
|
end
|
||||||
|
|
||||||
|
build do
|
||||||
|
rscons "subproject/Rsconscript", "build"
|
||||||
|
end
|
||||||
|
```
|
||||||
|
|
||||||
|
It is also perfectly valid to perform a different operation in the subsidiary
|
||||||
|
script from the one being performed in the top-level script.
|
||||||
|
For example, in a project that requires a particular cross compiler, the
|
||||||
|
top-level `configure` script could build the necessary cross compiler using a
|
||||||
|
subsidiary build script.
|
||||||
|
This could look something like:
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
configure do
|
||||||
|
rscons "cross/Rsconscript"
|
||||||
|
check_c_compiler "i686-elf-gcc"
|
||||||
|
end
|
||||||
|
```
|
||||||
|
|
||||||
|
This would build, and if necessary first configure, using the cross/Rsconscript
|
||||||
|
subsidiary build script.
|
||||||
|
Subsidiary build scripts are executed from within the directory containing the
|
||||||
|
build script.
|
||||||
|
|
||||||
|
###> Executing Commands: The sh Method
|
||||||
|
|
||||||
|
The
|
||||||
|
[`sh`](../yard/Rscons/Script/GlobalDsl.html#sh-instance_method)
|
||||||
|
build script method can be used to directly execute commands.
|
||||||
|
The `sh` method accepts either a single String argument or an Array of Strings.
|
||||||
|
When an Array is given, if the array length is greater than 1, then the command
|
||||||
|
will not be executed and interpreted by the system shell.
|
||||||
|
Otherwise, it will be executed and interpreted by the system shell.
|
||||||
|
|
||||||
|
For example:
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
build do
|
||||||
|
# Run "make" in imported "subcomponent" directory.
|
||||||
|
sh "cd subcomponent; make"
|
||||||
|
# Move a file around.
|
||||||
|
sh "mv", "subcomponent/file with spaces.txt", "new_name.txt"
|
||||||
|
end
|
||||||
|
```
|
||||||
|
|
||||||
|
If the command fails, rscons will normally print the error and terminate
|
||||||
|
execution.
|
||||||
|
If the `:continue` option is set, then rscons will not terminate execution.
|
||||||
|
For example:
|
||||||
|
|
||||||
|
```ruby
|
||||||
|
build do
|
||||||
|
# This command will fail and a message will be printed.
|
||||||
|
sh "false", continue: true
|
||||||
|
# However, due to the :continue option being set, execution will continue.
|
||||||
|
sh "echo hi"
|
||||||
|
end
|
||||||
|
```
|
||||||
|
|
||||||
##> Configuration Operations
|
##> Configuration Operations
|
||||||
|
|
||||||
A `configure` block is optional.
|
A `configure` block is optional.
|
||||||
@ -536,25 +674,6 @@ would be "build/myproj".
|
|||||||
This keeps the intermediate generated build artifacts separate from the source
|
This keeps the intermediate generated build artifacts separate from the source
|
||||||
files.
|
files.
|
||||||
|
|
||||||
###> Specifying Source Files: The glob Method
|
|
||||||
|
|
||||||
The [`glob`](../yard/Rscons/Script/Dsl.html#glob-instance_method) method can be
|
|
||||||
used to find files matching the patterns specified.
|
|
||||||
It supports a syntax similar to the Ruby [Dir.glob method](https://ruby-doc.org/core-2.5.1/Dir.html#method-c-glob) but operates more deterministically.
|
|
||||||
|
|
||||||
Example use:
|
|
||||||
|
|
||||||
```ruby
|
|
||||||
build do
|
|
||||||
Environment.new do |env|
|
|
||||||
env.Program("mytests", glob("src/**/*.cc", "test/**/*.cc"))
|
|
||||||
end
|
|
||||||
end
|
|
||||||
```
|
|
||||||
|
|
||||||
This example would build the `mytests` executable from all `.cc` source files
|
|
||||||
found recursively under the `src` or `test` directory.
|
|
||||||
|
|
||||||
###> Construction Variables
|
###> Construction Variables
|
||||||
|
|
||||||
Construction variables are values assigned to keys within an Environment.
|
Construction variables are values assigned to keys within an Environment.
|
||||||
@ -934,70 +1053,6 @@ In other words, build targets are not parallelized across a barrier.
|
|||||||
env.barrier
|
env.barrier
|
||||||
```
|
```
|
||||||
|
|
||||||
##> Global Build Script Functionality
|
|
||||||
|
|
||||||
###> Using Subsidiary Build Scripts
|
|
||||||
|
|
||||||
The `rscons` build script method can be used to invoke an rscons subprocess to
|
|
||||||
perform an operation using a subsidiary rscons build script.
|
|
||||||
This can be used, for example, when a subproject is imported and a top-level
|
|
||||||
`configure` or `build` operation should also perform the same operation in the
|
|
||||||
subproject directory.
|
|
||||||
|
|
||||||
The first argument to the `rscons` method specifies either a directory name, or
|
|
||||||
the path to the subsidiary Rsconscript file to execute.
|
|
||||||
Any additional arguments are passed to `rscons` when it executes the subsidiary
|
|
||||||
script.
|
|
||||||
`rscons` will change working directories to the directory containing the
|
|
||||||
subsidiary script when executing it.
|
|
||||||
|
|
||||||
For example:
|
|
||||||
|
|
||||||
```ruby
|
|
||||||
configure do
|
|
||||||
rscons "subproject", "configure"
|
|
||||||
end
|
|
||||||
|
|
||||||
build do
|
|
||||||
rscons "subproject/Rsconscript", "build"
|
|
||||||
end
|
|
||||||
```
|
|
||||||
|
|
||||||
It is also perfectly valid to perform a different operation in the subsidiary
|
|
||||||
script from the one being performed in the top-level script.
|
|
||||||
For example, in a project that requires a particular cross compiler, the
|
|
||||||
top-level `configure` script could build the necessary cross compiler using a
|
|
||||||
subsidiary build script.
|
|
||||||
This could look something like:
|
|
||||||
|
|
||||||
```ruby
|
|
||||||
configure do
|
|
||||||
rscons "cross/Rsconscript"
|
|
||||||
check_c_compiler "i686-elf-gcc"
|
|
||||||
end
|
|
||||||
```
|
|
||||||
|
|
||||||
This would build, and if necessary first configure, using the cross/Rsconscript
|
|
||||||
subsidiary build script.
|
|
||||||
Subsidiary build scripts are executed from within the directory containing the
|
|
||||||
build script.
|
|
||||||
|
|
||||||
###> PATH Management
|
|
||||||
|
|
||||||
`rscons` provides methods for management of the `PATH` environment variable.
|
|
||||||
|
|
||||||
The `path_append` and `path_prepend` methods can be used to append or prepend
|
|
||||||
a path to the `PATH` environment variable.
|
|
||||||
|
|
||||||
```ruby
|
|
||||||
path_prepend "i686-elf-gcc/bin"
|
|
||||||
```
|
|
||||||
|
|
||||||
The `path_set` method sets the `PATH` environment variable to the given
|
|
||||||
Array or String.
|
|
||||||
The `path_components` method returns an Array of the components in the `PATH`
|
|
||||||
environment variable.
|
|
||||||
|
|
||||||
##> Extending Rscons
|
##> Extending Rscons
|
||||||
|
|
||||||
### Adding New Languages
|
### Adding New Languages
|
||||||
|
@ -6,6 +6,33 @@ module Rscons
|
|||||||
# Global DSL methods.
|
# Global DSL methods.
|
||||||
class GlobalDsl
|
class GlobalDsl
|
||||||
|
|
||||||
|
# Return a list of paths matching the specified pattern(s).
|
||||||
|
#
|
||||||
|
# A pattern can contain a "/**" component to recurse through directories.
|
||||||
|
# If the pattern ends with "/**" then only the recursive list of
|
||||||
|
# directories will be returned.
|
||||||
|
#
|
||||||
|
# Examples:
|
||||||
|
# - "src/**": return all directories under "src", recursively (including
|
||||||
|
# "src" itself).
|
||||||
|
# - "src/**/*": return all files and directories recursively under the src
|
||||||
|
# directory.
|
||||||
|
# - "src/**/*.c": return all .c files recursively under the src directory.
|
||||||
|
# - "dir/*/": return all directories in dir, but no files.
|
||||||
|
#
|
||||||
|
# @return [Array<String>] Paths matching the specified pattern(s).
|
||||||
|
def glob(*patterns)
|
||||||
|
require "pathname"
|
||||||
|
patterns.reduce([]) do |result, pattern|
|
||||||
|
if pattern.end_with?("/**")
|
||||||
|
pattern += "/"
|
||||||
|
end
|
||||||
|
result += Dir.glob(pattern).map do |path|
|
||||||
|
Pathname.new(path.gsub("\\", "/")).cleanpath.to_s
|
||||||
|
end
|
||||||
|
end.sort
|
||||||
|
end
|
||||||
|
|
||||||
# Return path components from the PATH variable.
|
# Return path components from the PATH variable.
|
||||||
#
|
#
|
||||||
# @return [Array<String>]
|
# @return [Array<String>]
|
||||||
@ -80,6 +107,58 @@ module Rscons
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Execute a shell command, exiting on failure.
|
||||||
|
# The behavior to exit on failure is suppressed if the +:continue+
|
||||||
|
# option is given.
|
||||||
|
#
|
||||||
|
# @overload sh(command, options = {})
|
||||||
|
# @param command [String, Array<String>]
|
||||||
|
# Command to execute. The command is executed and interpreted by the
|
||||||
|
# system shell when given as a single string. It is not passed to the
|
||||||
|
# system shell if the array size is greater than 1.
|
||||||
|
# @param options [Hash]
|
||||||
|
# Options.
|
||||||
|
# @option options [Boolean] :continue
|
||||||
|
# If set to +true+, rscons will continue executing afterward, even if
|
||||||
|
# the command fails.
|
||||||
|
#
|
||||||
|
# @overload sh(*command, options = {})
|
||||||
|
# @param command [String, Array<String>]
|
||||||
|
# Command to execute. The command is executed and interpreted by the
|
||||||
|
# system shell when given as a single string. It is not passed to the
|
||||||
|
# system shell if the array size is greater than 1.
|
||||||
|
# @param options [Hash]
|
||||||
|
# Options.
|
||||||
|
# @option options [Boolean] :continue
|
||||||
|
# If set to +true+, rscons will continue executing afterward, even if
|
||||||
|
# the command fails.
|
||||||
|
def sh(*command)
|
||||||
|
options = {}
|
||||||
|
if command.last.is_a?(Hash)
|
||||||
|
options = command.slice!(-1)
|
||||||
|
end
|
||||||
|
if command.size == 1 && command[0].is_a?(Array)
|
||||||
|
command = command[0]
|
||||||
|
end
|
||||||
|
if Rscons.application.verbose
|
||||||
|
if command.size > 1
|
||||||
|
puts Util.command_to_s(command)
|
||||||
|
else
|
||||||
|
puts command[0]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
begin
|
||||||
|
system(*command, exception: true)
|
||||||
|
rescue StandardError => e
|
||||||
|
message = "#{e.backtrace[2]}: #{e.message}"
|
||||||
|
if options[:continue]
|
||||||
|
Ansi.write($stderr, :red, message, :reset, "\n")
|
||||||
|
else
|
||||||
|
raise RsconsError.new(message)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
# Top-level DSL available to the Rsconscript.
|
# Top-level DSL available to the Rsconscript.
|
||||||
@ -108,33 +187,6 @@ module Rscons
|
|||||||
def configure(&block)
|
def configure(&block)
|
||||||
@script.operations["configure"] = block
|
@script.operations["configure"] = block
|
||||||
end
|
end
|
||||||
|
|
||||||
# Return a list of paths matching the specified pattern(s).
|
|
||||||
#
|
|
||||||
# A pattern can contain a "/**" component to recurse through directories.
|
|
||||||
# If the pattern ends with "/**" then only the recursive list of
|
|
||||||
# directories will be returned.
|
|
||||||
#
|
|
||||||
# Examples:
|
|
||||||
# - "src/**": return all directories under "src", recursively (including
|
|
||||||
# "src" itself).
|
|
||||||
# - "src/**/*": return all files and directories recursively under the src
|
|
||||||
# directory.
|
|
||||||
# - "src/**/*.c": return all .c files recursively under the src directory.
|
|
||||||
# - "dir/*/": return all directories in dir, but no files.
|
|
||||||
#
|
|
||||||
# @return [Array<String>] Paths matching the specified pattern(s).
|
|
||||||
def glob(*patterns)
|
|
||||||
require "pathname"
|
|
||||||
patterns.reduce([]) do |result, pattern|
|
|
||||||
if pattern.end_with?("/**")
|
|
||||||
pattern += "/"
|
|
||||||
end
|
|
||||||
result += Dir.glob(pattern).map do |path|
|
|
||||||
Pathname.new(path.gsub("\\", "/")).cleanpath.to_s
|
|
||||||
end
|
|
||||||
end.sort
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
# DSL available to the 'configure' block.
|
# DSL available to the 'configure' block.
|
||||||
|
@ -48,7 +48,7 @@ module Rscons
|
|||||||
# @return [String]
|
# @return [String]
|
||||||
# The string representation of the command.
|
# The string representation of the command.
|
||||||
def command_to_s(command)
|
def command_to_s(command)
|
||||||
command.map { |c| c =~ /\s/ ? "'#{c}'" : c }.join(' ')
|
command.map { |c| c[" "] ? "'#{c.gsub("'", "'\\\\''")}'" : c }.join(" ")
|
||||||
end
|
end
|
||||||
|
|
||||||
# Determine the number of threads to use by default.
|
# Determine the number of threads to use by default.
|
||||||
|
@ -66,6 +66,7 @@ compressed_script = Zlib::Deflate.deflate(stripped.join)
|
|||||||
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)
|
hash = Digest::MD5.hexdigest(encoded_compressed_script)
|
||||||
|
|
||||||
|
FileUtils.rm_rf(DIST)
|
||||||
FileUtils.mkdir_p(DIST)
|
FileUtils.mkdir_p(DIST)
|
||||||
File.open("#{DIST}/#{PROG_NAME}", "wb", 0755) do |fh|
|
File.open("#{DIST}/#{PROG_NAME}", "wb", 0755) do |fh|
|
||||||
fh.write(<<EOF)
|
fh.write(<<EOF)
|
||||||
|
@ -2802,4 +2802,46 @@ EOF
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
context "sh method" do
|
||||||
|
it "executes the command given" do
|
||||||
|
test_dir "sh"
|
||||||
|
result = run_rscons(rsconscript: "sh.rb")
|
||||||
|
expect(result.stderr).to eq ""
|
||||||
|
expect(result.status).to eq 0
|
||||||
|
verify_lines(lines(result.stdout), [
|
||||||
|
"hi there",
|
||||||
|
"1 2",
|
||||||
|
])
|
||||||
|
end
|
||||||
|
|
||||||
|
it "prints the command when executing verbosely" do
|
||||||
|
test_dir "sh"
|
||||||
|
result = run_rscons(rsconscript: "sh.rb", rscons_args: %w[-v])
|
||||||
|
expect(result.stderr).to eq ""
|
||||||
|
expect(result.status).to eq 0
|
||||||
|
verify_lines(lines(result.stdout), [
|
||||||
|
%r{echo 'hi there'},
|
||||||
|
"hi there",
|
||||||
|
%r{echo 1 2},
|
||||||
|
"1 2",
|
||||||
|
])
|
||||||
|
end
|
||||||
|
|
||||||
|
it "terminates execution on failure" do
|
||||||
|
test_dir "sh"
|
||||||
|
result = run_rscons(rsconscript: "sh_fail.rb")
|
||||||
|
expect(result.stderr).to match /sh_fail\.rb:2:.*foobar42/
|
||||||
|
expect(result.status).to_not eq 0
|
||||||
|
expect(result.stdout).to_not match /continued/
|
||||||
|
end
|
||||||
|
|
||||||
|
it "continues execution on failure when :continue option is set" do
|
||||||
|
test_dir "sh"
|
||||||
|
result = run_rscons(rsconscript: "sh_fail_continue.rb")
|
||||||
|
expect(result.stderr).to match /sh_fail_continue\.rb:2:.*foobar42/
|
||||||
|
expect(result.status).to eq 0
|
||||||
|
expect(result.stdout).to match /continued/
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
Loading…
x
Reference in New Issue
Block a user