Class: Babushka::Dep

Inherits:
Object show all
Extended by:
LogHelpers, SuggestHelpers
Includes:
LogHelpers, PathHelpers
Defined in:
lib/babushka/dep.rb

Defined Under Namespace

Classes: BaseTemplate, Requirement

Instance Attribute Summary (collapse)

Class Method Summary (collapse)

Instance Method Summary (collapse)

Methods included from SuggestHelpers

suggest_value_for

Methods included from LogHelpers

debug, log, log_block, log_error, log_ok, log_verbose, log_warn

Methods included from PathHelpers

#cd, #in_build_dir, #in_dir, #in_download_dir

Constructor Details

- (Dep) initialize(name, source, params, opts, block)

Create a new dep named name within source, whose implementation is found in block. To define deps yourself, you should call dep (which is Dep::Helpers#dep).



42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
# File 'lib/babushka/dep.rb', line 42

def initialize name, source, params, opts, block
  if name.empty?
    raise InvalidDepName, "Deps can't have empty names."
  elsif /\A[[:print:]]+\z/i !~ name
    raise InvalidDepName, "The dep name '#{name}' contains nonprintable characters."
  elsif /\// =~ name
    raise InvalidDepName, "The dep name '#{name}' contains '/', which isn't allowed (logs are named after deps, and filenames can't contain '/')."
  elsif /\:/ =~ name
    raise InvalidDepName, "The dep name '#{name}' contains ':', which isn't allowed (colons separate dep and template names from source prefixes)."
  elsif !params.all? {|param| param.is_a?(Symbol) }
    non_symbol_params = params.reject {|p| p.is_a?(Symbol) }
    raise DepParameterError, "The dep '#{name}' has #{'a ' if non_symbol_params.length == 1}non-symbol param#{'s' if non_symbol_params.length > 1} #{non_symbol_params.map(&:inspect).to_list}, which #{non_symbol_params.length == 1 ? "isn't" : "aren't"} allowed."
  else
    @name = name.to_s
    @params = params
    @args = {}
    @opts = Base.sources.current_load_opts.merge(opts)
    @block = block
    @dep_source = source
    @load_path = Base.sources.current_load_path
    @dep_source.deps.register self
    assign_template if Base.sources.current_real_load_source.nil?
    @dep_defined = @_cached_process = nil # false represents failure for these two.
  end
end

Instance Attribute Details

- (Object) args (readonly)

Returns the value of attribute args



36
37
38
# File 'lib/babushka/dep.rb', line 36

def args
  @args
end

- (Object) dep_source (readonly)

Returns the value of attribute dep_source



36
37
38
# File 'lib/babushka/dep.rb', line 36

def dep_source
  @dep_source
end

- (Object) load_path (readonly)

Returns the value of attribute load_path



36
37
38
# File 'lib/babushka/dep.rb', line 36

def load_path
  @load_path
end

- (Object) name (readonly)

Returns the value of attribute name



36
37
38
# File 'lib/babushka/dep.rb', line 36

def name
  @name
end

- (Object) opts (readonly)

Returns the value of attribute opts



36
37
38
# File 'lib/babushka/dep.rb', line 36

def opts
  @opts
end

- (Object) params (readonly)

Returns the value of attribute params



36
37
38
# File 'lib/babushka/dep.rb', line 36

def params
  @params
end

- (Object) result_message

Returns the value of attribute result_message



37
38
39
# File 'lib/babushka/dep.rb', line 37

def result_message
  @result_message
end

- (Object) vars (readonly)

Returns the value of attribute vars



36
37
38
# File 'lib/babushka/dep.rb', line 36

def vars
  @vars
end

Class Method Details

+ (Object) find_or_suggest(dep_name, opts = {}, &block)

Look up the dep specified by dep_name, yielding it to the block if it was found.

If no such dep exists, search for other similarly spelt deps and re-call this same method on the one chosen by the user, if any.



88
89
90
91
92
93
94
95
96
97
98
# File 'lib/babushka/dep.rb', line 88

def self.find_or_suggest dep_name, opts = {}, &block
  if (dep = Dep(dep_name, opts)).nil?
    log "#{dep_name.to_s.colorize 'grey'} #{"<- this dep isn't defined!".colorize('red')}"
    suggestion = suggest_value_for(dep_name, Base.sources.current_names)
    Dep.find_or_suggest suggestion, opts, &block unless suggestion.nil?
  elsif block.nil?
    dep
  else
    block.call dep
  end
end

Instance Method Details

- (Object) basename

Return this dep's name, first removing the template suffix if one is present.

Note that this only removes the suffix when it was used to define the dep. Dep names that end in something that looks like a template suffix, but didn't match a template and result in a templated dep, won't be touched.

Some examples:

Dep('benhoskings:Chromium.app').basename #=> 'Chromium'
Dep('generated report.pdf').basename     #=> "generated report.pdf"


124
125
126
# File 'lib/babushka/dep.rb', line 124

def basename
  suffixed? ? name.sub(/\.#{Regexp.escape(template.name)}$/, '') : name
end

- (Object) context



68
69
70
71
# File 'lib/babushka/dep.rb', line 68

def context
  define! if @context.nil?
  @context
end

- (Object) contextual_name

Returns this dep's name, including the source name as a prefix if this dep is in a cloneable source.

A cloneable source is one that babushka knows how to automatically update; i.e. a source that babushka could have installed itself.

In effect, a cloneable source is one whose deps you prefix when you run them, so this method returns the dep's name in the same form as you would refer to it on the commandline or within a require call in another dep.



109
110
111
# File 'lib/babushka/dep.rb', line 109

def contextual_name
  dep_source.cloneable? ? "#{dep_source.name}:#{name}" : name
end

- (Object) defined_info



448
449
450
451
452
453
454
# File 'lib/babushka/dep.rb', line 448

def defined_info
  if dep_defined?
    "#{"(#{'un' unless cached_process}met) " if cached?}<- [#{context.requires.join(', ')}]"
  else
    "(not defined yet)"
  end
end

- (Boolean) dep_defined?

Returns true if #define! has aready successfully run on this dep.

Returns:

  • (Boolean)


79
80
81
# File 'lib/babushka/dep.rb', line 79

def dep_defined?
  @dep_defined
end

- (Object) inspect



444
445
446
# File 'lib/babushka/dep.rb', line 444

def inspect
  "#<Dep:#{object_id} #{"#{dep_source.name}:" unless dep_source.nil?}'#{name}' #{defined_info}>"
end

- (Object) meet(*args)

Entry point for a full met?/meet #process run.



160
161
162
# File 'lib/babushka/dep.rb', line 160

def meet *args
  with(*args).process :dry_run => false, :top_level => true
end

- (Boolean) met?(*args)

Entry point for a dry #process run, where only met? blocks will be evaluated.

This is useful to inspect the current state of a dep tree, without altering the system. It can cause failures, though, because some deps have requirements that need to be met before the dep can perform its met? check.

TODO: In future, there will be support for specifying that in the DSL.

Returns:

  • (Boolean)


155
156
157
# File 'lib/babushka/dep.rb', line 155

def met? *args
  with(*args).process :dry_run => true, :top_level => true
end

- (Object) process(with_opts = {})

Trigger a dep run with this dep at the top of the tree.

Running the dep involves the following:

  • First, the setup block is run.

  • Next, the dep's dependencies (i.e. the contents of requires) are run recursively by calling #process on each; this dep's #process early-exits if any of the subdeps fail.

  • Next, the met? block is run. If met? returns true, or any true-like value, the dep is already met and there is nothing to do. Otherwise, the dep is unmet, and the following happens:

    - The +prepare+ task is run
    - The +before+ task is run
    - If +before+ returned a true-like value, the +meet+ task is run.
      This is where the actual work of achieving the dep's aim is done.
    - If +meet+ returned a true-like value, the +after+ task is run.
    - Finally, the +met?+ task is run again, to check whether running
      +meet+ has achieved the dep's goal.

The final step is important to understand. The meet block is run unconditionally, and its return value is ignored, apart from it determining whether to run the after block. The result of a dep is always taken from its met? block, whether it was already met, unmeetable, or met during the run.

Sometimes there are conditions under which a dep can't be met. For example, if a dep detects that the existing version of a package is broken in some way that requires manual intervention, then there's no use running the meet block. In this circumstance, you can call #unmeetable, which raises an UnmeetableDep exception. Babushka will rescue it and consider the dep unmeetable (that is, it will just allow the dep to fail without attempting to meet it).

The following describes the return values of a few components, and of the dep itself.

  • A '-' means the corresponding block wouldn't be run at all.

  • An 'X' means the corresponding return value doesn't matter, and is discarded.

    Initial state   | initial met?         | meet  | subsequent met? | dep returns
    ----------------+----------------------+-------+-----------------+------------
    already met     | true                 | -     | -               | true
    unmeetable      | UnmeetableDep raised | -     | -               | false
    couldn't be met | false                | X     | false           | false
    met during run  | false                | X     | true            | true

Wherever possible, the met? test shouldn't directly test that the meet block performed specific tasks; only that its overall purpose has been achieved. For example, if the purpose of a given dep is to make sure the webserver is running, the contents of the meet block would probably involve `/etc/init.d/nginx start` or similar, on a Linux system at least. In this case, the met? block shouldn't test anything involving `/etc/init.d` directly; instead, it should separately test that the webserver is running, for example by using `netstat` to check that something is listening on port 80.



218
219
220
221
222
223
# File 'lib/babushka/dep.rb', line 218

def process with_opts = {}
  task.opts.update with_opts
  (cached? ? cached_result : process_and_cache).tap {
    Base.sources.uncache! if with_opts[:top_level]
  }
end

- (Object) suffix

Returns the portion of the end of the dep name that looks like a template suffix, if any. Unlike #basename, this method will return anything that looks like a template suffix, even if it doesn't match a template.



131
132
133
# File 'lib/babushka/dep.rb', line 131

def suffix
  name.scan(MetaDep::TEMPLATE_NAME_MATCH).flatten.first
end

- (Object) template



73
74
75
76
# File 'lib/babushka/dep.rb', line 73

def template
  assign_template if @template.nil?
  @template
end

- (Object) with(*args)



135
136
137
138
139
140
141
142
143
144
# File 'lib/babushka/dep.rb', line 135

def with *args
  @args = if args.map(&:class) == [Hash]
    parse_named_arguments(args.first)
  else
    parse_positional_arguments(args)
  end.map_values {|k,v|
    Parameter.for(k, v)
  }
  self
end