class Fig::RuntimeEnvironment

Manages the program's metadata, including packages and environment variables, and sets things up for running commands (from “command” statements in definition files or from the command-line).

Public Class Methods

new( repository, non_repository_packages, suppress_includes, variables_override, working_directory_maintainer ) click to toggle source

Note: when reading this code, understand that the word “retrieve” is a noun and not a verb, e.g. “retrieve path” means the value of a retrieve statement and not the action of retrieving a path.

# File lib/fig/runtime_environment.rb, line 29
def initialize(
  repository,
  non_repository_packages,
  suppress_includes,
  variables_override,
  working_directory_maintainer
)
  @repository                   = repository
  @non_repository_packages      = non_repository_packages
  @suppress_includes            = suppress_includes
  @variables                    =
    variables_override || Fig::OperatingSystem.get_environment_variables()
  @retrieves                    = {}
  @named_packages               = {}
  @working_directory_maintainer = working_directory_maintainer
end

Public Instance Methods

[](name) click to toggle source

Returns the value of an environment variable

# File lib/fig/runtime_environment.rb, line 47
def [](name)
  return @variables[name]
end
add_retrieve(retrieve_statement) click to toggle source

Indicates that the values from a particular environment variable path should be copied to a local directory.

# File lib/fig/runtime_environment.rb, line 57
def add_retrieve(retrieve_statement)
  name = retrieve_statement.variable
  if @retrieves.has_key?(name)
    Fig::Logging.warn \
      %Q<About to overwrite "#{name}" retrieve path of "#{@retrieves[name].path}" with "#{retrieve_statement.path}".>
  end

  @retrieves[name] = retrieve_statement
  retrieve_statement.added_to_environment(true)

  return
end
apply_config(package, config_name, backtrace) click to toggle source
# File lib/fig/runtime_environment.rb, line 92
def apply_config(package, config_name, backtrace)
  if package.applied_config_names.member?(config_name)
    return
  end

  Fig::Logging.debug(
    "Applying #{package.to_descriptive_string_with_config config_name}."
  )

  new_backtrace = backtrace ||
    Fig::IncludeBacktrace.new(
      nil,
      Fig::PackageDescriptor.new(
        package.name,
        package.version,
        config_name,
        :file_path   => package.file_path,
        :description => package.description
      )
    )

  config = nil
  begin
    config = package[config_name]
  rescue Fig::NoSuchPackageConfigError => error
    raise_repository_error(error.message, new_backtrace, error.package)
  end

  package.add_applied_config_name(config_name)
  config.statements.each do
    |statement|
    apply_config_statement(package, statement, new_backtrace)
  end

  return
end
apply_config_statement(package, statement, backtrace) click to toggle source

In order for this to work correctly, any Overrides need to be processed before any other kind of Statement. The Statement::Configuration class guarantees that those come first in its set of Statements.

# File lib/fig/runtime_environment.rb, line 164
def apply_config_statement(package, statement, backtrace)
  case statement
  when Fig::Statement::Path
    prepend_variable(package, statement, backtrace)
  when Fig::Statement::Set
    set_variable(package, statement, backtrace)
  when Fig::Statement::Include
    include_config(package, statement, backtrace)
  when Fig::Statement::IncludeFile
    include_file_config(package, statement, backtrace)
  when Fig::Statement::Override
    backtrace.add_override(statement)
  end

  return
end
check_for_unused_retrieves() click to toggle source
# File lib/fig/runtime_environment.rb, line 181
def check_for_unused_retrieves()
  @retrieves.keys().sort().each do
    |name|

    statement = @retrieves[name]
    if statement.loaded_but_not_referenced?
      text, * = Fig::Deparser.determine_version_and_deparse(
        [statement], :emit_as_input
      )
      Fig::Logging.warn \
        %Q<The #{name} variable was never referenced or didn't need expansion, so "#{text.strip}"#{statement.position_string} was ignored.>
    end
  end
end
expand_command_line(base_package, base_config, descriptor, command_line) { |expanded_command_line| ... } click to toggle source
# File lib/fig/runtime_environment.rb, line 129
def expand_command_line(base_package, base_config, descriptor, command_line)
  package, * =
    determine_package_for_execution(base_package, base_config, descriptor)

  expanded_command_line =
    command_line.map {
      |argument| expand_command_line_argument(argument, package)
    }

  @variables.with_environment { yield expanded_command_line }

  return
end
expand_command_statement_from_config( base_package, base_config, descriptor, extra_arguments, &block ) click to toggle source
# File lib/fig/runtime_environment.rb, line 143
def expand_command_statement_from_config(
  base_package, base_config, descriptor, extra_arguments, &block
)
  package, config_name =
    determine_package_for_execution(base_package, base_config, descriptor)

  command_statement = package[config_name].command_statement
  if command_statement
    expand_command(command_statement, extra_arguments, package, &block)
  else
    raise Fig::UserInputError.new(
      %Q<The "#{package.to_s}" package with the "#{config_name}" configuration does not contain a command.>
    )
  end

  return
end
get_package(name) click to toggle source
# File lib/fig/runtime_environment.rb, line 88
def get_package(name)
  return @named_packages[name]
end
register_package(package) click to toggle source
# File lib/fig/runtime_environment.rb, line 70
def register_package(package)
  name = package.name

  if get_package(name)
    raise_repository_error(
      name.nil? \
        ? %Q<There is already a package with the name "#{name}".> \
        : %q<There is already an unnamed package.>,
      nil,
      package
    )
  end

  @named_packages[name] = package

  return
end
variables() click to toggle source
# File lib/fig/runtime_environment.rb, line 51
def variables
  return @variables.clone
end

Private Instance Methods

check_source_existence( variable_name, variable_value, package, backtrace ) click to toggle source
# File lib/fig/runtime_environment.rb, line 479
def check_source_existence(
  variable_name, variable_value, package, backtrace
)
  return if File.exists?(variable_value) || File.symlink?(variable_value)

  raise_repository_error(
    %Q<In #{package}, the #{variable_name} variable points to a path that does not exist ("#{variable_value}", after expansion).>,
    backtrace,
    package
  )
end
derive_retrieve_destination(variable_name, variable_value, package) click to toggle source
# File lib/fig/runtime_environment.rb, line 491
def derive_retrieve_destination(variable_name, variable_value, package)
  retrieve_path =
    get_retrieve_path_with_substitution(variable_name, package)

  # A '//' in the variable value tells us to preserve path
  # information after the '//' when doing a retrieve.
  if variable_value.include? '//'
    preserved_path = variable_value.split('//', -1).last

    return File.join(retrieve_path, preserved_path)
  end

  if File.directory?(variable_value)
    return retrieve_path
  end

  return File.join(retrieve_path, File.basename(variable_value))
end
determine_config_to_executed(base_package, base_config, descriptor) click to toggle source
# File lib/fig/runtime_environment.rb, line 395
def determine_config_to_executed(base_package, base_config, descriptor)
  return base_config if base_config

  if descriptor
    return descriptor.config if descriptor.config

    config_name = find_config_name_in_package_named(descriptor.name)
    return config_name if config_name
  end

  return find_config_name_in_package(base_package)
end
determine_included_package(starting_package, include_statement, backtrace) click to toggle source
# File lib/fig/runtime_environment.rb, line 223
def determine_included_package(starting_package, include_statement, backtrace)
  descriptor = include_statement.descriptor

  if ! include_statement.included_package.nil?
    return \
      include_statement.included_package,
      descriptor,
      Fig::IncludeBacktrace.new(backtrace, descriptor)
  end

  resolved_descriptor = nil

  # Check to see if this include has been overridden.
  if (
    backtrace and
    override_package_name = descriptor.name || starting_package.name
  )
    override = backtrace.get_override(override_package_name)
    if override
      resolved_descriptor =
        Fig::PackageDescriptor.new(
          override_package_name, override, descriptor.config
        )
    end
  end
  resolved_descriptor ||= descriptor

  new_backtrace = Fig::IncludeBacktrace.new(backtrace, resolved_descriptor)
  package = nil

  if included_name = resolved_descriptor.name || starting_package.name
    package = lookup_package(
      included_name, resolved_descriptor.version, new_backtrace
    )
  else
    package = starting_package
  end

  return package, resolved_descriptor, new_backtrace
end
determine_package_for_execution(base_package, base_config, descriptor) click to toggle source
# File lib/fig/runtime_environment.rb, line 366
def determine_package_for_execution(base_package, base_config, descriptor)
  config_name =
    determine_config_to_executed(base_package, base_config, descriptor)

  package = nil

  if descriptor
    package_name = descriptor.name || base_package.name
    package      = lookup_package(
      package_name,
      descriptor.version,
      Fig::IncludeBacktrace.new(
        nil,
        Fig::PackageDescriptor.new(
          package_name,
          descriptor.version,
          config_name,
          :file_path   => descriptor.file_path,
          :description => descriptor.description
        )
      )
    )
  else
    package = base_package
  end

  return [package, config_name]
end
expand_command(command_statement, extra_arguments, package) { |expanded_command_line| ... } click to toggle source
# File lib/fig/runtime_environment.rb, line 421
def expand_command(command_statement, extra_arguments, package)
  expanded_command_line =
    [ command_statement.command, extra_arguments ].flatten.map {
      |argument| expand_command_line_argument(argument, package)
    }

  if command_statement.command.size == 1
    expanded_command_line = [ expanded_command_line.join(' ') ]
  end

  @variables.with_environment { yield expanded_command_line }

  return
end
expand_command_line_argument(argument, starting_package) click to toggle source
# File lib/fig/runtime_environment.rb, line 510
def expand_command_line_argument(argument, starting_package)
  return argument.to_expanded_string() do
    |token|

    package_name = token.raw_value[1..-1]
    package = nil
    if package_name.empty?
      package = starting_package
    else
      package = get_package(package_name)
      if package.nil?
        raise_repository_error(
          %Q<Command referenced the "#{package_name}" package, which has not been referenced by any other package, so there's nothing to substitute with.>,
          nil,
          nil
        )
      end
    end

    if package && package.runtime_directory
      next package.runtime_directory
    end

    next '@'
  end
end
expand_variable_as_path_and_process_retrieves( statement, package, backtrace ) click to toggle source
# File lib/fig/runtime_environment.rb, line 436
def expand_variable_as_path_and_process_retrieves(
  statement, package, backtrace
)
  tokenized_value = statement.tokenized_value
  return tokenized_value.to_expanded_string { '@' } \
    unless package && (package.name || ! (package.synthetic? || package.base?))

  variable_value =
    tokenized_value.to_expanded_string { package.runtime_directory }

  return variable_value if not @retrieves.member?(statement.name)

  if ! package.name
    Fig::Logging.warn \
      "Retrieve of #{statement.name}=#{variable_value} ignored because the statement#{statement.position_string} is in an unnamed package."

    return variable_value
  end

  return retrieve_files(
    statement.name, variable_value, package, backtrace
  )
end
find_config_name_in_package(package) click to toggle source
# File lib/fig/runtime_environment.rb, line 417
def find_config_name_in_package(package)
  return package.primary_config_name || Fig::Package::DEFAULT_CONFIG
end
find_config_name_in_package_named(name) click to toggle source
# File lib/fig/runtime_environment.rb, line 408
def find_config_name_in_package_named(name)
  package = get_package(name)
  if not package
    return Fig::Package::DEFAULT_CONFIG
  end

  return find_config_name_in_package(package)
end
get_retrieve_path_with_substitution(variable_name, package) click to toggle source
# File lib/fig/runtime_environment.rb, line 537
def get_retrieve_path_with_substitution(variable_name, package)
  retrieve_statement = @retrieves[variable_name]
  retrieve_statement.referenced(true)

  return retrieve_statement.tokenized_path.to_expanded_string() do
    |token|

    package.name
  end
end
include_config(starting_package, include_statement, backtrace) click to toggle source
# File lib/fig/runtime_environment.rb, line 198
def include_config(starting_package, include_statement, backtrace)
  # Because package application starts with the synthetic package for the
  # command-line, we can't really disable includes, full stop.  Instead, we
  # use the fact that the synthetic package hands the Statement::Include the
  # base Package object instead of a descriptor.
  return if
    include_statement.included_package.nil? && @suppress_includes == :all

  package, resolved_descriptor, new_backtrace =
    determine_included_package starting_package, include_statement, backtrace

  return if
        @suppress_includes == :cross_package \
    &&  package != starting_package          \
    &&  package != include_statement.included_package

  apply_config(
    package,
    resolved_descriptor.config || Fig::Package::DEFAULT_CONFIG,
    new_backtrace
  )

  return
end
include_file_config(including_package, include_file_statement, backtrace) click to toggle source
# File lib/fig/runtime_environment.rb, line 264
def include_file_config(including_package, include_file_statement, backtrace)
  return if @suppress_includes

  full_path = include_file_statement.full_path_relative_to including_package

  descriptor =
    Fig::PackageDescriptor.new(nil, nil, nil, :file_path => full_path)

  new_backtrace = Fig::IncludeBacktrace.new(backtrace, descriptor)
  package       = package_for_file(including_package, full_path, backtrace)
  config_name   = include_file_statement.config_name

  apply_config(
    package, config_name || Fig::Package::DEFAULT_CONFIG, new_backtrace
  )

  return
end
lookup_package(name, version, backtrace) click to toggle source
# File lib/fig/runtime_environment.rb, line 329
def lookup_package(name, version, backtrace)
  package = get_package(name)
  if package.nil?
    if not version
      raise_repository_error(
        "No version specified for #{name}.", backtrace, package
      )
    end

    package = @repository.get_package(
      Fig::PackageDescriptor.new(name, version, nil)
    )
    package.backtrace = backtrace
    @named_packages[name] = package
  elsif version && version != package.version
    raise_repository_error(
      "Version mismatch for package #{name} (#{version} vs #{package.version}).",
      backtrace,
      package
    )
  end

  return package
end
package_for_file(including_package, full_path, backtrace) click to toggle source
# File lib/fig/runtime_environment.rb, line 354
def package_for_file(including_package, full_path, backtrace)
  if package = @non_repository_packages[full_path]
    package.backtrace = backtrace

    return package
  end

  raise_repository_error(
    %Q<"#{full_path}" does not exist.>, backtrace, including_package
  )
end
prepend_variable(package, statement, backtrace) click to toggle source
# File lib/fig/runtime_environment.rb, line 306
def prepend_variable(package, statement, backtrace)
  expanded_value = expand_variable_as_path_and_process_retrieves(
    statement, package, backtrace
  )
  name = statement.name
  @variables.prepend_variable(name, expanded_value)

  if Fig::Logging.debug?
    tokenized_value = statement.tokenized_value
    escaped_value = tokenized_value.to_escaped_string
    expanded_message =
      expanded_value == escaped_value \
        ? ''  \
        : %Q< ("#{escaped_value}" expanded to "#{expanded_value}")>

    Fig::Logging.debug(
      %Q<Prepending to #{name} resulted in "#{@variables[name]}"#{expanded_message}.>
    )
  end

  return
end
raise_repository_error(message, backtrace, package) click to toggle source
# File lib/fig/runtime_environment.rb, line 548
def raise_repository_error(message, backtrace, package)
  string_handle = StringIO.new
  backtrace.dump(string_handle) if backtrace

  if package && package.backtrace && package.backtrace != backtrace
    package.backtrace.dump(string_handle)
  end

  stacktrace = string_handle.string

  raise Fig::RepositoryError.new(
      message + ( stacktrace.empty? ? '' : "\n#{stacktrace}" )
  )
end
retrieve_files(variable_name, variable_value, package, backtrace) click to toggle source
# File lib/fig/runtime_environment.rb, line 460
def retrieve_files(variable_name, variable_value, package, backtrace)
  destination_path =
    derive_retrieve_destination(variable_name, variable_value, package)

  # Check this *after* determining destination so that
  # derive_retrieve_destination() can mark retrieve statements as being
  # referenced.
  check_source_existence(
    variable_name, variable_value, package, backtrace
  )

  @working_directory_maintainer.switch_to_package_version(
    package.name, package.version
  )
  @working_directory_maintainer.retrieve(variable_value, destination_path)

  return destination_path
end
set_variable(package, statement, backtrace) click to toggle source
# File lib/fig/runtime_environment.rb, line 283
def set_variable(package, statement, backtrace)
  expanded_value = expand_variable_as_path_and_process_retrieves(
    statement, package, backtrace
  )
  name = statement.name
  @variables[name] = expanded_value

  if Fig::Logging.debug?
    tokenized_value = statement.tokenized_value
    escaped_value = tokenized_value.to_escaped_string
    expanded_message =
      expanded_value == escaped_value \
          ? ''  \
          : %Q< (expanded from "#{escaped_value}")>

    Fig::Logging.debug(
      %Q<Set #{name} to "#{expanded_value}"#{expanded_message}.>
    )
  end

  return
end