diff --git a/README.md b/README.md index 12f8184..fbaf7d3 100644 --- a/README.md +++ b/README.md @@ -268,8 +268,8 @@ env.Preprocess(target, source) env.Preprocess("module-preprocessed.cc", "module.cc") ``` -The Preprocess builder invokes either ${CC} or ${CXX} (depending on if the -source contains an extension in ${CXXSUFFIX} or not) and writes the +The Preprocess builder invokes either `${CC}` or `${CXX}` (depending on if the +source contains an extension in `${CXXSUFFIX}` or not) and writes the preprocessed output to the target file. #### Program diff --git a/lib/rscons.rb b/lib/rscons.rb index 80a5090..de324d2 100644 --- a/lib/rscons.rb +++ b/lib/rscons.rb @@ -15,6 +15,8 @@ require_relative "rscons/builders/program" # Namespace module for rscons classes module Rscons + # Names of the default builders which will be added to all newly created + # {Environment} objects. DEFAULT_BUILDERS = [ :CFile, :Disassemble, @@ -24,9 +26,10 @@ module Rscons :Program, ] + # Class to represent a fatal error while building a target. class BuildError < RuntimeError; end - # Remove all generated files + # Remove all generated files. def self.clean cache = Cache.instance # remove all built files @@ -43,20 +46,27 @@ module Rscons cache.clear end - # Return whether the given path is an absolute filesystem path or not - # @param path [String] the path to examine + # Return whether the given path is an absolute filesystem path. + # + # @param path [String] the path to examine. + # + # @return [Boolean] Whether the given path is an absolute filesystem path. def self.absolute_path?(path) path =~ %r{^(/|\w:[\\/])} end # Return a new path by changing the suffix in path to suffix. - # @param path [String] the path to alter - # @param suffix [String] the new filename suffix + # + # @param path [String] The path to alter. + # @param suffix [String] The new filename suffix, e.g. ".exe". + # + # @return [String] New path. def self.set_suffix(path, suffix) path.sub(/\.[^.]*$/, suffix) end # Return the system shell and arguments for executing a shell command. + # # @return [Array] The shell and flag. def self.get_system_shell @@shell ||= diff --git a/lib/rscons/build_target.rb b/lib/rscons/build_target.rb index 33fb060..23ddcee 100644 --- a/lib/rscons/build_target.rb +++ b/lib/rscons/build_target.rb @@ -18,6 +18,8 @@ module Rscons # Manually record a given target as depending on the specified files. # # @param user_deps [Array] Dependency files. + # + # @return [void] def depends(*user_deps) @env.depends(@target, *user_deps) end diff --git a/lib/rscons/builder.rb b/lib/rscons/builder.rb index 3af964a..241189f 100644 --- a/lib/rscons/builder.rb +++ b/lib/rscons/builder.rb @@ -7,14 +7,17 @@ module Rscons # Class to hold an object that knows how to build a certain type of file. class Builder # Return the name of the builder. + # # If not overridden this defaults to the last component of the class name. def name self.class.name.split(":").last end - # Return a set of default variable values for the Environment to use - # unless the user overrides any. + # Return a set of default construction variables for the builder. + # # @param env [Environment] The Environment. + # + # @return [Hash] Default construction variables. def default_variables(env) {} end @@ -39,16 +42,33 @@ module Rscons # Return whether this builder object is capable of producing a given target # file name from a given source file name. + # # @param target [String] The target file name. # @param source [String, Array] The source file name(s). # @param env [Environment] The Environment. + # + # @return [Boolean] + # Whether this builder object is capable of producing a given target + # file name from a given source file name. def produces?(target, source, env) false end # Check if the cache is up to date for the target and if not execute the # build command. - # Return the name of the target or false on failure. + # + # @param short_cmd_string [String] + # Short description of build action to be printed when env.echo == + # :short. + # @param target [String] Name of the target file. + # @param command [Array] + # The command to execute to build the target. + # @param sources [Array] Source file name(s). + # @param env [Environment] The Environment executing the builder. + # @param cache [Cache] The Cache object. + # + # @return [String,false] + # The name of the target on success or false on failure. def standard_build(short_cmd_string, target, command, sources, env, cache) unless cache.up_to_date?(target, command, sources, env) cache.mkdir_p(File.dirname(target)) diff --git a/lib/rscons/builders/cfile.rb b/lib/rscons/builders/cfile.rb index fe523cf..7534628 100644 --- a/lib/rscons/builders/cfile.rb +++ b/lib/rscons/builders/cfile.rb @@ -7,6 +7,11 @@ module Rscons # env.CFile("parser.tab.cc", "parser.yy") # env.CFile("lex.yy.cc", "parser.ll") class CFile < Builder + # Return default construction variables for the builder. + # + # @param env [Environment] The Environment using the builder. + # + # @return [Hash] Default construction variables for the builder. def default_variables(env) { "YACC" => "bison", @@ -18,6 +23,16 @@ module Rscons } end + # Run the builder to produce a build target. + # + # @param target [String] Target file name. + # @param sources [Array] Source file name(s). + # @param cache [Cache] The Cache object. + # @param env [Environment] The Environment executing the builder. + # @param vars [Hash,VarSet] Extra construction variables. + # + # @return [String,false] + # Name of the target file on success or false on failure. def run(target, sources, cache, env, vars) vars = vars.merge({ "_TARGET" => target, diff --git a/lib/rscons/builders/disassemble.rb b/lib/rscons/builders/disassemble.rb index 17a0d66..b5ba813 100644 --- a/lib/rscons/builders/disassemble.rb +++ b/lib/rscons/builders/disassemble.rb @@ -2,6 +2,11 @@ module Rscons module Builders # The Disassemble builder produces a disassembly listing of a source file. class Disassemble < Builder + # Return default construction variables for the builder. + # + # @param env [Environment] The Environment using the builder. + # + # @return [Hash] Default construction variables for the builder. def default_variables(env) { "OBJDUMP" => "objdump", @@ -10,6 +15,16 @@ module Rscons } end + # Run the builder to produce a build target. + # + # @param target [String] Target file name. + # @param sources [Array] Source file name(s). + # @param cache [Cache] The Cache object. + # @param env [Environment] The Environment executing the builder. + # @param vars [Hash,VarSet] Extra construction variables. + # + # @return [String,false] + # Name of the target file on success or false on failure. def run(target, sources, cache, env, vars) vars = vars.merge("_SOURCES" => sources) command = env.build_command("${DISASM_CMD}", vars) diff --git a/lib/rscons/builders/library.rb b/lib/rscons/builders/library.rb index fc3cfdc..4545325 100644 --- a/lib/rscons/builders/library.rb +++ b/lib/rscons/builders/library.rb @@ -2,6 +2,11 @@ module Rscons module Builders # A default Rscons builder that produces a static library archive. class Library < Builder + # Return default construction variables for the builder. + # + # @param env [Environment] The Environment using the builder. + # + # @return [Hash] Default construction variables for the builder. def default_variables(env) { 'AR' => 'ar', @@ -11,6 +16,16 @@ module Rscons } end + # Run the builder to produce a build target. + # + # @param target [String] Target file name. + # @param sources [Array] Source file name(s). + # @param cache [Cache] The Cache object. + # @param env [Environment] The Environment executing the builder. + # @param vars [Hash,VarSet] Extra construction variables. + # + # @return [String,false] + # Name of the target file on success or false on failure. def run(target, sources, cache, env, vars) # build sources to linkable objects objects = env.build_sources(sources, env.expand_varref(["${OBJSUFFIX}", "${LIBSUFFIX}"], vars).flatten, cache, vars) diff --git a/lib/rscons/builders/object.rb b/lib/rscons/builders/object.rb index dd88758..848f284 100644 --- a/lib/rscons/builders/object.rb +++ b/lib/rscons/builders/object.rb @@ -3,6 +3,7 @@ module Rscons # A default Rscons builder which knows how to produce an object file from # various types of source files. class Object < Builder + # Mapping of known sources from which to build object files. KNOWN_SUFFIXES = { "AS" => "ASSUFFIX", "CC" => "CSUFFIX", @@ -10,6 +11,11 @@ module Rscons "DC" => "DSUFFIX", } + # Return default construction variables for the builder. + # + # @param env [Environment] The Environment using the builder. + # + # @return [Hash] Default construction variables for the builder. def default_variables(env) { 'OBJSUFFIX' => '.o', @@ -51,12 +57,32 @@ module Rscons } end + # Return whether this builder object is capable of producing a given target + # file name from a given source file name. + # + # @param target [String] The target file name. + # @param source [String, Array] The source file name(s). + # @param env [Environment] The Environment. + # + # @return [Boolean] + # Whether this builder object is capable of producing a given target + # file name from a given source file name. def produces?(target, source, env) target.end_with?(*env['OBJSUFFIX']) and KNOWN_SUFFIXES.find do |compiler, suffix_var| source.end_with?(*env[suffix_var]) end end + # Run the builder to produce a build target. + # + # @param target [String] Target file name. + # @param sources [Array] Source file name(s). + # @param cache [Cache] The Cache object. + # @param env [Environment] The Environment executing the builder. + # @param vars [Hash,VarSet] Extra construction variables. + # + # @return [String,false] + # Name of the target file on success or false on failure. def run(target, sources, cache, env, vars) vars = vars.merge({ '_TARGET' => target, diff --git a/lib/rscons/builders/preprocess.rb b/lib/rscons/builders/preprocess.rb index ad35d55..39a4065 100644 --- a/lib/rscons/builders/preprocess.rb +++ b/lib/rscons/builders/preprocess.rb @@ -2,12 +2,27 @@ module Rscons module Builders # The Preprocess builder invokes the C preprocessor class Preprocess < Builder + # Return default construction variables for the builder. + # + # @param env [Environment] The Environment using the builder. + # + # @return [Hash] Default construction variables for the builder. def default_variables(env) { "CPP_CMD" => ["${_PREPROCESS_CC}", "-E", "-o", "${_TARGET}", "-I${CPPPATH}", "${CPPFLAGS}", "${CFLAGS}", "${_SOURCES}"], } end + # Run the builder to produce a build target. + # + # @param target [String] Target file name. + # @param sources [Array] Source file name(s). + # @param cache [Cache] The Cache object. + # @param env [Environment] The Environment executing the builder. + # @param vars [Hash,VarSet] Extra construction variables. + # + # @return [String,false] + # Name of the target file on success or false on failure. def run(target, sources, cache, env, vars) pp_cc = if sources.find {|s| s.end_with?(*env.expand_varref("${CXXSUFFIX}", vars))} "${CXX}" diff --git a/lib/rscons/builders/program.rb b/lib/rscons/builders/program.rb index 89fab29..4785117 100644 --- a/lib/rscons/builders/program.rb +++ b/lib/rscons/builders/program.rb @@ -3,6 +3,11 @@ module Rscons # A default Rscons builder that knows how to link object files into an # executable program. class Program < Builder + # Return default construction variables for the builder. + # + # @param env [Environment] The Environment using the builder. + # + # @return [Hash] Default construction variables for the builder. def default_variables(env) { 'OBJSUFFIX' => '.o', @@ -18,6 +23,20 @@ module Rscons } end + # Create a BuildTarget object for this build target. + # + # The build target filename is given a ".exe" suffix if Rscons is + # executing on a Windows platform and no other suffix is given. + # + # @param options [Hash] Options to create the BuildTarget with. + # @option options [Environment] :env + # The Environment. + # @option options [String] :target + # The user-supplied target name. + # @option options [Array] :sources + # The user-supplied source file name(s). + # + # @return [BuildTarget] def create_build_target(options) my_options = options.dup unless my_options[:target] =~ /\./ @@ -26,6 +45,16 @@ module Rscons super(my_options) end + # Run the builder to produce a build target. + # + # @param target [String] Target file name. + # @param sources [Array] Source file name(s). + # @param cache [Cache] The Cache object. + # @param env [Environment] The Environment executing the builder. + # @param vars [Hash,VarSet] Extra construction variables. + # + # @return [String,false] + # Name of the target file on success or false on failure. def run(target, sources, cache, env, vars) # build sources to linkable objects objects = env.build_sources(sources, env.expand_varref(["${OBJSUFFIX}", "${LIBSUFFIX}"], vars).flatten, cache, vars) diff --git a/lib/rscons/cache.rb b/lib/rscons/cache.rb index 28dcfa8..6079010 100644 --- a/lib/rscons/cache.rb +++ b/lib/rscons/cache.rb @@ -84,7 +84,8 @@ module Rscons @dirty = false end - # Check if target(s) are up to date + # Check if target(s) are up to date. + # # @param targets [String, Array] The name(s) of the target file(s). # @param command [String, Array] The command used to build the target. # @param deps [Array] List of the target's dependency files. @@ -93,6 +94,7 @@ module Rscons # @option options [Boolean] :strict_deps # Only consider a target up to date if its list of dependencies is # exactly equal (including order) to the cached list of dependencies + # # @return [Boolean] # True value if the targets are all up to date, meaning that, # for each target: @@ -144,11 +146,14 @@ module Rscons true end - # Store cache information about target(s) built by a builder + # Store cache information about target(s) built by a builder. + # # @param targets [String, Array] The name of the target(s) built. # @param command [String, Array] The command used to build the target. # @param deps [Array] List of dependencies for the target. # @param env [Environment] The {Rscons::Environment}. + # + # @return [void] def register_build(targets, command, deps, env) Array(targets).each do |target| @cache["targets"][target] = { @@ -171,13 +176,19 @@ module Rscons end end - # Return a list of targets that have been built + # Return a list of targets that have been built. + # + # @return [Array] List of targets that have been built. def targets @cache["targets"].keys end # Make any needed directories and record the ones that are created for # removal upon a "clean" operation. + # + # @param path [String] Directory to create. + # + # @return [void] def mkdir_p(path) parts = path.split(/[\\\/]/) parts.each_index do |i| @@ -191,7 +202,10 @@ module Rscons end end - # Return a list of directories which were created as a part of the build + # Return a list of directories which were created as a part of the build. + # + # @return [Array] + # List of directories which were created as a part of the build. def directories @cache["directories"].keys end @@ -213,14 +227,20 @@ module Rscons end # Return a file's checksum, or the previously calculated checksum for - # the same file + # the same file. + # # @param file [String] The file name. + # + # @return [String] The file's checksum. def lookup_checksum(file) @lookup_checksums[file] || calculate_checksum(file) end - # Calculate and return a file's checksum + # Calculate and return a file's checksum. + # # @param file [String] The file name. + # + # @return [String] The file's checksum. def calculate_checksum(file) @lookup_checksums[file] = Digest::MD5.hexdigest(File.read(file, mode: "rb")) rescue "" end diff --git a/lib/rscons/environment.rb b/lib/rscons/environment.rb index 9e40dc1..085095d 100644 --- a/lib/rscons/environment.rb +++ b/lib/rscons/environment.rb @@ -7,25 +7,33 @@ module Rscons # contains a collection of construction variables, options, builders, and # rules for building targets. class Environment - # Hash of +{"builder_name" => builder_object}+ pairs. + # @return [Hash] Set of \{"builder_name" => builder_object} pairs. attr_reader :builders # :command, :short, or :off attr_accessor :echo - # String or +nil+ + # @return [String, nil] The build root. attr_reader :build_root + + # Set the build root. + # + # @param build_root [String] The build root. def build_root=(build_root) @build_root = build_root @build_root.gsub!('\\', '/') if @build_root end # Create an Environment object. + # # @param options [Hash] - # Possible options keys: - # :echo => :command, :short, or :off (default :short) - # :build_root => String specifying build root directory (default nil) - # :exclude_builders => true to omit adding default builders (default false) + # @option options [Symbol] :echo + # :command, :short, or :off (default :short) + # @option options [String] :build_root + # Build root directory (default nil) + # @option options [Boolean] :exclude_builders + # Whether to omit adding default builders (default false) + # # If a block is given, the Environment object is yielded to the block and # when the block returns, the {#process} method is automatically called. def initialize(options = {}) @@ -108,6 +116,10 @@ module Rscons end # Add a {Builder} object to the Environment. + # + # @param builder [Builder] The {Builder} object to add. + # + # @return [void] def add_builder(builder) @builders[builder.name] = builder var_defs = builder.default_variables(self) @@ -119,19 +131,45 @@ module Rscons end # Add a build hook to the Environment. + # + # @yield [build_op] + # Invoke the given block with the current build operation. + # @yieldparam build_op [Hash] + # Hash with keys: + # - :builder - The builder object in use. + # - :target - Target file name. + # - :sources - List of source file(s). + # - :vars - Set of construction variable values in use. + # - :env - The Environment invoking the builder. + # + # @return [void] def add_build_hook(&block) @build_hooks << block end # Specify a build directory for this Environment. + # # Source files from src_dir will produce object files under obj_dir. + # + # @param src_dir [String] Path to the source directory. + # @param obj_dir [String] Path to the object directory. + # + # @return [void] def build_dir(src_dir, obj_dir) src_dir = src_dir.gsub('\\', '/') if src_dir.is_a?(String) @build_dirs << [src_dir, obj_dir] end - # Return the file name to be built from source_fname with suffix suffix. + # Return the file name to be built from +source_fname+ with suffix + # +suffix+. + # # This method takes into account the Environment's build directories. + # + # @param source_fname [String] Source file name. + # @param suffix [String] Suffix, including "." if desired. + # + # @return [String] + # The file name to be built from +source_fname+ with suffix +suffix+. def get_build_fname(source_fname, suffix) build_fname = Rscons.set_suffix(source_fname, suffix).gsub('\\', '/') found_match = @build_dirs.find do |src_dir, obj_dir| @@ -151,26 +189,32 @@ module Rscons end # Access a construction variable or environment option. + # # @see VarSet#[] def [](*args) @varset.send(:[], *args) end # Set a construction variable or environment option. + # # @see VarSet#[]= def []=(*args) @varset.send(:[]=, *args) end # Add a set of construction variables or environment options. + # # @see VarSet#append def append(*args) @varset.append(*args) end - # Build all target specified in the Environment. + # Build all build targets specified in the Environment. + # # When a block is passed to Environment.new, this method is automatically # called after the block returns. + # + # @return [void] def process unless @targets.empty? expand_paths! @@ -227,13 +271,16 @@ module Rscons end alias_method :build_command, :expand_varref - # Execute a builder command + # Execute a builder command. + # # @param short_desc [String] Message to print if the Environment's echo # mode is set to :short # @param command [Array] The command to execute. # @param options [Hash] Optional options, possible keys: # - :env - environment Hash to pass to Kernel#system. # - :options - options Hash to pass to Kernel#system. + # + # @return [true,false,nil] Return value from Kernel.system(). def execute(short_desc, command, options = {}) print_command = proc do puts command.map { |c| c =~ /\s/ ? "'#{c}'" : c }.join(' ') @@ -253,6 +300,14 @@ module Rscons end end + # Define a build target. + # + # @param method [Symbol] Method name. + # @param args [Array] Method arguments. + # + # @return [BuildTarget] + # The {BuildTarget} object registered, if the method called is a + # {Builder}. def method_missing(method, *args) if @builders.has_key?(method.to_s) target, sources, vars, *rest = args @@ -269,6 +324,15 @@ module Rscons end end + # Add a build target. + # + # @param target [String] Build target file name. + # @param builder [Builder] The {Builder} to use to build the target. + # @param sources [Array] Source file name(s). + # @param vars [Hash] Construction variable overrides. + # @param args [Object] Any extra arguments passed to the {Builder}. + # + # @return [void] def add_target(target, builder, sources, vars, args) @targets[target] = { builder: builder, @@ -290,20 +354,28 @@ module Rscons @user_deps[target] = (@user_deps[target] + user_deps).uniq end - # Return the list of user dependencies for a given target, or +nil+ for - # none. + # Return the list of user dependencies for a given target. + # + # @param target [String] Target file name. + # + # @return [Array,nil] + # List of user-specified dependencies for the target, or nil if none were + # specified. def get_user_deps(target) @user_deps[target] end # Build a list of source files into files containing one of the suffixes # given by suffixes. + # # This method is used internally by Rscons builders. + # # @param sources [Array] List of source files to build. # @param suffixes [Array] List of suffixes to try to convert source files into. # @param cache [Cache] The Cache. # @param vars [Hash] Extra variables to pass to the builder. - # Return a list of the converted file names. + # + # @return [Array] List of the converted file name(s). def build_sources(sources, suffixes, cache, vars) sources.map do |source| if source.end_with?(*suffixes) @@ -325,12 +397,14 @@ module Rscons end # Invoke a builder to build the given target based on the given sources. + # # @param builder [Builder] The Builder to use. # @param target [String] The target output file. # @param sources [Array] List of source files. # @param cache [Cache] The Cache. # @param vars [Hash] Extra variables to pass to the builder. - # Return the result of the builder's run() method. + # + # @return [String,false] Return value from the {Builder}'s +run+ method. def run_builder(builder, target, sources, cache, vars) vars = @varset.merge(vars) @build_hooks.each do |build_hook_block| @@ -516,6 +590,8 @@ module Rscons # This method expand construction variable references in the target and # source file names before passing them to the builder. It also expands # "^/" prefixes to the Environment's build root if a build root is defined. + # + # @return [void] def expand_paths! @targets = @targets.reduce({}) do |result, (target, target_params)| sources = target_params[:sources].map do |source| @@ -530,9 +606,13 @@ module Rscons end # Parse dependencies for a given target from a Makefile. + # # This method is used internally by Rscons builders. + # # @param mf_fname [String] File name of the Makefile to read. # @param target [String] Name of the target to gather dependencies for. + # + # @return [Array] Paths of dependency files. def self.parse_makefile_deps(mf_fname, target) deps = [] buildup = '' diff --git a/lib/rscons/varset.rb b/lib/rscons/varset.rb index 2c53237..be575d8 100644 --- a/lib/rscons/varset.rb +++ b/lib/rscons/varset.rb @@ -1,9 +1,9 @@ module Rscons - # This class represents a collection of variables which can be accessed - # as certain types. - # Only nil, strings, arrays, and hashes should be stored in a VarSet. + # This class represents a collection of variables which supports efficient + # deep cloning. class VarSet # Create a VarSet. + # # @param vars [Hash] Optional initial variables. def initialize(vars = {}) @my_vars = {} @@ -11,8 +11,10 @@ module Rscons append(vars) end - # Access the value of variable as a particular type + # Access the value of variable. + # # @param key [String, Symbol] The variable name. + # # @return [Object] The variable's value. def [](key) if @my_vars.include?(key) @@ -29,15 +31,19 @@ module Rscons end # Assign a value to a variable. + # # @param key [String, Symbol] The variable name. + # # @param val [Object] The value to set. def []=(key, val) @my_vars[key] = val end # Check if the VarSet contains a variable. + # # @param key [String, Symbol] The variable name. - # @return [true, false] Whether the VarSet contains a variable. + # + # @return [Boolean] Whether the VarSet contains the variable. def include?(key) if @my_vars.include?(key) true @@ -48,8 +54,11 @@ module Rscons end end - # Add or overwrite a set of variables + # Add or overwrite a set of variables. + # # @param values [VarSet, Hash] New set of variables. + # + # @return [self] def append(values) coa! if values.is_a?(VarSet) @@ -62,7 +71,10 @@ module Rscons end # Create a new VarSet object based on the first merged with other. + # # @param other [VarSet, Hash] Other variables to add or overwrite. + # + # @return [VarSet] The newly created VarSet. def merge(other = {}) coa! varset = self.class.new @@ -71,9 +83,13 @@ module Rscons end alias_method :clone, :merge - # Replace "$" variable references in varref with the variables values, - # recursively. + # Replace "$\{var}" variable references in varref with the expanded + # variables' values, recursively. + # # @param varref [String, Array] Value containing references to variables. + # + # @return [String, Array] + # Expanded value with "$\{var}" variable references replaced. def expand_varref(varref) if varref.is_a?(Array) varref.map do |ent| @@ -99,6 +115,8 @@ module Rscons private # Move all VarSet variables into the copy-on-access list. + # + # @return [void] def coa! unless @my_vars.empty? @coa_vars.unshift(@my_vars) @@ -107,8 +125,13 @@ module Rscons end # Create a deep copy of an object. - # @param obj [nil, String, Array, Hash] Object to deep copy. - # @return [nil, String, Array, Hash] Deep copied value. + # + # Only objects which are of type String, Array, or Hash are deep copied. + # Any other object just has its referenced copied. + # + # @param obj [Object] Object to deep copy. + # + # @return [Object] Deep copied value. def deep_dup(obj) obj_class = obj.class if obj_class == Hash