user guide: fill in a lot of Adding Custom Builders section

This commit is contained in:
Josh Holtrop 2019-06-23 19:07:20 -04:00
parent 9bf0abbece
commit 8593c5e219

View File

@ -703,13 +703,253 @@ For example:
Rscons::DEFAULT_CONSTRUCTION_VARIABLES["CXXCMD"] = %w[${CXX} -c -o ${_TARGET} ${CXXDEPGEN} ${INCPREFIX}${CPPPATH} ${CPPFLAGS} ${CXXFLAGS} ${CCFLAGS} ${_SOURCES}]
```
### Adding New Builders
###> Adding Custom Builders
It is also possible to extend Rscons with new builders.
This is the most flexible method to extend Rscons.
Builders can execute a command line program, call another builder, or just use
plain Ruby code to produce an output file.
A builder is a class that inherits from the `Rscons::Builder` base class.
Rscons provides a `Rscons::Builders` namespacing module which contains the
built-in builder classes.
User-provided custom builder classes can also reside in the `Rscons::Builders`
namespacing module, but this is not required.
####> Adding a Custom Builder to an Environment
The user can add a builder class to an Environment with the `env.add_builder`
method.
For example:
```ruby
class Rscons::Builders::Mine < Rscons::Builder
end
build do
Environment.new do |env|
env.add_builder(Rscons::Builders::Mine)
end
end
```
Alternatively, the builder author can add the name of the custom builder to the
`Rscons::DEFAULT_BUILDERS` array and then Rscons will automatically add the
custom builder to every Environment.
This method only works if the custom builder class is contained within the
`Rscons::Builders` namespacing module.
For example:
```ruby
#SpecialBuilder.rb
class Rscons::Builders::Special < Rscons::Builder
end
Rscons::DEFAULT_BUILDERS << :Special
#Rsconscript
load "SpecialBuilder.rb"
build do
Environment.new do |env|
# A build target using the "Special" builder can be registered.
env.Special("target", "source")
end
end
```
####> Builder Name
By default, the builder name is taken from the last component of the class name.
For example, a class called `Rscons::Builders::Mine` would be usable in the
Rsconscript with `env.Mine()`.
A builder author can override the builder name by defining a class method
within the builder class called `name`.
For example, with the following builder definition:
```ruby
class Rscons::Builders::MySpecialBuilder < Rscons::Builder
def self.name
"Special"
end
end
```
This builder would be registered in the Rsconscript with `env.Special()`.
####> Custom Builder Constructor
It is optional for a custom builder to provide an `initialize` method.
If an `initialize` method is provided, it must call `super` to invoke the
base `Rscons::Builder` class's constructor.
A single Hash parameter is passed to the builder constructor.
This Hash contains many parameters describing how the build target was
registered by the user.
The base constructor will set several instance attributes within the builder:
* `@target` will contain the path to the build target
* `@sources` will contain the path(s) to the build source(s)
* `@cache` will contain a reference to the `Rscons::Cache` object used for
the build
* `@env` will contain a reference to the Environment object that registered
the build target using the builder
* `@vars` will contain any user-specified construction variable values that
should be used for the build operation (overriding any Environment-wide
construction variable values)
####> Custom Builder Operation
In order for a builder to perform a build operation, the builder class must
implement a the `Builder#run()` method.
Generally, the `run()` method will use the source file(s) to produce the target
file.
Here is an example of a trivial builder:
```ruby
class Rscons::Builders::Custom < Rscons::Builder
def run(options)
File.open(@target, "w") do |fh|
fh.write("Target file created.")
end
true
end
end
```
##### Return Value
If the build operation has completed and failed, the `run` method should return
`false`.
In this case, generally the command executed or the builder itself would be
expected to output something to `$stderr` indicating the reason for the build
failure.
If the build operation has completed successfully, the `run` method should
return `true`.
If the build operation is not yet complete and is waiting on other operations,
the `run` method should return the return value from the `Builder#wait_for`
method.
See ${#Custom Builder Parallelization}.
##### Printing Build Status
A builder should print a status line when it produces a build target.
The `Builder#print_run_message` method can be used to print the builder status
line.
This method supports a limited markup syntax to identify and color code the
build target and/or source(s).
Here is our Custom builder example extended to print its status:
```ruby
class Rscons::Builders::Custom < Rscons::Builder
def run(options)
print_run_message("Creating <target>#{@target}<reset> from Custom builder")
File.open(@target, "w") do |fh|
fh.write("Target file created.")
end
true
end
end
```
##### Custom Builder Cache Usage - Only Rebuild When Necessary
Whenever possible, a builder should keep track of information necessary to
know whether the target file(s) need to be rebuilt.
The `Rscons::Cache` object is the mechanism by which to keep track of this
information.
The Cache object provides two methods: `#up_to_date?` and `#register_build`
which can be used to check if a built file is still up-to-date, and to
register build information for a subsequent check.
Here is a Custom builder which combines its source files similar to what the
`cat` command would do:
```ruby
class Rscons::Builders::Custom < Rscons::Builder
def run(options)
unless @cache.up_to_date?(@target, nil, @sources, @env)
print_run_message("Combining <source>#{Util.short_format_paths(@sources)}<reset> => <target>#{@target}<reset>")
File.open(@target, "wb") do |fh|
@sources.each do |source|
fh.write(File.read(source, mode: "rb"))
end
end
@cache.register_build(@target, nil, @sources, @env)
end
true
end
end
```
This builder would rebuild the target file and print its run message if the
target file or any of the source file(s) were changed, but otherwise would be
silent and not re-combine the source files.
Note that generally the same arguments should be passed to
`@cache.register_build` and `@cache.up_to_date?`.
##### Custom Builder Parallelization
The Rscons scheduler can parallelize builders to take advantage of multiple
processor cores.
Taking advantage of this ability to parallelize requires the builder author to
author the builder in a particular way.
The `#run()` method of each builder is called from Rscons in the main program
thread.
However, the builder may execute a subcommand, spawn a thread, or register
other builders to execute as a part of doing its job.
In any of these cases, the builder's `run` method should make use of
`Builder#wait_for` to "sleep" until one of the items being waited for has
completed.
###### Using a Ruby Thread to Parallelize a Build Operation
Here is an example of using a Ruby thread to parallelize a build operation:
```ruby
${include build_tests/custom_builder/wait_for_thread.rb}
```
It is up to the author of the thread logic to only perform actions that are
thread-safe.
It is not safe to call other Rscons methods, for example, registering other
builders or using the Cache, from a thread other than the one that calls the
`#run()` method.
###### Executing a Subcommand from a Custom Builder
It is a very common case that a builder will execute a subcommand which
produces the build target.
This is how most of the built-in Rscons builders execute.
A low-level way to handle this is for the builder to construct an instance of
the `Rscons::Command` class and then `wait_for` the Command object.
However, this is a common enough case that Rscons provides a few
convenience methods to handle this:
* `Rscons::Builder#register_command`
* `Rscons::Builder#standard_command`
* `Rscons::Builder#finalize_command`
The `register_command` helper method can be used to create a Command object
and wait for it to complete.
The `standard_command` helper does the same thing as `register_command` but
additionally checks the `@cache` for the target being up to date.
The `finalize_command` helper can be used in conjunction with either of the
previous helper methods.
The built-in Rscons builders `Command` and `Disassemble` show examples of how
to use the `standard_command` and `finalize_command` helper methods.
Example (built-in Command builder):
```ruby
${include lib/rscons/builders/command.rb}
```
Example (built-in Disassemble builder):
```ruby
${include lib/rscons/builders/disassemble.rb}
```
#> Reference
## Default Construction Variables