diff --git a/build_tests/sh/sh.rb b/build_tests/sh/sh.rb new file mode 100644 index 0000000..90b2082 --- /dev/null +++ b/build_tests/sh/sh.rb @@ -0,0 +1,4 @@ +build do + sh "echo", "hi there" + sh(["echo 1 2"]) +end diff --git a/build_tests/sh/sh_fail.rb b/build_tests/sh/sh_fail.rb new file mode 100644 index 0000000..230c2dd --- /dev/null +++ b/build_tests/sh/sh_fail.rb @@ -0,0 +1,4 @@ +build do + sh "foobar42" + sh "echo", "continued" +end diff --git a/build_tests/sh/sh_fail_continue.rb b/build_tests/sh/sh_fail_continue.rb new file mode 100644 index 0000000..b5c25bd --- /dev/null +++ b/build_tests/sh/sh_fail_continue.rb @@ -0,0 +1,4 @@ +build do + sh "foobar42", continue: true + sh "echo", "continued" +end diff --git a/doc/user_guide.md b/doc/user_guide.md index 6c71b7b..64a14e3 100644 --- a/doc/user_guide.md +++ b/doc/user_guide.md @@ -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. +##> 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 A `configure` block is optional. @@ -536,25 +674,6 @@ would be "build/myproj". This keeps the intermediate generated build artifacts separate from the source 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 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 ``` -##> 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 ### Adding New Languages diff --git a/lib/rscons/script.rb b/lib/rscons/script.rb index a254e68..bdf1e84 100644 --- a/lib/rscons/script.rb +++ b/lib/rscons/script.rb @@ -6,6 +6,33 @@ module Rscons # Global DSL methods. 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] 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 [Array] @@ -80,6 +107,58 @@ module Rscons 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] + # 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] + # 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 # Top-level DSL available to the Rsconscript. @@ -108,33 +187,6 @@ module Rscons def configure(&block) @script.operations["configure"] = block 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] 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 # DSL available to the 'configure' block. diff --git a/lib/rscons/util.rb b/lib/rscons/util.rb index 120176c..89ec9bb 100644 --- a/lib/rscons/util.rb +++ b/lib/rscons/util.rb @@ -48,7 +48,7 @@ module Rscons # @return [String] # The string representation of the command. def command_to_s(command) - command.map { |c| c =~ /\s/ ? "'#{c}'" : c }.join(' ') + command.map { |c| c[" "] ? "'#{c.gsub("'", "'\\\\''")}'" : c }.join(" ") end # Determine the number of threads to use by default. diff --git a/rb/build_dist.rb b/rb/build_dist.rb index 070dc89..914800b 100755 --- a/rb/build_dist.rb +++ b/rb/build_dist.rb @@ -66,6 +66,7 @@ compressed_script = Zlib::Deflate.deflate(stripped.join) encoded_compressed_script = Base64.encode64(compressed_script).gsub("\n", "") hash = Digest::MD5.hexdigest(encoded_compressed_script) +FileUtils.rm_rf(DIST) FileUtils.mkdir_p(DIST) File.open("#{DIST}/#{PROG_NAME}", "wb", 0755) do |fh| fh.write(<