Class: Teapot::Configuration

Inherits:
Definition show all
Defined in:
lib/teapot/configuration.rb

Overview

A configuration represents a mapping between package/dependency names and actual source locations. Usually, there is only one configuration, but in some cases it is useful to have more than one, e.g. one for local development using local source code, one for continuous integration, and one for deployment.

Defined Under Namespace

Classes: Import

Constant Summary collapse

DEFAULT_OPTIONS =
{
	:import => true
}.freeze

Instance Attribute Summary collapse

Attributes inherited from Definition

#context, #description, #name, #package

Instance Method Summary collapse

Methods inherited from Definition

#path, #pretty_print

Constructor Details

#initialize(context, package, name, packages = [], options = nil) ⇒ Configuration

Returns a new instance of Configuration.



39
40
41
42
43
44
45
46
47
48
49
50
51
52
# File 'lib/teapot/configuration.rb', line 39

def initialize(context, package, name, packages = [], options = nil)
	super context, package, name

	if options
		@options = options
	else
		@options = DEFAULT_OPTIONS.dup
	end

	@packages = IdentitySet.new(packages)
	@imports = IdentitySet.new

	@visibility = :private
end

Instance Attribute Details

#importsObject (readonly)

A list of other configurations to include when materialising the list of packages.



81
82
83
# File 'lib/teapot/configuration.rb', line 81

def imports
  @imports
end

#optionsObject (readonly)

Options used to bind packages to this configuration.



75
76
77
# File 'lib/teapot/configuration.rb', line 75

def options
  @options
end

#packagesObject (readonly)

A list of packages which are required by this configuration.



78
79
80
# File 'lib/teapot/configuration.rb', line 78

def packages
  @packages
end

#visibilityObject (readonly)

Controls how the configuration is exposed in the context.



64
65
66
# File 'lib/teapot/configuration.rb', line 64

def visibility
  @visibility
end

Instance Method Details

#[](key) ⇒ Object

Get a configuration option.



116
117
118
# File 'lib/teapot/configuration.rb', line 116

def [] key
	@options[key]
end

#[]=(key, value) ⇒ Object

Set a configuration option.



111
112
113
# File 'lib/teapot/configuration.rb', line 111

def []= key, value
	@options[key] = value
end

#freezeObject



54
55
56
57
58
59
60
61
# File 'lib/teapot/configuration.rb', line 54

def freeze
	@options.freeze
	@packages.freeze
	@imports.freeze
	@visibility.freeze
	
	super
end

#groupObject

Create a group for configuration options which will be only be active within the group block.



102
103
104
105
106
107
108
# File 'lib/teapot/configuration.rb', line 102

def group
	options = @options.dup
	
	yield
	
	@options = options
end

#import(name, explicit = true) ⇒ Object

Specifies that this package will import additional configuration records from another definition.



97
98
99
# File 'lib/teapot/configuration.rb', line 97

def import(name, explicit = true)
	@imports << Import.new(name, explicit, @options.dup)
end

#load_allObject

Load all packages defined by this configuration.



139
140
141
142
143
# File 'lib/teapot/configuration.rb', line 139

def load_all
	@packages.each do |package|
		@context.load(package)
	end
end

#lock_pathObject



130
131
132
# File 'lib/teapot/configuration.rb', line 130

def lock_path
	context.root + "#{@name}-lock.yml"
end

#lock_storeObject



134
135
136
# File 'lib/teapot/configuration.rb', line 134

def lock_store
	@lock_store ||= YAML::Store.new(lock_path.to_s)
end

#materializeObject

Process all import directives and return a new configuration based on the current configuration. Import directives bring packages and other import directives from the specififed configuration definition.



151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
# File 'lib/teapot/configuration.rb', line 151

def materialize
	# Potentially no materialization is required:
	return false if @imports.count == 0
	
	# Avoid loops in the dependency chain:
	imported = IdentitySet.new
	
	# Enumerate all imports and attempt to resolve the packages:
	begin
		updated = false
		
		# Before trying to materialize, we should load all possible packages:
		@packages.each do |package|
			@context.load(package) rescue nil
		end
		
		imports = @imports
		@imports = IdentitySet.new
		
		imports.each do |import|
			named_configuration = @context.configurations[import.name]

			# So we don't get into some crazy cycle:
			next if imported.include? import
			
			# It would be nice if we could detect cycles and issue an error to the user. However, sometimes the case above is not hit at the point where the cycle begins - it isn't clear at what point the user explicitly created a cycle, and what configuration actually ends up being imported a second time.
			
			if named_configuration && named_configuration != self
				# Mark this as resolved
				imported << import
				
				updated = self.merge(named_configuration, import.options) || updated
			else
				# It couldn't be resolved and hasn't already been resolved...
				@imports << import
			end
		end
	end while updated
	
	return true
end

#merge(configuration, options) ⇒ Object

Merge an external configuration into this configuration. We won’t override already defined packages.



194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
# File 'lib/teapot/configuration.rb', line 194

def merge(configuration, options)
	updated = false
	
	configuration.packages.each do |external_package|
		# The top level configuration will override packages that are defined by imported configurations. This is desirable behaviour, as it allows us to flatten the configuration but provide overrides if required.
		unless @packages.include? external_package
			options = options.merge(external_package.options)
			
			@packages << Package.new(packages_path + external_package.name, external_package.name, options)
			
			updated = true
		end
	end
	
	configuration.imports.each do |external_import|
		unless @imports.include? external_import
			options = options.merge(external_import.options)
			
			@imports << Import.new(external_import.name, external_import.explicit, options)
			
			updated = true
		end
	end
	
	return updated
end

#packages_pathObject

The path where packages will be located when fetched.



121
122
123
# File 'lib/teapot/configuration.rb', line 121

def packages_path
	context.root + "teapot/packages/#{name}"
end

#platforms_pathObject

The path where built products will be installed.



126
127
128
# File 'lib/teapot/configuration.rb', line 126

def platforms_path
	context.root + "teapot/platforms/#{name}"
end

#public!Object



70
71
72
# File 'lib/teapot/configuration.rb', line 70

def public!
	@visibility = :public
end

#public?Boolean

Returns:

  • (Boolean)


66
67
68
# File 'lib/teapot/configuration.rb', line 66

def public?
	@visibility == :public
end

#require(name, options = nil) ⇒ Object

Specifies that this configuration depends on an external package of some sort.



84
85
86
87
88
89
90
91
92
93
94
# File 'lib/teapot/configuration.rb', line 84

def require(name, options = nil)
	options = options ? @options.merge(options) : @options.dup
	
	@packages << Package.new(packages_path + name.to_s, name, options)
	
	if options[:import] == true
		import(name, false)
	elsif String === options[:import]
		import(options[:import])
	end
end

#to_sObject



221
222
223
# File 'lib/teapot/configuration.rb', line 221

def to_s
	"#<#{self.class} #{@name.dump} visibility=#{@visibility}>"
end

#top!Object

Conceptually, a configuration belongs to a package. Primarily, a configuration lists dependent packages, but this also includes itself as the dependencies are purely target based, e.g. this configuration has access to any targets exposed by its own package.



146
147
148
# File 'lib/teapot/configuration.rb', line 146

def top!
	@packages << @package
end