Build::EnvironmentSourceBuildEnvironment

class Environment

Represents a layered build environment, implemented as a linked list of hashes. It is primarily used for organising build configurations across a wide range of different sub-systems, e.g. platform configuration, target configuration, local project configuration, etc.

Nested

Definitions

def initialize(parent = nil, values = nil, name: nil, &block)

Initialize a new environment with an optional parent, initial values, name, and update block.

Signature

parameter parent Environment | Nil

The parent environment to inherit values from.

parameter values Hash | Nil

The initial key-value pairs for this layer.

parameter name String | Nil

An optional name for this environment.

parameter block Proc

An optional block applied when constructing the environment.

Implementation

def initialize(parent = nil, values = nil, name: nil, &block)
	@parent = parent
	@values = (values || {}).to_h
	@update = block
	
	@name = name
end

def ==(other)

Compare this environment with another for equality.

Signature

parameter other Environment

The environment to compare against.

returns Boolean

true if both environments have the same parent, values, update, and name.

Implementation

def == other
	self.equal?(other) or
		self.class == other.class and
		@parent == other.parent and
		@values == other.values and
		@update == other.update and
		@name == other.name
end

def eql?(other)

Check equality using the == operator.

Signature

parameter other Environment

The environment to compare against.

returns Boolean

true if the environments are equal.

Implementation

def eql?(other)
	self == other
end

def hash

Compute a hash value for this environment based on its parent, values, update, and name.

Signature

returns Integer

The computed hash value.

Implementation

def hash
	@parent.hash ^ @values.hash ^ @update.hash ^ @name.hash
end

def dup(parent: @parent, values: @values, update: @update, name: @name)

Create a duplicate of this environment, optionally overriding parent, values, update, or name.

Signature

parameter parent Environment | Nil

The parent for the duplicate.

parameter values Hash

The values for the duplicate.

parameter update Proc | Nil

The update block for the duplicate.

parameter name String | Nil

The name for the duplicate.

returns Environment

A new environment with the given attributes.

Implementation

def dup(parent: @parent, values: @values, update: @update, name: @name)
	self.class.new(parent, values.dup, name: name, &update)
end

def freeze

Freeze this environment and its entire parent chain, making it immutable.

Signature

returns Environment

The frozen environment.

Implementation

def freeze
	return self if frozen?
	
	@parent.freeze
	@values.freeze
	@update.freeze
	
	super
end

def lookup(name)

Find the environment layer that contains the given key.

Signature

parameter name Symbol

The key to search for.

returns Environment | Nil

The environment layer containing the key, or nil if not found.

Implementation

def lookup(name)
	if @values.include? name
		self
	elsif @parent
		@parent.lookup(name)
	end
end

def include?(key)

Check whether the key exists in this environment or any parent layer.

Signature

parameter key Symbol

The key to check.

returns Boolean | Nil

true if the key exists, nil otherwise.

Implementation

def include?(key)
	if @values.include?(key)
		true
	elsif @parent
		@parent.include?(key)
	end
end

def size

Count the total number of key-value pairs across all layers.

Signature

returns Integer

The total size.

Implementation

def size
	@values.size + (@parent ? @parent.size : 0)
end

def fetch(key, *default, &block)

Retrieve the value for a key, with support for a default value or block.

Signature

parameter key Symbol

The key to look up.

parameter default Array

An optional default value.

raises KeyError

If the key is not found and no default or block is given.

returns Object

The value associated with the key, or the default.

Implementation

def fetch(key, *default, &block)
	if environment = lookup(key)
		return environment.values[key]
	elsif block_given?
		yield(key, *default)
	elsif !default.empty?
		return default.first
	else
		raise KeyError.new("Environment missing #{key}")
	end
end

def [](key)

Retrieve the value for a key, returning nil if not found.

Signature

parameter key Symbol

The key to look up.

returns Object | Nil

The value, or nil if the key does not exist.

Implementation

def [](key)
	environment = lookup(key)
	
	environment ? environment.values[key] : nil
end

def []=(key, value)

Set the value for a key in the current environment layer.

Signature

parameter key Symbol

The key to set.

parameter value Object

The value to assign.

Implementation

def []=(key, value)
	@values[key] = value
end

def to_s

Return a human-readable string representation of this environment.

Signature

returns String

A string showing the class, name, update source location, and values.

Implementation

def to_s
	buffer = String.new("\#<#{self.class} ")
	
	if @name
		buffer << @name.inspect << " "
	end
	
	if @update
		buffer << @update.source_location.join(":") << " "
	end
	
	buffer << @values.to_s << ">"
	
	return buffer
end

def construct!(proxy, *arguments, &block)

Apply a block to this environment using a class Build::Environment::Constructor proxy, then return the environment.

Signature

parameter proxy Object | Nil

An optional proxy object to delegate unknown method calls to.

parameter arguments Array

Arguments forwarded to the block.

returns Environment

The updated environment.

Implementation

def construct!(proxy, *arguments, &block)
	constructor = Constructor.new(self, proxy)
	
	if block_given?
		constructor.instance_exec(*arguments, &block)
	end
	
	return self
end

def self.combine(*environments)

Flatten the list of environments.

Implementation

def self.combine(*environments)
	ordered = []
	
	environments.each do |environment|
		environment.flatten_to_array(ordered)
	end
	
	ordered.inject(nil) do |parent, environment|
		environment.dup(parent: parent)
	end
end

def merge(**options, &block)

Create a new child environment inheriting from this one, with optional keyword options and block.

Signature

parameter options Hash

Options forwarded to the new environment's constructor.

parameter block Proc

An optional update block for the new environment.

returns Environment

A new environment with self as the parent.

Implementation

def merge(**options, &block)
	self.class.new(self, **options, &block)
end

def to_a

Convert the hierarchy of environments to an array where the parent comes before the child.

Implementation

def to_a
	flat = []
	
	flatten_to_array(flat)
	
	return flat
end

def flatten_to_array(array)

Recursively collect this environment and its ancestors into an array, parent first.

Signature

parameter array Array

The array to append environments into.

Implementation

def flatten_to_array(array)
	if @parent
		@parent.flatten_to_array(array)
	end
	
	array << self
end

def to_h

Evaluate all environment layers into a single flat hash with all values resolved.

Signature

returns Hash

A hash mapping all keys to their fully evaluated values.

Implementation

def to_h
	hash = {}
	
	# Flatten this chain of environments:
	flatten_to_hash(hash)
	
	# Evaluate all items to their respective object value:
	evaluator = Evaluator.new(hash)
	
	# Evaluate all the individual environment values so that they are flat:
	Hash[hash.map{|key, value| [key, evaluator.object_value(value)]}]
end

def evaluator

Create a new class Build::Environment::Evaluator for this environment.

Signature

returns Evaluator

An evaluator backed by this environment.

Implementation

def evaluator
	Evaluator.new(self)
end

def evaluate(**options)

Flatten and evaluate all values, returning a new single-layer environment.

Signature

parameter options Hash

Options forwarded to the new environment's constructor.

returns Environment

A new environment with all values fully evaluated.

Implementation

def evaluate(**options)
	self.class.new(nil, self.to_h, **options)
end

def flatten(**options)

Merge all environment layers into a single-level environment without evaluating values.

Signature

parameter options Hash

Options forwarded to the new environment's constructor.

returns Environment

A flat environment containing the merged but unevaluated values.

Implementation

def flatten(**options)
	hash = {}
	
	flatten_to_hash(hash)
	
	options[:name] ||= self.name
	
	return self.class.new(nil, hash, **options)
end

def defined

Return all key-value pairs in the current layer whose values are Define instances.

Signature

returns Hash

A hash of keys mapped to their Define values.

Implementation

def defined
	@values.select{|name,value| Define === value}
end

def checksum(digester: Digest::SHA1.new)

Compute a hex digest checksum of the environment's content.

Signature

parameter digester Digest

The digest algorithm to use.

returns String

The hexadecimal digest string.

Implementation

def checksum(digester: Digest::SHA1.new)
	checksum_recursively(digester)
	
	return digester.hexdigest
end

def update!

Apply the update function to this environment.

Implementation

def update!
	construct!(self, &@update)
	@update = nil
	
	return self
end

def flatten_to_hash(hash)

We fold in the ancestors one at a time from oldest to youngest.

Implementation

def flatten_to_hash(hash)
	if parent = @parent
		parent = parent.flatten_to_hash(hash)
	end
	
	if @update
		self.dup(parent: parent).update!.update_hash(hash)
	else
		self.update_hash(hash)
	end
end

def self.system_environment(env = ENV, **options)

Construct an environment from a given system environment:

Implementation

def self.system_environment(env = ENV, **options)
	self.new(nil, Hash[env.map{|key, value| [key.downcase.to_sym, value]}], **options)
end

def export

Make a hash appropriate for a process environment

Implementation

def export
	System.convert_to_shell(self)
end