Class: Omnibus::Software

Inherits:
Object
  • Object
show all
Includes:
Cleanroom, Digestable, Logging, NullArgumentable, Sugarable
Defined in:
lib/omnibus/software.rb

Constant Summary

Constants included from NullArgumentable

NullArgumentable::NULL

Instance Attribute Summary collapse

DSL methods collapse

Public API collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Sugarable

extended, included, #node

Methods included from NullArgumentable

included, #null?

Methods included from Logging

included

Methods included from Digestable

#digest, #digest_directory, included

Constructor Details

#initialize(project, filepath = nil, manifest = nil) ⇒ Software

Create a new software object.

Parameters:

  • project (Project)

    the Omnibus project that instantiated this software definition

  • filepath (String) (defaults to: nil)

    the path to where this software definition lives on disk

  • manifest (String) (defaults to: nil)

    the user-supplied software manifest


87
88
89
90
91
92
93
94
95
96
97
98
99
100
# File 'lib/omnibus/software.rb', line 87

def initialize(project, filepath = nil, manifest=nil)
  unless project.is_a?(Project)
    raise ArgumentError,
      "`project' must be a kind of `Omnibus::Project', but was `#{project.class.inspect}'!"
  end

  # Magical methods
  @filepath = filepath
  @project  = project
  @manifest = manifest

  # Overrides
  @overrides = NULL
end

Instance Attribute Details

#manifestObject (readonly)

Returns the value of attribute manifest


73
74
75
# File 'lib/omnibus/software.rb', line 73

def manifest
  @manifest
end

Class Method Details

.load(project, name, manifest) ⇒ Software

Parameters:

  • project (Project)

    the project that loaded this software definition

  • name (String)

    the path to the software definition to load from disk

Returns:


32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
# File 'lib/omnibus/software.rb', line 32

def load(project, name, manifest)
  loaded_softwares[name] ||= begin
    filepath = Omnibus.software_path(name)

    if filepath.nil?
      raise MissingSoftware.new(name)
    else
      log.internal(log_key) do
        "Loading software `#{name}' from `#{filepath}'."
      end
    end

    instance = new(project, filepath, manifest)
    instance.evaluate_file(filepath)
    instance.load_dependencies

    # Add the loaded component to the library
    project.library.component_added(instance)

    instance
  end
end

Instance Method Details

#<=>(other) ⇒ 1, ...

Compare two software projects (by name).

Returns:

  • (1, 0, -1)

117
118
119
# File 'lib/omnibus/software.rb', line 117

def <=>(other)
  self.name <=> other.name
end

#==(other) ⇒ true, false Also known as: eql?

Determine if two softwares are identical.

Parameters:

Returns:

  • (true, false)

819
820
821
# File 'lib/omnibus/software.rb', line 819

def ==(other)
  self.hash == other.hash
end

#build(&block) ⇒ Proc

Define a series of Builder DSL commands that are executed to build the software.

Parameters:

  • block (Proc)

    a block of build commands

Returns:

  • (Proc)

    the build block

See Also:


405
406
407
# File 'lib/omnibus/software.rb', line 405

def build(&block)
  builder.evaluate(&block)
end

#build_dirString

The path where the software will be built.

Returns:

  • (String)

375
376
377
# File 'lib/omnibus/software.rb', line 375

def build_dir
  File.expand_path("#{Config.build_dir}/#{project.name}")
end

#build_metrue

Build the software package. If git caching is turned on (see Config#use_git_caching), the build is restored according to the documented restoration procedure in the git cache. If the build cannot be restored (if the tag does not exist), the actual build steps are executed.

Returns:

  • (true)

778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
# File 'lib/omnibus/software.rb', line 778

def build_me
  if Config.use_git_caching
    if project.dirty?
      log.info(log_key) do
        "Building because `#{project.culprit.name}' dirtied the cache"
      end
      execute_build
    elsif git_cache.restore
      log.info(log_key) { "Restored from cache" }
    else
      log.info(log_key) { "Could not restore from cache" }
      execute_build
      project.dirty!(self)
    end
  else
    log.debug(log_key) { "Forcing build because git caching is off" }
    execute_build
  end

  project.build_version_dsl.resolve(self)
  true
end

#builderBuilder

The builder object for this software definition.

Returns:


627
628
629
# File 'lib/omnibus/software.rb', line 627

def builder
  @builder ||= Builder.new(self)
end

#default_version(val = NULL) ⇒ String

Set or retrieve the #default_version of the software to build.

Examples:

default_version '1.2.3'

Parameters:

  • val (String) (defaults to: NULL)

    the default version to set for the software. For a git source, the default version may be a git ref (e.g. tag, branch name, or sha).

Returns:

  • (String)

281
282
283
284
285
286
287
# File 'lib/omnibus/software.rb', line 281

def default_version(val = NULL)
  if null?(val)
    @version
  else
    @version = val
  end
end

#dependenciesArray<String>

The list of software dependencies for this software. These is the software that comprises your software, and is distinct from runtime dependencies.

Parameters:

  • (Array<String>)

Returns:

  • (Array<String>)

See Also:


666
667
668
# File 'lib/omnibus/software.rb', line 666

def dependencies
  @dependencies ||= []
end

#dependency(val) ⇒ Array<String>

Add a software dependency to this software.

Examples:

dependency 'libxml2'
dependency 'libpng'

Parameters:

  • val (String)

    the name of a software dependency

Returns:

  • (Array<String>)

    the list of current dependencies


192
193
194
195
# File 'lib/omnibus/software.rb', line 192

def dependency(val)
  dependencies << val
  dependencies.dup
end

#description(val = NULL) ⇒ String

Sets the description of the software.

Examples:

description 'Installs libxslt'

Parameters:

  • val (String) (defaults to: NULL)

    the description of the software

Returns:

  • (String)

170
171
172
173
174
175
176
# File 'lib/omnibus/software.rb', line 170

def description(val = NULL)
  if null?(val)
    @description
  else
    @description = val
  end
end

#fetchtrue, false

Fetch the software definition using the appropriate fetcher. This may fetch the software from a local path location, git location, or download the software from a remote URL (HTTP(s)/FTP)

Returns:

  • (true, false)

    true if the software was fetched, false if it was cached


647
648
649
650
651
652
653
654
# File 'lib/omnibus/software.rb', line 647

def fetch
  if fetcher.fetch_required?
    fetcher.fetch
    true
  else
    false
  end
end

#fetcherFetcher

The fetcher for this software

Returns:


746
747
748
# File 'lib/omnibus/software.rb', line 746

def fetcher
  @fetcher ||= Fetcher.fetcher_class_for_source(self.source).new(manifest_entry, project_dir, build_dir)
end

#filepathString?

The path (on disk) where this software came from. Warning: this can be nil if a software was dynamically created!

Returns:

  • (String, nil)

685
686
687
# File 'lib/omnibus/software.rb', line 685

def filepath
  @filepath
end

#hashFixnum

The unique “hash” for this software.

Returns:

  • (Fixnum)

See Also:

  • Omnibus::Software.((#shasum)

808
809
810
# File 'lib/omnibus/software.rb', line 808

def hash
  shasum.hash
end

#install_dirString

The directory where this software is installed on disk.

Examples:

{ 'PATH' => "#{install_dir}/embedded/bin:#{ENV["PATH"]}", }

Returns:

  • (String)

388
389
390
# File 'lib/omnibus/software.rb', line 388

def install_dir
  @project.install_dir
end

#load_dependenciestrue

Recursively load all the dependencies for this software.

Returns:

  • (true)

614
615
616
617
618
619
620
# File 'lib/omnibus/software.rb', line 614

def load_dependencies
  dependencies.each do |dependency|
    Software.load(project, dependency, manifest)
  end

  true
end

#manifest_entryObject


102
103
104
105
106
107
108
109
110
# File 'lib/omnibus/software.rb', line 102

def manifest_entry
  @manifest_entry ||= if manifest
                        log.info(log_key) {"Using user-supplied manifest entry for #{name}"}
                        manifest.entry_for(name)
                      else
                        log.info(log_key) {"Resolving manifest entry for #{name}"}
                        to_manifest_entry
                      end
end

#name(val = NULL) ⇒ String

**[Required]** Sets or retreives the name of the software.

Examples:

name 'libxslt'

Parameters:

  • val (String) (defaults to: NULL)

    name of the Software

Returns:

  • (String)

Raises:


150
151
152
153
154
155
156
# File 'lib/omnibus/software.rb', line 150

def name(val = NULL)
  if null?(val)
    @name || raise(MissingRequiredAttribute.new(self, :name, 'libxslt'))
  else
    @name = val
  end
end

#ohaiOhai

A proxy method to the underlying Ohai system.

Examples:

ohai['platform_family']

Returns:


593
594
595
# File 'lib/omnibus/software.rb', line 593

def ohai
  Ohai
end

#overridden?true, false

Determine if this software version overridden externally, relative to the version declared within the software DSL file?

Returns:

  • (true, false)

710
711
712
713
# File 'lib/omnibus/software.rb', line 710

def overridden?
  # NOTE: using instance variables to bypass accessors that enforce overrides
  @overrides.key?(:version) && (@overrides[:version] != @version)
end

#overridesHash

The repo-level and project-level overrides for the software.

Returns:

  • (Hash)

694
695
696
697
698
699
700
701
702
# File 'lib/omnibus/software.rb', line 694

def overrides
  if null?(@overrides)
    # lazily initialized because we need the 'name' to be parsed first
    @overrides = {}
    @overrides = project.overrides[name.to_sym].dup if project.overrides[name.to_sym]
  end

  @overrides
end

#prepend_path(*paths) ⇒ String

A PATH variable format string representing the current PATH with the given path prepended. The correct path separator for the platform is used to join the paths.

Parameters:

  • paths (Array<String>)

Returns:

  • (String)

576
577
578
579
580
581
582
# File 'lib/omnibus/software.rb', line 576

def prepend_path(*paths)
  path_values = Array(paths)
  path_values << ENV[path_key]

  separator = File::PATH_SEPARATOR || ':'
  path_values.join(separator)
end

#projectProject

The project that created this software.

Returns:


132
133
134
# File 'lib/omnibus/software.rb', line 132

def project
  @project
end

#project_dirString

The path where the extracted software lives.

Returns:

  • (String)

365
366
367
# File 'lib/omnibus/software.rb', line 365

def project_dir
  File.expand_path("#{Config.source_dir}/#{relative_path}")
end

#project_fileObject

Deprecated.

There is no replacement for this DSL method

The path to the downloaded file from a NetFetcher.


415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
# File 'lib/omnibus/software.rb', line 415

def project_file
  if fetcher && fetcher.is_a?(NetFetcher)
    log.deprecated(log_key) do
      "project_file (DSL). This is a property of the NetFetcher and will " \
      "not be publically exposed in the next major release. In general, " \
      "you should not be using this method in your software definitions " \
      "as it is an internal implementation detail of the NetFetcher. If " \
      "you disagree with this statement, you should open an issue on the " \
      "Omnibus repository on GitHub an explain your use case. For now, " \
      "I will return the path to the downloaded file on disk, but please " \
      "rethink the problem you are trying to solve :)."
    end

    fetcher.downloaded_file
  else
    log.warn(log_key) do
      "Cannot retrieve a `project_file' for software `#{name}'. This " \
      "attribute is actually an internal representation that is unique " \
      "to the NetFetcher class and requires the use of a `source' " \
      "attribute that is declared using a `:url' key. For backwards-" \
      "compatability, I will return `nil', but this is most likely not " \
      "your desired behavior."
    end

    nil
  end
end

#relative_path(val = NULL) ⇒ String

The relative path inside the extracted tarball.

Examples:

relative_path 'example-1.2.3'

Parameters:

  • val (String) (defaults to: NULL)

    the relative path inside the tarball

Returns:

  • (String)

351
352
353
354
355
356
357
# File 'lib/omnibus/software.rb', line 351

def relative_path(val = NULL)
  if null?(val)
    @relative_path || name
  else
    @relative_path = val
  end
end

#shasumString

The unique SHA256 for this sofware definition.

A software is defined by its parent project's shasum, its own name, its version_for_cache, and any overrides (as JSON). Additionally, if provided, the actual file contents are included in the SHA to ensure uniqueness.

Returns:

  • (String)

833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
# File 'lib/omnibus/software.rb', line 833

def shasum
  @shasum ||= begin
    digest = Digest::SHA256.new

    update_with_string(digest, project.shasum)
    update_with_string(digest, builder.shasum)
    update_with_string(digest, name)
    update_with_string(digest, version_for_cache)
    update_with_string(digest, JSON.fast_generate(overrides))

    if filepath && File.exist?(filepath)
      update_with_file_contents(digest, filepath)
    else
      update_with_string(digest, '<DYNAMIC>')
    end

    digest.hexdigest
  end
end

#source(val = NULL) ⇒ Hash

Set or retrieve the source for the software.

If multiple checksum types are provided, only the strongest will be used.

Examples:

source url: 'http://ftp.gnu.org/gnu/autoconf/autoconf-2.68.tar.gz',
       md5: 'c3b5247592ce694f7097873aa07d66fe'

Parameters:

  • val (Hash<Symbol, String>) (defaults to: NULL)

    a single key/pair that defines the kind of source and a path specifier

Options Hash (val):

  • :git (String) — default: nil

    a git URL

  • :url (String) — default: nil

    general URL

  • :path (String) — default: nil

    a fully-qualified local file system path

  • :md5 (String) — default: nil

    the MD5 checksum of the downloaded artifact

  • :sha1 (String) — default: nil

    the SHA1 checksum of the downloaded artifact

  • :sha256 (String) — default: nil

    the SHA256 checksum of the downloaded artifact

  • :sha512 (String) — default: nil

    the SHA512 checksum of the downloaded artifact

  • :cookie (String) — default: nil

    a cookie to set

  • :warning (String) — default: nil

    a warning message to print when downloading

  • :submodules (Boolean) — default: false

    clone git submodules

Returns:

  • (Hash)

Raises:

  • (InvalidValue)

    if the parameter is not a Hash

  • (InvalidValue)

    if the hash includes extraneous keys

  • (InvalidValue)

    if the hash declares keys that cannot work together (like :git and :path)


241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
# File 'lib/omnibus/software.rb', line 241

def source(val = NULL)
  unless null?(val)
    unless val.is_a?(Hash)
      raise InvalidValue.new(:source,
        "be a kind of `Hash', but was `#{val.class.inspect}'")
    end

    extra_keys = val.keys - [:git, :path, :url, :md5, :sha1, :sha256, :sha512,
                             :cookie, :warning, :unsafe, :options, :submodules]
    unless extra_keys.empty?
      raise InvalidValue.new(:source,
        "only include valid keys. Invalid keys: #{extra_keys.inspect}")
    end

    duplicate_keys = val.keys & [:git, :path, :url]
    unless duplicate_keys.size < 2
      raise InvalidValue.new(:source,
        "not include duplicate keys. Duplicate keys: #{duplicate_keys.inspect}")
    end

    @source ||= {}
    @source.merge!(val)
  end

  apply_overrides(:source)
end

#source_typeString

The type of source specified for this software defintion.

Returns:

  • (String)

755
756
757
758
759
760
761
762
763
764
765
766
767
# File 'lib/omnibus/software.rb', line 755

def source_type
  if source
    if source[:url]
      :url
    elsif source[:git]
      :git
    elsif source[:path]
      :path
    end
  else
    :project_local
  end
end

#to_manifest_entryObject


631
632
633
634
635
636
637
# File 'lib/omnibus/software.rb', line 631

def to_manifest_entry
  Omnibus::ManifestEntry.new(name, {
                               source_type: source_type,
                               described_version: version,
                               locked_version: Fetcher.resolve_version(version, source),
                               locked_source: source})
end

#version(val = NULL, &block) ⇒ String, Proc

Evaluate a block only if the version matches.

Examples:

version '1.2.3' do
  source path: '/local/path/to/software-1.2.3'
end

Parameters:

  • val (String) (defaults to: NULL)

    the version of the software

  • block (Proc)

    the block to run if the version we are building matches the argument

Returns:

  • (String, Proc)

305
306
307
308
309
310
311
312
313
314
315
316
317
318
# File 'lib/omnibus/software.rb', line 305

def version(val = NULL, &block)
  if block_given?
    if val.equal?(NULL)
      raise InvalidValue.new(:version,
        'pass a block when given a version argument')
    else
      if val == apply_overrides(:version)
        block.call
      end
    end
  end

  apply_overrides(:version)
end

#version_for_cacheObject

Returns the version to be used in cache.


725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
# File 'lib/omnibus/software.rb', line 725

def version_for_cache
  @version_for_cache ||= if fetcher.version_for_cache
    fetcher.version_for_cache
  elsif version
    version
  else
    log.warn(log_key) do
      "No version given! This is probably a bad thing. I am going to " \
      "assume the version `0.0.0', but that is most certainly not your " \
      "desired behavior. If git caching seems off, this is probably why."
    end

    '0.0.0'
  end
end

#version_guidObject


720
721
722
# File 'lib/omnibus/software.rb', line 720

def version_guid
  fetcher.version_guid
end

#whitelist_file(file) ⇒ Array<String>

Add a file to the healthcheck whitelist.

Examples:

whitelist_file '/path/to/file'

Parameters:

  • file (String, Regexp)

    the name of a file to ignore in the healthcheck

Returns:

  • (Array<String>)

    the list of currently whitelisted files


333
334
335
336
337
# File 'lib/omnibus/software.rb', line 333

def whitelist_file(file)
  file = Regexp.new(file) unless file.kind_of?(Regexp)
  whitelist_files << file
  whitelist_files.dup
end

#whitelist_filesArray<String>

The list of files to ignore in the healthcheck.

Returns:

  • (Array<String>)

675
676
677
# File 'lib/omnibus/software.rb', line 675

def whitelist_files
  @whitelist_files ||= []
end

#with_embedded_path(env = {}) ⇒ Hash

A PATH variable format string representing the current PATH with the project's embedded/bin directory prepended. The correct path separator for the platform is used to join the paths.

Parameters:

  • env (Hash) (defaults to: {})

Returns:

  • (Hash)

561
562
563
564
# File 'lib/omnibus/software.rb', line 561

def with_embedded_path(env = {})
  path_value = prepend_path("#{install_dir}/bin", "#{install_dir}/embedded/bin")
  env.merge(path_key => path_value)
end

#with_standard_compiler_flags(env = {}, opts = {}) ⇒ Hash

Add standard compiler flags to the environment hash to produce omnibus binaries (correct RPATH, etc).

Supported options:

:aix => :use_gcc    force using gcc/g++ compilers on aix

Parameters:

  • env (Hash) (defaults to: {})
  • opts (Hash) (defaults to: {})

Returns:

  • (Hash)

456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
# File 'lib/omnibus/software.rb', line 456

def with_standard_compiler_flags(env = {}, opts = {})
  env ||= {}
  opts ||= {}
  compiler_flags =
    case Ohai['platform']
    when "aix"
      {
        "CC" => "xlc_r -q64",
        "CXX" => "xlC_r -q64",
        "CFLAGS" => "-q64 -I#{install_dir}/embedded/include -D_LARGE_FILES -O",
        "LDFLAGS" => "-q64 -L#{install_dir}/embedded/lib -Wl,-blibpath:#{install_dir}/embedded/lib:/usr/lib:/lib",
        "LD" => "ld -b64",
        "OBJECT_MODE" => "64",
        "ARFLAGS" => "-X64 cru",
      }
    when "mac_os_x"
      {
        "LDFLAGS" => "-L#{install_dir}/embedded/lib",
        "CFLAGS" => "-I#{install_dir}/embedded/include",
      }
    when "solaris2"
      {
        # this override is due to a bug in libtool documented here:
        # http://lists.gnu.org/archive/html/bug-libtool/2005-10/msg00004.html
        "CC" => "gcc -static-libgcc",
        "LDFLAGS" => "-R#{install_dir}/embedded/lib -L#{install_dir}/embedded/lib -static-libgcc",
        "CFLAGS" => "-I#{install_dir}/embedded/include",
      }
    when "freebsd"
      freebsd_flags = {
        "LDFLAGS" => "-L#{install_dir}/embedded/lib",
        "CFLAGS" => "-I#{install_dir}/embedded/include",
      }
      # Clang became the default compiler in FreeBSD 10+
      if Ohai['os_version'].to_i >= 1000024
        freebsd_flags.merge!(
          "CC" => "clang",
          "CXX" => "clang++",
        )
      end
      freebsd_flags
    when "windows"
      {
        "LDFLAGS" => "-L#{install_dir}/embedded/lib",
        "CFLAGS" => "-I#{install_dir}/embedded/include",
      }
    else
      {
        "LDFLAGS" => "-Wl,-rpath,#{install_dir}/embedded/lib -L#{install_dir}/embedded/lib",
        "CFLAGS" => "-I#{install_dir}/embedded/include",
      }
    end

  # merge LD_RUN_PATH into the environment.  most unix distros will fall
  # back to this if there is no LDFLAGS passed to the linker that sets
  # the rpath.  the LDFLAGS -R or -Wl,-rpath will override this, but in
  # some cases software may drop our LDFLAGS or think it knows better
  # and edit them, and we *really* want the rpath setting and do know
  # better.  in that case LD_RUN_PATH will probably survive whatever
  # edits the configure script does
  extra_linker_flags = {
    "LD_RUN_PATH" => "#{install_dir}/embedded/lib"
  }

  if solaris2?
    # in order to provide compatibility for earlier versions of libc on solaris 10,
    # we need to specify a mapfile that restricts the version of system libraries
    # used. See http://docs.oracle.com/cd/E23824_01/html/819-0690/chapter5-1.html
    # for more information
    # use the mapfile if it exists, otherwise ignore it
    ld_options = "-R#{install_dir}/embedded/lib"
    mapfile_path = File.expand_path(Config.solaris_linker_mapfile, Config.project_root)
    ld_options  << " -M #{mapfile_path}" if File.exist?(mapfile_path)

    # solaris linker can also use LD_OPTIONS, so we throw the kitchen sink against
    # the linker, to find every way to make it use our rpath. This is also required
    # to use the aforementioned mapfile.
    extra_linker_flags.merge!(
      {
        "LD_OPTIONS" => ld_options
      }
    )
  end

  env.merge(compiler_flags).
    merge(extra_linker_flags).
    # always want to favor pkg-config from embedded location to not hose
    # configure scripts which try to be too clever and ignore our explicit
    # CFLAGS and LDFLAGS in favor of pkg-config info
    merge({"PKG_CONFIG_PATH" => "#{install_dir}/embedded/lib/pkgconfig"}).
    # Set default values for CXXFLAGS and CPPFLAGS.
    merge('CXXFLAGS' => compiler_flags['CFLAGS']).
    merge('CPPFLAGS' => compiler_flags['CFLAGS'])
end