« Prev 4.6.2.3 Custom Builder Constructor | Table of Contents | Next » 4.6.2.5 Simple custom builders added with add_builder |
In order for a builder to run, 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:
class Rscons::Builders::Custom < Rscons::Builder def run(options) File.open(@target, "w") do |fh| fh.write("Target file created.") end true end end
If the builder 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 builder has completed successfully, the run
method should
return true
.
If the builder is not yet complete and is waiting on other steps, the run
method should return the return value from the Builder#wait_for
method.
See Custom Builder Parallelization.
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:
class Rscons::Builders::Custom < Rscons::Builder def run(options) print_run_message("Creating <target>#{@target}<reset> from Custom builder", nil) File.open(@target, "w") do |fh| fh.write("Target file created.") end true end end
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:
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>", nil) 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?
.
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.
Here is an example of using a Ruby thread to parallelize a builder:
class MyBuilder < Rscons::Builder def run(options) if @thread true else print_run_message("#{name} #{target}", nil) @thread = Thread.new do sleep 2 FileUtils.touch(@target) end wait_for(@thread) end end end env do |env| env.add_builder(MyBuilder) env.MyBuilder("foo") end
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.
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:
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):
module Rscons module Builders # A builder to execute an arbitrary command that will produce the given # target based on the given sources. # # Example: # env.Command("docs.html", "docs.md", # CMD => %w[pandoc -fmarkdown -thtml -o${_TARGET} ${_SOURCES}]) class Command < 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("${CMD}", @vars) cmd_desc = @vars["CMD_DESC"] || "Command" options = {} if @vars["CMD_STDOUT"] options[:stdout] = @env.expand_varref("${CMD_STDOUT}", @vars) end standard_command("#{cmd_desc} <target>#{@target}<reset>", command, options) end end end end end
Example (built-in Disassemble builder):
module Rscons module Builders # The Disassemble builder produces a disassembly listing of a source file. class Disassemble < Builder # Run the builder to produce a build target. def run(options) if @command finalize_command else @vars["_SOURCES"] = @sources command = @env.build_command("${DISASM_CMD}", @vars) standard_command("Disassembling <source>#{Util.short_format_paths(@sources)}<reset> => <target>#{target}<reset>", command, stdout: @target) end end end end end
« Prev 4.6.2.3 Custom Builder Constructor | Table of Contents | Next » 4.6.2.5 Simple custom builders added with add_builder |