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

DSL methods collapse

Public API collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Sugarable

included, #node

Methods included from NullArgumentable

included, #null?

Methods included from Logging

included

Methods included from Digestable

#digest, #digest_directory

Constructor Details

#initialize(project, filepath = nil) ⇒ Software

Create a new software object.


82
83
84
85
86
87
88
89
90
91
92
93
94
# File 'lib/omnibus/software.rb', line 82

def initialize(project, filepath = 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

  # Overrides
  @overrides = NULL
end

Class Method Details

.load(project, name) ⇒ Software


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

def load(project, name)
  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)
    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).


101
102
103
# File 'lib/omnibus/software.rb', line 101

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

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

Determine if two softwares are identical.


759
760
761
# File 'lib/omnibus/software.rb', line 759

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

#build(&block) ⇒ Proc

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

See Also:


377
378
379
# File 'lib/omnibus/software.rb', line 377

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

#build_dirString

The path where the software will be built.


347
348
349
# File 'lib/omnibus/software.rb', line 347

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.


718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
# File 'lib/omnibus/software.rb', line 718

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.


580
581
582
# File 'lib/omnibus/software.rb', line 580

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

#default_version(val = NULL) ⇒ String

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

Examples:

default_version '1.2.3'

253
254
255
256
257
258
259
# File 'lib/omnibus/software.rb', line 253

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.

See Also:


611
612
613
# File 'lib/omnibus/software.rb', line 611

def dependencies
  @dependencies ||= []
end

#dependency(val) ⇒ Array<String>

Add a software dependency to this software.

Examples:

dependency 'libxml2'
dependency 'libpng'

176
177
178
179
# File 'lib/omnibus/software.rb', line 176

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

#description(val = NULL) ⇒ String

Sets the description of the software.

Examples:

description 'Installs libxslt'

154
155
156
157
158
159
160
# File 'lib/omnibus/software.rb', line 154

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)


592
593
594
595
596
597
598
599
# File 'lib/omnibus/software.rb', line 592

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

#fetcherFetcher

The fetcher for this software, based off of the source attribute.


695
696
697
698
699
700
701
702
703
704
705
706
707
# File 'lib/omnibus/software.rb', line 695

def fetcher
  @fetcher ||= if source
    if source[:url]
      NetFetcher.new(self)
    elsif source[:git]
      GitFetcher.new(self)
    elsif source[:path]
      PathFetcher.new(self)
    end
  else
    NullFetcher.new(self)
  end
end

#filepathString?

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


630
631
632
# File 'lib/omnibus/software.rb', line 630

def filepath
  @filepath
end

#hashFixnum

The unique “hash” for this software.

See Also:

  • Omnibus::Software.((#shasum)

748
749
750
# File 'lib/omnibus/software.rb', line 748

def hash
  shasum.hash
end

#install_dirString

The directory where this software is installed on disk.

Examples:

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

360
361
362
# File 'lib/omnibus/software.rb', line 360

def install_dir
  @project.install_dir
end

#load_dependenciestrue

Recursively load all the dependencies for this software.


567
568
569
570
571
572
573
# File 'lib/omnibus/software.rb', line 567

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

  true
end

#name(val = NULL) ⇒ String

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

Examples:

name 'libxslt'

Raises:


134
135
136
137
138
139
140
# File 'lib/omnibus/software.rb', line 134

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']

546
547
548
# File 'lib/omnibus/software.rb', line 546

def ohai
  Ohai
end

#overridden?true, false

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


655
656
657
658
# File 'lib/omnibus/software.rb', line 655

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.


639
640
641
642
643
644
645
646
647
# File 'lib/omnibus/software.rb', line 639

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.


529
530
531
532
533
534
535
# File 'lib/omnibus/software.rb', line 529

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.


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

def project
  @project
end

#project_dirString

The path where the extracted software lives.


337
338
339
# File 'lib/omnibus/software.rb', line 337

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.


387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
# File 'lib/omnibus/software.rb', line 387

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'

323
324
325
326
327
328
329
# File 'lib/omnibus/software.rb', line 323

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.


773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
# File 'lib/omnibus/software.rb', line 773

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.

Examples:

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

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 checksum of the downloaded artifact

  • :cookie (String) — default: nil

    a cookie to set

  • :warning (String) — default: nil

    a warning message to print when downloading

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)


215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
# File 'lib/omnibus/software.rb', line 215

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, :cookie, :warning, :unsafe]
    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

#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

277
278
279
280
281
282
283
284
285
286
287
288
289
290
# File 'lib/omnibus/software.rb', line 277

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.


670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
# File 'lib/omnibus/software.rb', line 670

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


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

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'

305
306
307
308
309
# File 'lib/omnibus/software.rb', line 305

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.


620
621
622
# File 'lib/omnibus/software.rb', line 620

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.


514
515
516
517
# File 'lib/omnibus/software.rb', line 514

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

428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
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
# File 'lib/omnibus/software.rb', line 428

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",
        "CXXFLAGS" => "-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
    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"
  }
  # 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.
  extra_linker_flags.merge!(
    {
      "LD_OPTIONS" => "-R#{install_dir}/embedded/lib"
    }
  ) if Ohai['platform'] == "solaris2"
  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.
    merge('CXXFLAGS' => compiler_flags['CFLAGS'])
end