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.

Autodetecting dependencies There are two sides in dependency autodetection. The first side is that packages must declare what they provide. One example is the handling of pkgconfig dependencies: packages must declare that they provide a pkgconfig definition. This side of the autodetection must be done just after the package's import, by overloading the import method:

def import
  super

  # Do autodetection and call Package#provides
end

Note that, in most cases, the overloaded import method must begin with “super”.

The other side is the detection itself. That must be done by overloading the prepare method.

Attributes

dependencies[R]

The list of packages this one depends upon

doc_dir[W]

Directory in which the documentation target will have generated the documentation (if any). The interpretation of relative directories is package-specific. The default implementation interpret them as relative to the source directory, but packages like CMake will interpret them as relative to their build directories.

doc_target_dir[W]

Directory in which the documentation target should install the documentation. If it is relative, it is interpreted as relative to the documentation prefix directory (Autobuild.doc_prefix)

failures[R]

If something failed on this package, returns the corresponding exception object. Otherwise, returns nil

importer[RW]

Sets an importer object for this package

logdir[W]

Sets the log directory. If no value is set, the package will use Autobuild.logdir

name[R]

the package name

prefix[W]

set the installation directory. If a relative path is given, it is relative to Autobuild.prefix. Defaults to ”

srcdir[W]

set the source directory. If a relative path is given, it is relative to Autobuild.srcdir. Defaults to name

statistics[R]

Some statistics about the commands that have been run

updated[W]

Public Class Methods

[](name) click to toggle source

Gets a package from its name

# File lib/autobuild/package.rb, line 600
def self.[](name)
    @@packages[name.to_s] || @@provides[name.to_s]
end
clear() click to toggle source

Removes all package definitions

# File lib/autobuild/package.rb, line 605
def self.clear
    @@packages.clear
    @@provides.clear
end
each(with_provides = false, &p) click to toggle source

Iterates on all available packages if with_provides is true, includes the list of package aliases

# File lib/autobuild/package.rb, line 590
def self.each(with_provides = false, &p)
    if !p
        return enum_for(:each, with_provides)
    end

    @@packages.each(&p) 
    @@provides.each(&p) if with_provides
end
new(spec = Hash.new) { |self| ... } click to toggle source
# File lib/autobuild/package.rb, line 110
def initialize(spec = Hash.new)
    @dependencies   = Array.new
    @provides       = Array.new
    @parallel_build_level = nil
    @statistics     = Hash.new
    @failures = Array.new
    @post_install_blocks = Array.new
    @in_dir_stack = Array.new

    if Hash === spec
        name, depends = spec.to_a.first
    else
        name, depends = spec, nil
    end

    name = name.to_s
    @name = name
    raise ConfigException, "package #{name} is already defined" if Autobuild::Package[name]
    @@packages[name] = self

    # Call the config block (if any)
    yield(self) if block_given?

    @doc_dir        ||= 'doc'
    @doc_target_dir ||= name

    # Define the default tasks
    task "#{name}-import" do
        isolate_errors { import }
    end
    task :import => "#{name}-import"

    # Define the prepare task
    task "#{name}-prepare" => "#{name}-import" do
        isolate_errors { prepare }
    end
    task :prepare => "#{name}-prepare"

    task "#{name}-build" => "#{name}-prepare"
    task :build => "#{name}-build"

    task(name) do
        Rake::Task["#{name}-import"].invoke
        Rake::Task["#{name}-prepare"].invoke
        Rake::Task["#{name}-build"].invoke
        if has_doc? && Autobuild.do_doc
            Rake::Task["#{name}-doc"].invoke
        end
    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

add_stat(phase, duration) click to toggle source
# File lib/autobuild/package.rb, line 77
def add_stat(phase, duration)
    @statistics[phase] ||= 0
    @statistics[phase] += duration
end
all_dependencies(result = Set.new) click to toggle source

Returns the name of all the packages self depends on

# File lib/autobuild/package.rb, line 522
def all_dependencies(result = Set.new)
    dependencies.each do |pkg_name|
        pkg = Autobuild::Package[pkg_name]
        if !result.include?(pkg.name)
            result << pkg.name
            pkg.all_dependencies(result)
        end
    end
    result
end
depends_on(*packages) click to toggle source

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 542
def depends_on(*packages)
    packages.each do |p|
        raise ArgumentError, "#{p.inspect} should be a string" if !p.respond_to? :to_str
        p = p.to_str
        next if p == name
        unless pkg = Package[p]
            raise ConfigException.new(self), "package #{p}, listed as a dependency of #{self.name}, is not defined"
        end

        next if @dependencies.include?(pkg.name)

        if Autobuild.verbose
            Autobuild.message "#{name} depends on #{pkg.name}"
        end

        task "#{name}-import"  => "#{pkg.name}-import"
        task "#{name}-prepare" => "#{pkg.name}-prepare"
        task "#{name}-build"   => "#{pkg.name}-build"
        @dependencies << pkg.name
    end
end
depends_on?(package_name) click to toggle source

Returns true if this package depends on package_name and false otherwise.

# File lib/autobuild/package.rb, line 535
def depends_on?(package_name)
    @dependencies.include?(package_name)
end
disable() click to toggle source

Make sure that this package will be ignored in the build

# File lib/autobuild/package.rb, line 652
def disable
    @disabled = true
    %w{import prepare build doc}.each do |phase|
        task "#{name}-#{phase}"
        t = Rake::Task["#{name}-#{phase}"]
        def t.needed?; false end
        t.instance_variable_set :@already_invoked, true
    end
    task(installstamp)
    t = Rake::Task[installstamp]
    def t.needed?; false end
    t.instance_variable_set :@already_invoked, true
end
disable_doc() click to toggle source

Disables any documentation generation, regardless of whether #doc_task has been called or not.

# File lib/autobuild/package.rb, line 480
def disable_doc
    @doc_disabled = true
end
disabled?() click to toggle source
# File lib/autobuild/package.rb, line 647
def disabled?
    @disabled
end
doc_dir() click to toggle source

Absolute path to where documentation is generated. Returns nil if the #doc_dir attribute is not set.

# File lib/autobuild/package.rb, line 369
def doc_dir
    if @doc_dir
        File.expand_path(@doc_dir, srcdir)
    end
end
doc_disabled() click to toggle source

Can be called in the #doc_task implementation to announce that the documentation is to be disabled for that package. This is mainly used when a runtime check is necessary to know if a package has documentation or not.

# File lib/autobuild/package.rb, line 502
def doc_disabled
    throw :doc_disabled
end
doc_target_dir() click to toggle source

Absolute path to where documentation has to be installed. Returns nil if the #doc_target_dir attribute is not set.

# File lib/autobuild/package.rb, line 382
def doc_target_dir
    if @doc_target_dir
        File.expand_path(@doc_target_dir, File.expand_path(Autobuild.doc_prefix || '.',  prefix))
    end
end
doc_task() { || ... } click to toggle source

Defines a documentation generation task. The documentation is first generated by the given block, and then installed. The local attribute doc_dir defines where the documentation is generated by the package’s build system, and the doc_target_dir and Autobuild.doc_prefix attributes define where it should be installed.

The block is invoked in the package’s source directory

In general, specific package types define a meaningful with_doc method which calls this method internally.

# File lib/autobuild/package.rb, line 425
def doc_task
    @doc_task = task "#{name}-doc" => "#{name}-build" do
        # This flag allows to disable documentation generation
        # once doc_task has been called
        if generates_doc?
            @installed_doc = false
            catch(:doc_disabled) do
                begin
                    yield if block_given?

                    unless @installed_doc
                        install_doc
                    end

                rescue Interrupt
                    raise
                rescue ::Exception => e
                    if Autobuild.doc_errors
                        raise
                    else
                        warn "%s: failed to generate documentation"
                        if e.kind_of?(SubcommandFailed)
                            warn "%s: see #{e.logfile} for more details"
                        else
                            warn "%s: #{e.message}"
                        end
                    end
                end
            end
        end
    end

    task :doc => "#{name}-doc"
end
enable_doc() click to toggle source

Re-enables documentation generation after disable_doc has been called

# File lib/autobuild/package.rb, line 474
def enable_doc
    @doc_disabled = false
end
error(error_string) click to toggle source

Display a progress message. %s in the string is replaced by the package name

# File lib/autobuild/package.rb, line 303
def error(error_string)
    message("  ERROR: #{error_string}", :red, :bold)
end
failed?() click to toggle source

Returns true if one of the operations applied on this package failed

# File lib/autobuild/package.rb, line 186
def failed?
    @failed
end
file(*args, &block) click to toggle source

Calls Rake to define a file task and then extends it with TaskExtension

Calls superclass method
# File lib/autobuild/package.rb, line 400
def file(*args, &block)
    task = super
    task.extend TaskExtension
    task.package = self
    task
end
generates_doc?() click to toggle source

True if some documentation will be generated and false otherwise.

This will return true only if a documentation task has been defined by calling doc_task and disable_doc has not been called (or if enable_doc has been called after the last call to disable_doc).

# File lib/autobuild/package.rb, line 465
def generates_doc?
    if @doc_disabled
        return false
    else
        return !!@doc_task
    end
end
has_doc?() click to toggle source

True if a documentation task is defined for this package

# File lib/autobuild/package.rb, line 507
def has_doc?
    !!Rake.application.lookup("#{name}-doc")
end
import() click to toggle source

Call the importer if there is one. Autodetection of “provides” should be done there as well. See the documentation of Autobuild::Package for more information.

# File lib/autobuild/package.rb, line 243
def import
    if @importer
        @importer.import(self)
    elsif Autobuild.do_update
        message "%s: no importer defined, doing nothing"
    end

    # Add the dependencies declared in spec
    depends_on *@spec_dependencies if @spec_dependencies
    update_environment
end
import=(value) click to toggle source

Sets importer object for this package. Defined for backwards compatibility. Use the importer attribute instead

# File lib/autobuild/package.rb, line 65
def import=(value)
    @importer = value
end
in_dir(directory) { || ... } click to toggle source
# File lib/autobuild/package.rb, line 639
def in_dir(directory)
    @in_dir_stack << directory
    yield

ensure
    @in_dir_stack.pop
end
install() click to toggle source

Install the result in prefix

# File lib/autobuild/package.rb, line 342
def 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
    # Safety net for forgotten progress_done
    progress_done

    Autobuild.touch_stamp(installstamp)
    update_environment
end
install_doc() click to toggle source
# File lib/autobuild/package.rb, line 484
def install_doc
    if !File.directory?(self.doc_dir)
        raise "#{self.doc_dir} was expected to be a directory, but it is not. Check the package's documentation generation, the generated documentation should be in #{self.doc_dir}"
    end

    doc_target_dir  = self.doc_target_dir
    doc_dir         = self.doc_dir
    FileUtils.rm_rf   doc_target_dir
    FileUtils.mkdir_p File.dirname(doc_target_dir)
    FileUtils.cp_r    doc_dir, doc_target_dir

    @installed_doc = true
end
installstamp() click to toggle source

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 99
def installstamp
    File.join(logdir, "#{name}-#{STAMPFILE}")
end
isolate_errors(mark_as_failed = true) { || ... } click to toggle source

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 201
def isolate_errors(mark_as_failed = true)
    # Don't do anything if we already have failed
    return if failed?

    begin
        toplevel = !Thread.current[:isolate_errors]
        Thread.current[:isolate_errors] = true
        yield
    rescue Interrupt
        raise
    rescue ::Exception => e
        @failures << e
        if mark_as_failed
            @failed = true
        end

        if Autobuild.ignore_errors
            lines = e.to_s.split("\n")
            if lines.empty?
                lines = e.message.split("\n")
            end
            if lines.empty?
                lines = ["unknown error"]
            end
            message(lines.shift, :red, :bold)
            lines.each do |line|
                message(line)
            end
            nil
        else
            raise
        end
    ensure
        if toplevel
            Thread.current[:isolate_errors] = false
        end
    end
end
logdir() click to toggle source

Absolute path to the log directory for this package. See logdir=

# File lib/autobuild/package.rb, line 87
def logdir
    if @logdir
        File.expand_path(@logdir, prefix)
    else
        Autobuild.logdir
    end
end
message(*args) click to toggle source

Display a progress message. %s in the string is replaced by the package name

# File lib/autobuild/package.rb, line 309
def message(*args)
    if !args.empty?
        args[0] = "  #{process_formatting_string(args[0])}"
    end
    Autobuild.message(*args)
end
parallel_build_level() click to toggle source

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 625
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
parallel_build_level=(value) click to toggle source

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 616
def parallel_build_level=(value)
    @parallel_build_level = Integer(value)
end
post_install(*args, &block) click to toggle source
# File lib/autobuild/package.rb, line 511
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
prefix() click to toggle source

Absolute path to the installation directory. See prefix=

# File lib/autobuild/package.rb, line 85
def prefix; File.expand_path(@prefix || '', Autobuild.prefix) end
prepare() click to toggle source

Create all the dependencies required to reconfigure and/or rebuild the package when required. The package’s build target is called “package_name-build”.

Calls superclass method
# File lib/autobuild/package.rb, line 265
def prepare
    super if defined? super

    stamps = dependencies.map { |p| Package[p].installstamp }

    file installstamp => stamps do
        isolate_errors { install }
    end
    task "#{name}-build" => installstamp
end
prepare_for_forced_build() click to toggle source

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 169
def prepare_for_forced_build
    if File.exists?(installstamp)
        FileUtils.rm_f installstamp
    end
end
prepare_for_rebuild() click to toggle source

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 177
def prepare_for_rebuild
    prepare_for_forced_build

    if File.exists?(installstamp)
        FileUtils.rm_f installstamp
    end
end
process_formatting_string(msg, *prefix_style) click to toggle source
# File lib/autobuild/package.rb, line 276
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?
        return msg
    elsif prefix_style.empty?
        return (prefix + suffix).join(" ")
    else
        return [Autobuild.color(prefix.join(" "), *prefix_style), *suffix].join(" ")
    end
end
progress(*args) click to toggle source
# File lib/autobuild/package.rb, line 329
def progress(*args)
    args[0] = process_formatting_string(args[0], :bold)
    Autobuild.progress(self, *args)
end
progress_done(done_message = nil) click to toggle source
# File lib/autobuild/package.rb, line 334
def progress_done(done_message = nil)
    if done_message
        progress(process_formatting_string(done_message))
    end
    Autobuild.progress_done(self)
end
progress_start(*args, &block) click to toggle source
# File lib/autobuild/package.rb, line 316
def progress_start(*args, &block)
    args[0] = process_formatting_string(args[0], :bold)
    if args.last.kind_of?(Hash)
        options, raw_options = Kernel.filter_options args.last, :done_message
        if options[:done_message]
            options[:done_message] = process_formatting_string(options[:done_message])
        end
        args[-1] = options.merge(raw_options)
    end
        
    Autobuild.progress_start(self, *args, &block)
end
provides(*packages) click to toggle source

Declare that this package provides packages. In effect, the names listed in packages are aliases for this package.

# File lib/autobuild/package.rb, line 566
def provides(*packages)
    packages.each do |p|
        raise ArgumentError, "#{p.inspect} should be a string" if !p.respond_to? :to_str
        p = p.to_str
        next if p == name
        next if @provides.include?(name)

        @@provides[p] = self 

        if Autobuild.verbose
            Autobuild.message "#{name} provides #{p}"
        end

        task p => name
        task "#{p}-import" => "#{name}-import"
        task "#{p}-prepare" => "#{name}-prepare"
        task "#{p}-build" => "#{name}-build"
        @provides << p
    end
end
run(*args, &block) click to toggle source
# File lib/autobuild/package.rb, line 356
def run(*args, &block)
    Autobuild::Subprocess.run(self, *args, &block)
end
source_tree(*args, &block) click to toggle source
# File lib/autobuild/package.rb, line 392
def source_tree(*args, &block)
    task = Autobuild.source_tree(*args, &block)
    task.extend TaskExtension
    task.package = self
    task
end
srcdir() click to toggle source

Absolute path to the source directory. See srcdir=

# File lib/autobuild/package.rb, line 83
def srcdir; File.expand_path(@srcdir || name, Autobuild.srcdir) end
task(*args, &block) click to toggle source

Calls Rake to define a plain task and then extends it with TaskExtension

Calls superclass method
# File lib/autobuild/package.rb, line 408
def task(*args, &block)
    task = super
    task.extend TaskExtension
    task.package = self
    task
end
update_environment() click to toggle source

Called to set/update all environment variables at import and after install time

Calls superclass method
# File lib/autobuild/package.rb, line 257
def update_environment
    super if defined? super
    Autobuild.update_environment prefix
end
updated?() click to toggle source

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 108
def updated?; !!@updated end
warn(warning_string) click to toggle source

Display a progress message. %s in the string is replaced by the package name

# File lib/autobuild/package.rb, line 297
def warn(warning_string)
    message("  WARN: #{warning_string}", :magenta)
end
working_directory() click to toggle source
# File lib/autobuild/package.rb, line 635
def working_directory
    @in_dir_stack.last
end