class Autobuild::Package
Basic block for the autobuilder
The build is done in three phases:
- import - prepare - build & install
In the first stage checks the source out and/or updates it.
In the second stage, packages create their dependency structure to handle specific build systems. For instance, it is there that build systems like CMake
are handled so that reconfiguration happens if needed. In the same way, it is there that code generation will happen as well.
Finally, the build stage actually calls the package’s build targets (of the form “package_name-build”, which will trigger the build if needed.
Constants
- EnvOp
Attributes
The list of packages this one depends upon
List of environment values added by this package with {#env_add}, {#env_add_path} or {#env_set}
@return [Array<EnvOp>]
If something failed on this package, returns the corresponding exception object. Otherwise, returns nil
set the importdir, this can be different than the sourcedir if the source-root is in an subfolder of the package itself then the importdir will be the root
Sets an importer object for this package
Sets the log directory. If no value is set, the package will use Autobuild.logdir
the package name
set the installation directory. If a relative path is given, it is relative to Autobuild.prefix. Defaults to ”
set the source directory. If a relative path is given, it is relative to Autobuild.srcdir. Defaults to name
Some statistics about the commands that have been run
Sets whether this package should update itself or not. If false, the only importer operation that will be performed is checkout
If nil, the global setting Autobuild.do_update is used
The set of utilities attached to this package @return [{String=>Utility}]
Public Class Methods
Gets a package from its name
# File lib/autobuild/package.rb, line 793 def self.[](name) @@packages[name.to_s] || @@provides[name.to_s] end
Removes all package definitions
# File lib/autobuild/package.rb, line 798 def self.clear @@packages.clear @@provides.clear end
Iterates on all available packages if with_provides is true, includes the list of package aliases
# File lib/autobuild/package.rb, line 785 def self.each(with_provides = false, &block) return enum_for(:each, with_provides) unless block @@packages.each(&block) @@provides.each(&block) if with_provides end
# File lib/autobuild/package.rb, line 136 def initialize(spec = Hash.new) @srcdir = @importdir = @logdir = @prefix = nil @updated = false @update = nil @failed = nil @dependencies = Array.new @provides = Array.new @statistics = Hash.new @parallel_build_level = nil @failures = Array.new @post_install_blocks = Array.new @applied_post_install = false @in_dir_stack = Array.new @utilities = Hash.new @env = Array.new @imported = false @prepared = false @built = false @disabled = nil if Hash === spec name, depends = spec.to_a.first else name = spec depends = nil end name = name.to_s @name = name if Autobuild::Package[name] raise ConfigException, "package #{name} is already defined" end @@packages[name] = self # Call the config block (if any) yield(self) if block_given? doc_utility.source_dir ||= 'doc' doc_utility.target_dir ||= name # Define the default tasks task "#{name}-import" do isolate_errors { import } @imported = true end task :import => "#{name}-import" # Define the prepare task task "#{name}-prepare" => "#{name}-import" do isolate_errors { prepare } @prepared = true end task :prepare => "#{name}-prepare" task "#{name}-build" task :build => "#{name}-build" task(name) do Rake::Task["#{name}-import"].invoke Rake::Task["#{name}-prepare"].invoke Rake::Task["#{name}-build"].invoke Rake::Task["#{name}-doc"].invoke if has_doc? && Autobuild.do_doc end task :default => name # The dependencies will be declared in the import phase, so save # them there for now @spec_dependencies = depends end
Public Instance Methods
@api private
Adds a new operation to this package’s environment setup. This is a helper for the other env_* methods
@param [EnvOp] op @return [void]
# File lib/autobuild/package.rb, line 243 def add_env_op(envop) env << envop end
# File lib/autobuild/package.rb, line 75 def add_stat(phase, duration) @statistics[phase] ||= 0 @statistics[phase] += duration end
Returns the name of all the packages self
depends on
# File lib/autobuild/package.rb, line 712 def all_dependencies(result = Set.new) dependencies.each do |pkg_name| pkg = Autobuild::Package[pkg_name] unless result.include?(pkg.name) result << pkg.name pkg.all_dependencies(result) end end result end
Whether {#apply_post_install} has been called
# File lib/autobuild/package.rb, line 48 def applied_post_install? @applied_post_install end
@api private
Apply this package’s environment to the given {Environment} object
It does not apply the dependencies’ environment. Call {#resolved_env} for that.
@param [Environment] env the environment to be updated @param [Set] set a set of environment variable names which have
already been set by a {#env_set}. Autoproj will verify that only one package sets a variable as to avoid unexpected conflicts.
@return [Array<EnvOp>] list of environment-modifying operations
applied so far
# File lib/autobuild/package.rb, line 319 def apply_env(env, set = Hash.new, ops = Array.new) self.env.each do |env_op| next if ops.last == env_op if env_op.type == :set if (last = set[env_op.name]) last_pkg, last_values = *last if last_values != env_op.values raise IncompatibleEnvironment, "trying to reset "\ "#{env_op.name} to #{env_op.values} in #{name} "\ "but this conflicts with #{last_pkg.name} "\ "already setting it to #{last_values}" end else set[env_op.name] = [self, env_op.values] end end if env_op.type == :source_after env.send(env_op.type, env_op.name, **env_op.values) else env.send(env_op.type, env_op.name, *env_op.values) end ops << env_op end ops end
# File lib/autobuild/package.rb, line 555 def apply_post_install Autobuild.post_install_handlers.each do |b| Autobuild.apply_post_install(self, b) end @post_install_blocks.each do |b| Autobuild.apply_post_install(self, b) end @applied_post_install = true end
Whether the package’s source directory is present on disk
# File lib/autobuild/package.rb, line 208 def checked_out? File.directory?(srcdir) end
This package depends on packages
. It means that its build will always be triggered after the packages listed in packages
are built and installed.
# File lib/autobuild/package.rb, line 732 def depends_on(*packages) packages.each do |p| p = p.name if p.respond_to?(:name) unless p.respond_to?(:to_str) raise ArgumentError, "#{p.inspect} should be a string" end p = p.to_str next if p == name unless (pkg = Package[p]) raise ConfigException.new(self), "package #{p}, "\ "listed as a dependency of #{name}, is not defined" end next if @dependencies.include?(pkg.name) Autobuild.message "#{name} depends on #{pkg.name}" if Autobuild.verbose task "#{name}-import" => "#{pkg.name}-import" task "#{name}-prepare" => "#{pkg.name}-prepare" task "#{name}-build" => "#{pkg.name}-build" @dependencies << pkg.name end end
Returns true if this package depends on package_name
and false otherwise.
# File lib/autobuild/package.rb, line 725 def depends_on?(package_name) @dependencies.include?(package_name) end
Make sure that this package will be ignored in the build
# File lib/autobuild/package.rb, line 853 def disable(_phases = Autobuild.all_phases) @disabled = true end
# File lib/autobuild/package.rb, line 652 def disable_doc doc_utility.enabled = false end
Makes sure that the specified phases of this package will be no-ops
# File lib/autobuild/package.rb, line 844 def disable_phases(*phases) phases.each do |phase| task "#{name}-#{phase}" t = Rake::Task["#{name}-#{phase}"] t.disable end end
# File lib/autobuild/package.rb, line 839 def disabled? @disabled end
# File lib/autobuild/package.rb, line 628 def doc_dir doc_utility.source_dir end
# File lib/autobuild/package.rb, line 624 def doc_dir=(value) doc_utility.source_dir = value end
# File lib/autobuild/package.rb, line 660 def doc_disabled doc_utility.disabled end
# File lib/autobuild/package.rb, line 636 def doc_target_dir doc_utility.target_dir end
# File lib/autobuild/package.rb, line 632 def doc_target_dir=(value) doc_utility.target_dir = value end
# File lib/autobuild/package.rb, line 640 def doc_task(&block) doc_utility.task(&block) end
# File lib/autobuild/package.rb, line 648 def enable_doc doc_utility.enabled = true end
Add value(s) to a list-based environment variable
This differs from {#env_add_path} in that a value can be added multiple times in the list.
@param [String] name the environment variable name @param [Array<String>] values list of values to be added @return [void]
# File lib/autobuild/package.rb, line 255 def env_add(name, *values) add_env_op EnvOp.new(:add, name, values) end
Add a new path to a PATH-like environment variable
It differs from {#env_add} in its handling of duplicate values. Any value already existing will be removed, and re-appended to the value so that it takes priority.
@param [String] name the environment variable name @param [Array<String>] values list of values. They will be joined
using the platform's standard separator (e.g. : on Unices)
@return [void]
# File lib/autobuild/package.rb, line 269 def env_add_path(name, *values) add_env_op EnvOp.new(:add_path, name, values) end
Add a prefix to be resolved into the environment
Autoproj will update all “standard” environment variables based on what it finds as subdirectories from the prefix
# File lib/autobuild/package.rb, line 287 def env_add_prefix(prefix, includes = nil) add_env_op EnvOp.new(:add_prefix, prefix, [includes]) end
Set an environment variable to a list of values
@param [String] name the environment variable name @param [Array<String>] values list of values. They will be joined
using the platform's standard separator (e.g. : on Unices)
@return [void]
# File lib/autobuild/package.rb, line 279 def env_set(name, *values) add_env_op EnvOp.new(:set, name, values) end
Add a file to be sourced at the end of the generated env file
# File lib/autobuild/package.rb, line 292 def env_source_after(file, shell: "sh") add_env_op EnvOp.new(:source_after, file, shell: shell) end
Display a progress message. %s in the string is replaced by the package name
# File lib/autobuild/package.rb, line 527 def error(error_string) message(" ERROR: #{error_string}", :red, :bold) end
Returns true if one of the operations applied on this package failed
# File lib/autobuild/package.rb, line 398 def failed? @failed end
Calls Rake
to define a file task and then extends it with TaskExtension
# File lib/autobuild/package.rb, line 609 def file(*args, &block) task = super task.extend TaskExtension task.package = self task end
Find a file in a path-like environment variable
# File lib/autobuild/package.rb, line 369 def find_in_path(file, envvar = 'PATH') full_env.find_in_path(file, envvar) end
Returns a unique hash representing a state of the package and its dependencies, if any dependency can’t calculate its own fingerprint the result will be nil @return [String]
# File lib/autobuild/package.rb, line 686 def fingerprint(recursive: true, memo: {}) return memo[name] if memo.key?(name) self_fingerprint = self.self_fingerprint return unless self_fingerprint if dependencies.empty? return (memo[name] = self_fingerprint) elsif !recursive return self_fingerprint end dependency_fingerprints = dependencies.sort.map do |pkg_name| pkg = Autobuild::Package[pkg_name] unless (fingerprint = memo[pkg.name]) fingerprint = pkg.fingerprint(recursive: true, memo: memo) break unless fingerprint end fingerprint end return unless dependency_fingerprints memo[name] = Digest::SHA1.hexdigest( self_fingerprint + dependency_fingerprints.join("")) end
This package’s environment
# File lib/autobuild/package.rb, line 359 def full_env(root = Autobuild.env) set = Hash.new env = root.dup ops = Array.new ops = resolve_dependency_env(env, set, ops) apply_env(env, set, ops) env end
# File lib/autobuild/package.rb, line 644 def generates_doc? doc_utility.enabled? end
# File lib/autobuild/package.rb, line 664 def has_doc? doc_utility.has_task? end
Call the importer if there is one. Autodetection of “provides” should be done there as well.
(see Importer#import
)
# File lib/autobuild/package.rb, line 462 def import(*old_boolean, **options) options = { only_local: old_boolean.first } unless old_boolean.empty? @import_invoked = true if @importer result = @importer.import(self, **options) elsif update? message "%s: no importer defined, doing nothing" end @imported = true # Add the dependencies declared in spec depends_on(*@spec_dependencies) if @spec_dependencies result end
Sets importer object for this package. Defined for backwards compatibility. Use the importer
attribute instead
# File lib/autobuild/package.rb, line 54 def import=(value) @importer = value end
# File lib/autobuild/package.rb, line 212 def import_invoked? @import_invoked end
Absolute path to the import directory. See importdir=
# File lib/autobuild/package.rb, line 86 def importdir File.expand_path(@importdir || srcdir, Autobuild.srcdir) end
# File lib/autobuild/package.rb, line 216 def imported? @imported end
# File lib/autobuild/package.rb, line 832 def in_dir(directory) @in_dir_stack << directory yield ensure @in_dir_stack.pop end
# File lib/autobuild/package.rb, line 232 def inspect to_s end
Install the result in prefix
# File lib/autobuild/package.rb, line 566 def install apply_post_install # Safety net for forgotten progress_done progress_done Autobuild.touch_stamp(installstamp) @installed = true end
# File lib/autobuild/package.rb, line 656 def install_doc doc_utility.install end
# File lib/autobuild/package.rb, line 220 def install_invoked? @install_invoked end
# File lib/autobuild/package.rb, line 224 def installed? @installed end
The file which marks when the last sucessful install has finished. The path is absolute
A package is sucessfully built when it is installed
# File lib/autobuild/package.rb, line 108 def installstamp File.join(logdir, "#{name}-#{STAMPFILE}") end
If Autobuild.ignore_errors
is set, an exception raised from within the provided block will be filtered out, only displaying a message instead of stopping the build
Moreover, the package will be marked as “failed” and isolate_errors
will subsequently be a noop. I.e. if build
fails, install
will do nothing.
# File lib/autobuild/package.rb, line 413 def isolate_errors(options = Hash.new) options = Hash[mark_as_failed: true] unless options.kind_of?(Hash) options = validate_options options, mark_as_failed: true, ignore_errors: Autobuild.ignore_errors # Don't do anything if we already have failed if failed? unless options[:ignore_errors] raise AlreadyFailedError, "attempting to do an operation "\ "on a failed package" end return end begin toplevel = !Thread.current[:isolate_errors] Thread.current[:isolate_errors] = true yield rescue InteractionRequired raise rescue Interrupt raise rescue ::Exception => e @failures << e @failed = true if options[:mark_as_failed] if options[:ignore_errors] lines = e.to_s.split("\n") lines = e.message.split("\n") if lines.empty? lines = ["unknown error"] if lines.empty? message(lines.shift, :red, :bold) lines.each do |line| message(line) end nil else raise end ensure Thread.current[:isolate_errors] = false if toplevel end end
Absolute path to the log directory for this package. See logdir=
# File lib/autobuild/package.rb, line 96 def logdir if @logdir File.expand_path(@logdir, prefix) else Autobuild.logdir end end
Display a progress message. %s in the string is replaced by the package name
# File lib/autobuild/package.rb, line 533 def message(*args) args[0] = " #{process_formatting_string(args[0])}" unless args.empty? Autobuild.message(*args) end
# File lib/autobuild/package.rb, line 865 def method_missing(name, *args, &block) case name.to_s when /(\w+)_utility$/ utility_name = $1 unless args.empty? raise ArgumentError, "expected 0 arguments and got #{args.size}" end begin return utility(utility_name) rescue ArgumentError => e raise NoMethodError.new(name), e.message, e.backtrace end end super end
Returns the level of parallelism authorized during the build for this particular package. If not set, defaults to the system-wide option (Autobuild.parallel_build_level
and Autobuild.parallel_build_level=
).
The default value is the number of CPUs on this system.
# File lib/autobuild/package.rb, line 818 def parallel_build_level if @parallel_build_level.nil? Autobuild.parallel_build_level elsif !@parallel_build_level || @parallel_build_level <= 0 1 else @parallel_build_level end end
Sets the level of parallelism authorized while building this package
See parallel_build_level
and Autobuild.parallel_build_level
for more information.
Note that not all package types use this value
# File lib/autobuild/package.rb, line 809 def parallel_build_level=(value) @parallel_build_level = Integer(value) end
# File lib/autobuild/package.rb, line 668 def post_install(*args, &block) if args.empty? @post_install_blocks << block elsif !block @post_install_blocks << args else raise ArgumentError, "cannot set both arguments and block" end end
Absolute path to the installation directory. See prefix=
# File lib/autobuild/package.rb, line 91 def prefix File.expand_path(@prefix || '', Autobuild.prefix) end
Create all the dependencies required to reconfigure and/or rebuild the package when required. The package’s build target is called “package_name-build”.
# File lib/autobuild/package.rb, line 481 def prepare super if defined? super stamps = dependencies.map { |p| Package[p].installstamp } file installstamp => stamps do isolate_errors do @install_invoked = true install end end task "#{name}-build" => installstamp @prepared = true end
Called before a forced build. It should remove all the timestamp and target files so that all the build phases of this package gets retriggered. However, it should not clean the build products.
# File lib/autobuild/package.rb, line 385 def prepare_for_forced_build FileUtils.rm_f installstamp if File.exist?(installstamp) end
Called when the user asked for a full rebuild. It should delete the build products so that a full build is retriggered.
# File lib/autobuild/package.rb, line 391 def prepare_for_rebuild prepare_for_forced_build FileUtils.rm_f installstamp if File.exist?(installstamp) end
# File lib/autobuild/package.rb, line 497 def process_formatting_string(msg, *prefix_style) prefix = [] suffix = [] msg.split(" ").each do |token| if token =~ /%s/ suffix << token.gsub(/%s/, name) elsif suffix.empty? prefix << token else suffix << token end end if suffix.empty? msg elsif prefix_style.empty? (prefix + suffix).join(" ") else colorized_prefix = Autobuild.color(prefix.join(" "), *prefix_style) [colorized_prefix, *suffix].join(" ") end end
# File lib/autobuild/package.rb, line 545 def progress(*args) args[0] = process_formatting_string(args[0], :bold) Autobuild.progress(self, *args) end
# File lib/autobuild/package.rb, line 550 def progress_done(done_message = nil) done_message = process_formatting_string(done_message) if done_message Autobuild.progress_done(self, message: done_message) end
# File lib/autobuild/package.rb, line 538 def progress_start(*args, done_message: nil, **raw_options, &block) args[0] = process_formatting_string(args[0], :bold) done_message = process_formatting_string(done_message) if done_message Autobuild.progress_start(self, *args, done_message: done_message, **raw_options, &block) end
Declare that this package provides packages
. In effect, the names listed in packages
are aliases for this package.
# File lib/autobuild/package.rb, line 760 def provides(*packages) packages.each do |p| unless p.respond_to?(:to_str) raise ArgumentError, "#{p.inspect} should be a string" end p = p.to_str next if p == name next if @provides.include?(name) @@provides[p] = self Autobuild.message "#{name} provides #{p}" if Autobuild.verbose task p => name task "#{p}-import" => "#{name}-import" task "#{p}-prepare" => "#{name}-prepare" task "#{p}-build" => "#{name}-build" @provides << p end end
@api private
Updates an {Environment} object with the environment of the package’s dependencies
# File lib/autobuild/package.rb, line 350 def resolve_dependency_env(env, set, ops) all_dependencies.each do |pkg_name| pkg = Autobuild::Package[pkg_name] ops = pkg.apply_env(env, set, ops) end ops end
Resolves this package’s environment into Hash form
@param [Environment] root the base environment object to update @return [Hash<String,String>] the full environment @see Autobuild::Environment#resolved_env
# File lib/autobuild/package.rb, line 378 def resolved_env(root = Autobuild.env) full_env(root).resolved_env end
# File lib/autobuild/package.rb, line 861 def respond_to_missing?(name, _include_all) utilities.key?(name.to_s) end
# File lib/autobuild/package.rb, line 577 def run(*args, &block) options = if args.last.kind_of?(Hash) args.pop else Hash.new end options[:env] = options.delete(:resolved_env) || (options[:env] || Hash.new).merge(resolved_env) Autobuild::Subprocess.run(self, *args, options, &block) end
# File lib/autobuild/package.rb, line 678 def self_fingerprint importer.fingerprint(self) end
# File lib/autobuild/package.rb, line 601 def source_tree(*args, &block) task = Autobuild.source_tree(*args, &block) task.extend TaskExtension task.package = self task end
Absolute path to the source directory. See srcdir=
# File lib/autobuild/package.rb, line 81 def srcdir File.expand_path(@srcdir || name, Autobuild.srcdir) end
Calls Rake
to define a plain task and then extends it with TaskExtension
# File lib/autobuild/package.rb, line 617 def task(*args, &block) task = super task.extend TaskExtension task.package = self task end
# File lib/autobuild/package.rb, line 228 def to_s "#<#{self.class} name=#{name}>" end
True if this package should update itself when import
is called
# File lib/autobuild/package.rb, line 119 def update? if @update.nil? Autobuild.do_update else @update end end
Hook called by autoproj to set up the default environment for this package
By default, it calls {#env_add_prefix} with this package’s prefix
# File lib/autobuild/package.rb, line 300 def update_environment env_add_prefix prefix end
Returns true if this package has already been updated. It will not be true if the importer has been called while Autobuild.do_update was false.
# File lib/autobuild/package.rb, line 132 def updated? @updated end
# File lib/autobuild/package.rb, line 857 def utility(utility_name) utilities[utility_name.to_s] ||= Autobuild.create_utility(utility_name, self) end
Display a progress message. %s in the string is replaced by the package name
# File lib/autobuild/package.rb, line 521 def warn(warning_string) message(" WARN: #{warning_string}", :magenta) end
# File lib/autobuild/package.rb, line 828 def working_directory @in_dir_stack.last end