lib/teapot/context.rb in teapot-2.1.0 vs lib/teapot/context.rb in teapot-2.2.0

- old
+ new

@@ -24,13 +24,10 @@ require 'build/rulebook' require 'build/text/substitutions' require 'build/text/merge' module Teapot - TEAPOT_FILE = 'teapot.rb'.freeze - DEFAULT_CONFIGURATION_NAME = 'default'.freeze - class AlreadyDefinedError < StandardError def initialize(definition, previous) super "Definition #{definition.name} in #{definition.path} has already been defined in #{previous.path}!" end @@ -38,57 +35,150 @@ previous = definitions[definition.name] raise self.new(definition, previous) if previous end end - - # A context represents a specific root package instance with a given configuration and all related definitions. - # A context is stateful in the sense that package selection is specialized based on #select and #dependency_chain. These parameters are usually set up initially as part of the context setup. - class Context - def initialize(root, **options) - @root = Path[root] - @options = options - + + # A selection is a specific view of the data exposed by the context at a specific point in time. + class Select + def initialize(context, configuration, names = []) + @context = context + @configuration = Configuration.new(context, configuration.package, configuration.name, [], configuration.options) + @targets = {} @configurations = {} @projects = {} @rules = Build::Rulebook.new - + @dependencies = [] @selection = Set.new + @unresolved = Set.new + + load!(configuration, names) + + @chain = nil + end + + attr :context + attr :configuration + + attr :targets + attr :projects + + # Alises as defined by Configuration#targets + attr :aliases + + # All public configurations. + attr :configurations + attr :rules + + attr :dependencies + attr :selection + attr :unresolved + + def chain + @chain ||= Build::Dependency::Chain.expand(@dependencies, @targets.values, @selection) + end + + def direct_targets(ordered) + @dependencies.collect do |dependency| + ordered.find{|(package, _)| package.provides? dependency} + end.compact + end + + private + + # Add a definition to the current context. + def append definition + case definition + when Target + AlreadyDefinedError.check(definition, @targets) + @targets[definition.name] = definition + when Configuration + # We define configurations in two cases, if they are public, or if they are part of the root package of this context. + if definition.public? or definition.package == @context.root_package + AlreadyDefinedError.check(definition, @configurations) + @configurations[definition.name] = definition + end + when Project + AlreadyDefinedError.check(definition, @projects) + @projects[definition.name] = definition + when Rule + AlreadyDefinedError.check(definition, @rules) + @rules << definition + end + end + + def load_package!(package) + begin + script = @context.load(package) + + # Load the definitions into the current selection: + script.defined.each do |definition| + append(definition) + end + rescue NonexistantTeapotError, IncompatibleTeapotError + # If the package doesn't exist or the teapot version is too old, it failed: + @unresolved << package + end + end + + def load!(configuration, names) + # Load the root package which makes all the named configurations and targets available. + load_package!(@context.root_package) + + # Load all packages defined by this configuration. + configuration.traverse(@configurations) do |configuration| + @configuration.merge(configuration) do |package| + # puts "Load package: #{package} from #{configuration}" + load_package!(package) + end + end + + @configuration.freeze + + names.each do |name| + if @targets.key? name + @selection << name + else + @dependencies << name + end + end + end + end + + # A context represents a specific root package instance with a given configuration and all related definitions. A context is stateful in the sense that package selection is specialized based on #select and #dependency_chain. These parameters are usually set up initially as part of the context setup. + class Context + def initialize(root, **options) + @root = Path[root] + @options = options + + @configuration = nil + @project = nil + @loaded = {} load_root_package(options) unless options[:load_root] == false end attr :root attr :options - attr :targets - attr :projects - - # Context metadata - attr :metadata - - # All public configurations. - attr :configurations - - attr :rules - - # The context's primary configuration. + # The primary configuration. attr :configuration - # The context's primary project. + # The primary project. attr :project - attr :dependencies - attr :selection - def repository @repository ||= Rugged::Repository.new(@root.to_s) end + + def select(names = [], configuration = @configuration) + Select.new(self, configuration, names) + end def substitutions substitutions = Build::Text::Substitutions.new substitutions['TEAPOT_VERSION'] = Teapot::VERSION @@ -117,96 +207,20 @@ substitutions['YEAR'] = current_date.strftime("%Y") return substitutions end - def select(names) - names.each do |name| - if @targets.key? name - @selection << name - else - @dependencies << name - end - end - end - - def dependency_chain(dependency_names, configuration = @configuration) - configuration.load_all - - select(dependency_names) - - Build::Dependency::Chain.expand(@dependencies, @targets.values, @selection) - end - - def direct_targets(ordered) - @dependencies.collect do |dependency| - ordered.find{|(package, _)| package.provides? dependency} - end.compact - end - - # Add a definition to the current context. - def << definition - case definition - when Target - AlreadyDefinedError.check(definition, @targets) - - @targets[definition.name] = definition - when Configuration - # We define configurations in two cases, if they are public, or if they are part of the root package of this context. - if definition.public? or definition.package == @root_package - # The root package implicitly defines the default configuration. - if definition.name == DEFAULT_CONFIGURATION_NAME - raise AlreadyDefinedError.new(definition, root_package) - end - - AlreadyDefinedError.check(definition, @configurations) - - @configurations[definition.name] = definition - end - when Project - AlreadyDefinedError.check(definition, @projects) - - @project ||= definition - - @projects[definition.name] = definition - when Rule - AlreadyDefinedError.check(definition, @rules) - - @rules << definition - end - end - def load(package) - # In certain cases, a package record might be loaded twice. This typically occurs when multiple configurations are loaded in the same context, or if a package has already been loaded (as is typical with the root package). - @loaded.fetch(package) do - loader = Loader.new(self, package) - - loader.load(TEAPOT_FILE) - - # Load the definitions into the current context: - loader.defined.each do |definition| - self << definition - end - - # Save the definitions per-package: - @loaded[package] = loader.defined + if loader = @loaded[package] + return loader.script unless loader.changed? end - end - - def unresolved(packages) - failed_to_load = Set.new - packages.collect do |package| - begin - definitions = load(package) - rescue NonexistantTeapotError, IncompatibleTeapotError - # If the package doesn't exist or the teapot version is too old, it failed: - failed_to_load << package - end - end + loader = Loader.new(self, package) - return failed_to_load + @loaded[package] = loader + + return loader.script end # The root package is a special package which is used to load definitions from a given root path. def root_package @root_package ||= Package.new(@root, "root") @@ -214,25 +228,22 @@ private def load_root_package(options) # Load the root package: - defined = load(root_package) + script = load(root_package) # Find the default configuration, if it exists: - @default_configuration = defined.default_configuration - if configuration_name = options[:configuration] @configuration = @configurations[configuration_name] else - @configuration = @default_configuration + @configuration = script.default_configuration end + @project = script.default_project + if @configuration.nil? raise ArgumentError.new("Could not load configuration: #{configuration_name.inspect}") end - - # Materialize the configuration: - @configuration.materialize if @configuration end end end