user guide: fill in a lot of Adding Custom Builders section
This commit is contained in:
parent
9bf0abbece
commit
8593c5e219
@ -703,13 +703,253 @@ For example:
|
|||||||
Rscons::DEFAULT_CONSTRUCTION_VARIABLES["CXXCMD"] = %w[${CXX} -c -o ${_TARGET} ${CXXDEPGEN} ${INCPREFIX}${CPPPATH} ${CPPFLAGS} ${CXXFLAGS} ${CCFLAGS} ${_SOURCES}]
|
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.
|
It is also possible to extend Rscons with new builders.
|
||||||
This is the most flexible method to extend Rscons.
|
This is the most flexible method to extend Rscons.
|
||||||
Builders can execute a command line program, call another builder, or just use
|
Builders can execute a command line program, call another builder, or just use
|
||||||
plain Ruby code to produce an output file.
|
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
|
#> Reference
|
||||||
|
|
||||||
## Default Construction Variables
|
## Default Construction Variables
|
||||||
|
Loading…
x
Reference in New Issue
Block a user