# -*- coding: UTF-8 -*- require 'mj/visitor' require 'build-tool/configuration' require 'build-tool/environment' require 'build-tool/model/feature' require 'build-tool/repository' require 'build-tool/model/module' require 'build-tool/sshkey' require 'build-tool/server' module BuildTool; module Cfg; # Base class for all Visitors which need to iterate over their child # nodes. class ListVisitor < MJ::VisitorBase # Visit one node. def visit( node ) return self.visit_nodes( node.values ) end # Visit all nodes. def visit_nodes(nodes) list = [] return list if nodes.nil? nodes.each do |child| break if child.nil? list << child.accept( self ) end return list end end # class ListVisitor # Build System Declaration Visitor. class BuildSystemDeclarationNodeVisitor < ListVisitor # Initialize a build system. If no build system is provided we will # create a new one. Which is not registered with the configuration. def initialize( configuration, build_system ) super( configuration ) @build_system = build_system end def visit_BuildSystemDeclarationNode( node ) visit_nodes( node.values[1] ) return @build_system end def visit_BuildSystemInplaceNode( node ) @build_system.out_of_source = false end def visit_BuildSystemOptionNode( node ) case node.values.length when 3 case node.values[1] when 'append'; @build_system.append( node.values[0], node.values[2] ) when 'prepend'; @build_system.prepend( node.values[0], node.values[2] ) when 'set'; @build_system.set( node.values[0], node.values[2] ) else raise StandardError, "Unexpected token #{node.values[1]}!" end when 2 @build_system.set( node.values[0], node.values[1] ) else raise StandardError, "Unexpected number of tokens #{node.values.length} #{node.values.join(', ')}" end end end # Statement Visitor class StatementVisitor < ListVisitor def visit_BuildSystemDeclarationNode( node ) raise ArgumentsError if node.values.length != 2 name = node.values[0] bs = configuration.build_system_defaults( name ) visitor = BuildSystemDeclarationNodeVisitor.new( configuration, bs ) node.accept( visitor ) end def visit_ConfigurationFileList( node ) self.visit(node) end def visit_EnvironmentDeclarationNode( node ) visitor = EnvironmentDeclarationNodeVisitor.new( configuration ) node.accept( visitor ) end def visit_EnableFeatureNode( node ) set_feature_status( node.value, true, node.options[:global] ) end def visit_DisableFeatureNode( node ) set_feature_status( node.value, false, node.options[:global] ) end def visit_EnableModuleNode( node ) set_module_status( node.value, true, node.options[:global] ) end def visit_DisableModuleNode( node ) set_module_status( node.value, false, node.options[:global] ) end def visit_FeatureNode( node ) visitor = FeatureNodeVisitor.new( configuration ) node.accept( visitor ) end def visit_GitOptionsDeclarationNode( node ) visitor = GitOptionsDeclarationNodeVisitor.new( configuration ) node.accept( visitor ) end def visit_IncludeNode( node ) visitor = IncludeNodeVisitor.new( configuration ) node.accept( visitor ) end def visit_ModuleDeclarationNode( node ) visitor = ModuleDeclarationNodeVisitor.new( configuration ) node.accept( visitor ) end def visit_RepositoryDeclarationNode( node ) visitor = RepositoryDeclarationNodeVisitor.new( configuration ) node.accept(visitor) end def visit_ServerDeclarationNode( node ) visitor = ServerDeclarationNodeVisitor.new( configuration ) node.accept(visitor) end def visit_SshKeyDeclarationNode( node ) visitor = SshKeyDeclarationNodeVisitor.new( configuration ) node.accept(visitor) end protected def set_feature_status( featureName, status, global ) feat = configuration.feature( featureName ) # Make sure the feature is known. if feat.nil? raise ConfigurationError, "Attempt to disable unknown feature '%s'!" % featureName end # Disable the feature. if global # We are still loading the recipe. OK feat.default_active = status else # We are in the user recipe override file. Issue a warning. feat.active = status end end def set_module_status( moduleName, status, global ) mod = configuration.module( moduleName ) # Make sure the feature is known. if mod.nil? raise ConfigurationError, "Attempt to disable unknown module '%s'!" % moduleName end # Disable the feature. if global # We are still loading the recipe. OK mod.default_active = status else # We are in the user recipe override file. Issue a warning. mod.active = status end end end # class StatementVisitor class ArchiveDeclarationNodeVisitor < ListVisitor def initialize( configuration, vcs ) super( configuration ) @vcs = vcs end def visit_ArchiveDeclarationNode( node ) visit_nodes( node.values ) return @vcs end def visit_RepositoryDeclarationNode( node ) visitor = RepositoryDeclarationNodeVisitor.new( configuration ) @vcs.repository = node.accept( visitor ) end def visit_UseRepositoryNode( node ) repo = configuration.repository( node.value ) if repo.nil? raise ConfigurationError, "Unknown repository #{node.value}!" end @vcs.repository = repo end def visit_RemotePathNode( node ) @vcs.remote_path = node.value end end # class ArchiveDeclarationNodeVisitor # The Configuration File Visitor. # # This is the toplevel visitor. class ConfigurationFileVisitor < StatementVisitor def visit_LogDirectoryNode( node ) configuration.log_directory = node.value end end # class ConfigurationFileVisitor class EnvironmentDeclarationNodeVisitor < ListVisitor def visit_EnvironmentDeclarationNode( node ) name = node.values[0] @environment = configuration.environment( name ) if @environment.nil? @environment = BuildTool::Environment.new( name ) # Only add a environment to a feature if this is the first time it is declared. configuration.add_environment( @environment ) if !configuration.active_feature.nil? @environment.feature = configuration.active_feature configuration.active_feature.environments << @environment end end if node.values[1] == :INHERITANCE parentName = node.values[2] @environment.parent = configuration.environment(parentName) if @environment.parent.nil? raise ConfigurationError, "Environment #{name} inherits from unknown environment #{parentName}!" end self.visit_nodes( node.values[3] ) else self.visit_nodes( node.values[1] ) end return @environment end def visit_EnvironmentVariableNode( node ) case node.values.length when 3 case node.values[1] when 'append'; @environment.append( node.values[0], node.values[2] ) when 'prepend'; @environment.prepend( node.values[0], node.values[2] ) when 'set'; @environment.set( node.values[0], node.values[2] ) else raise StandardError, "Unexpected token #{node.values[1]}!" end when 2 @environment.set( node.values[0], node.values[1] ) else raise StandardError, "Unexpected number of tokens #{node.values.length} #{node.values.join(', ')}" end end end # class EnvironmentDeclarationNodeVisitor class FeatureNodeVisitor < ConfigurationFileVisitor def initialize( configuration ) super( configuration ) @feature = nil end def visit_FeatureNode( node ) raise ArgumentsError if node.values.length != 2 childs = node.values # Get the name if nested name = childs.shift if configuration.active_feature @feature = configuration.feature( "#{configuration.active_feature.name}/#{name}" ) else @feature = configuration.feature( name ) end # Create a new feature if needed if @feature.nil? @feature = BuildTool::Feature.create( :name => name, :parent => configuration.active_feature ) configuration.add_feature( @feature ) end # Set it as the current feature old_feat = configuration.active_feature configuration.active_feature= @feature # Visit the childs visit_nodes( childs.shift ) # Reset the old current feature configuration.active_feature = old_feat end def visit_LongDescriptionNode( node ) if @feature.long_description logger.warn "Overwriting long description for feature #{@feature.name}" end @feature.long_description = node.values end def visit_ShortDescriptionNode( node ) if !@feature.description.nil? logger.warn "Overwriting short description for feature #{@feature.name}" end @feature.description = node.values end end # class FeatureNodeVisitor class GitRemoteDeclarationNodeVisitor < ListVisitor def visit_GitRemoteDeclarationNode( node ) name = node.values[0] @remote = BuildTool::VCS::GitRemote.new( name ) stmts = node.values[1] visit_nodes( stmts ) return @remote end def visit_GitRemoteUrlNode( node ) if node.values.length == 2 @remote.server = configuration.server( node.values[0] ) if @remote.server.nil? raise ConfigurationError, "Unknown server #{node.values[0]} configured." end @remote.path = node.values[1] else @remote.server = BuildTool::Server.new( "unnamed" ) @remote.server.host = node.values[0] end end def visit_GitRemotePushNode( node ) if node.values.length == 2 @remote.push_server = configuration.server( node.values[0] ) if @remote.push_server.nil? raise ConfigurationError, "Unknown server #{node.values[0]} configured." end @remote.push_path = node.values[1] else @remote.push_server = BuildTool::Server.new( "unnamed" ) @remote.push_server.host = node.values[0] end end end # class GitRemoteDeclarationNodeVisitor module GitOptionDeclarationMethods def self.included( base ) base.class_eval do extend ClassMethods include InstanceMethods end end module ClassMethods end module InstanceMethods def visit_GitOptionsDeclarationNode( node ) visit_nodes( node.values ) end def visit_GitOptionNode( node ) if ( node.args[0] == :GLOBAL ) @vcs.global_options[ node.values[0] ] = node.values[1] else @vcs.options[ node.values[0] ] = node.values[1] end end end end class GitOptionsDeclarationNodeVisitor < ListVisitor include GitOptionDeclarationMethods def initialize( configuration ) super( configuration ) if BuildTool::VCS::GitConfiguration.global_config.nil? BuildTool::VCS::GitConfiguration.global_config = BuildTool::VCS::GitConfiguration.new end @vcs = BuildTool::VCS::GitConfiguration.global_config end end class GitDeclarationNodeVisitor < GitRemoteDeclarationNodeVisitor include GitOptionDeclarationMethods def initialize( configuration, vcs ) super( configuration ) @remote = BuildTool::VCS::GitRemote.new( "origin" ) @vcs = vcs @vcs.remote[@remote.name] = @remote end def visit_GitDeclarationNode( node ) visit_nodes( node.values ) if @remote.server.nil? and @remote.path.nil? @vcs.remote.delete( "origin" ) end return @vcs end def visit_GitRemoteDeclarationNode( node ) visitor = GitRemoteDeclarationNodeVisitor.new( configuration ) remote = node.accept(visitor) @vcs.remote[remote.name] = remote end def visit_GitTrackNode( node ) @vcs.track = node.values end end # class GitDeclarationNodeVisitor class GitSvnDeclarationNodeVisitor < GitDeclarationNodeVisitor def initialize( configuration, vcs ) super( configuration, vcs ) end def visit_GitSvnDeclarationNode( node ) visit_nodes( node.values ) if @remote.server.nil? and @remote.path.nil? @vcs.remote.delete( "origin" ) end return @vcs end def visit_GitSvnExternalNode( node ) ( name, value ) = node.value.split( '#' ) @vcs.add_external( name, value ) end def visit_RepositoryDeclarationNode( node ) visitor = RepositoryDeclarationNodeVisitor.new( configuration ) @vcs.repository = node.accept( visitor ) end def visit_UseRepositoryNode( node ) repo = configuration.repository( node.value ) if repo.nil? raise ConfigurationError, "Unknown repository #{node.value}!" end @vcs.repository = repo end def visit_RemotePathNode( node ) @vcs.remote_path = node.value end end # class GitSvnDeclarationNodeVisitor class IncludeNodeVisitor < ListVisitor def visit_IncludeNode( node ) name = node.value configuration.recipe.include_file( name, configuration ) end end class MercurialDeclarationNodeVisitor < ListVisitor def initialize( configuration, vcs ) super( configuration ) @vcs = vcs end def visit_MercurialDeclarationNode( node ) visit_nodes( node.values ) @vcs end def visit_MercurialTrackNode( node ) @vcs.track = node.values end def visit_MercurialUrlNode( node ) @vcs.url = node.values[0] end end class ModuleDeclarationNodeVisitor < ListVisitor def initialize( configuration ) super( configuration ) @module = nil end def visit_ApplyPatchesNode( node ) @module.patches << node.value end def visit_ArchiveDeclarationNode( node ) vcs = BuildTool::VCS::ArchiveConfiguration.new # if @module.vcs_configuration and @module.vcs_configuration.name == "archive" # vcs.copy_configuration( @module.vcs_configuration ) # end @module.vcs_configuration = vcs visitor = ArchiveDeclarationNodeVisitor.new( configuration, vcs ) node.accept( visitor ) end def visit_BuildSystemDeclarationNode( node ) raise ArgumentsError if node.values.length != 2 name = node.values[0] # We have to create a build-system. There are some possibilities: # 1. The module has a build-system. Inherit from that one (Overloading) # 2. The module has a parent but no build-system: Create the build-system parentless # so it forwards to the parent module # 3. The module has no parent and no build-system. Inherit from the build-system # standard module. if @module.our_build_system # logger.debug2( "Inherit from previous build-system for #{@module.name}" ) build_system = configuration.build_system_adjust( name, @module.our_build_system ) elsif @module.parent # logger.debug2( "Inherit without parent for #{@module.name}" ) build_system = configuration.build_system_adjust( name, nil ) else # logger.debug2( "Inherit from #{name} for #{@module.name}" ) build_system = configuration.build_system_adjust( name, configuration.build_system_defaults( name ) ) end visitor = BuildSystemDeclarationNodeVisitor.new( configuration, build_system ) @module.build_system = node.accept( visitor ) @module.build_system.module = @module @module.build_system.feature = configuration.active_feature end def visit_LongDescriptionNode( node ) if @module.long_description logger.warn "Overwriting long description for module #{@module.name}" end @module.long_description = node.values end def visit_EnvironmentDeclarationNode( node ) visitor = EnvironmentDeclarationNodeVisitor.new( configuration ) @module.environment = node.accept( visitor ) end def visit_GitDeclarationNode( node ) vcs = BuildTool::VCS::GitConfiguration.new if node.options[:inheritance] if not @module.vcs_configuration raise ConfigurationError, "#{@module.name} has no previous git configuration!" end if @module.vcs_configuration.name != "git" raise ConfigurationError, "#{@module.name} has a #{@module.vcs_configuration.name} configuration!" end vcs.parent = @module.vcs_configuration end @module.vcs_configuration = vcs visitor = GitDeclarationNodeVisitor.new( configuration, vcs ) node.accept( visitor ) end def visit_GitSvnDeclarationNode( node ) vcs = BuildTool::VCS::GitSvnConfiguration.new if node.options[:inheritance] if not @module.vcs_configuration raise ConfigurationError, "#{@module.name} has no previous git-svn configuration!" end if @module.vcs_configuration.name != "git-svn" raise ConfigurationError, "#{@module.name} has a #{@module.vcs_configuration.name} configuration!" end vcs.parent = @module.vcs_configuration end @module.vcs_configuration = vcs visitor = GitSvnDeclarationNodeVisitor.new( configuration, vcs ) node.accept( visitor ) end def visit_MercurialDeclarationNode( node ) vcs = BuildTool::VCS::MercurialConfiguration.new if node.options[:inheritance] if not @module.vcs_configuration raise ConfigurationError, "#{@module.name} has no previous mercurial configuration!" end if @module.vcs_configuration.name != "mercurial" raise ConfigurationError, "#{@module.name} has a #{@module.vcs_configuration.name} configuration!" end vcs.parent = @module.vcs_configuration end @module.vcs_configuration = vcs visitor = MercurialDeclarationNodeVisitor.new( configuration, vcs ) node.accept( visitor ) end def visit_ModuleBuildPrefixNode( node ) @module.build_prefix = node.value end def visit_ModuleDeclarationNode( node ) name = node.values[0] inheritance = node.values[1] == :INHERITANCE if inheritance stmts = node.values[3] else stmts = node.values[1] end # Check if the module is alread defined. If yes we reopen it. @module = configuration.module( name ) if @module.nil? @module = BuildTool::Module.create( { :name => name } ) configuration.add_module( @module ) end if inheritance if @module.found_in_recipe raise ConfigurationError, "Attempt to change a module #{name} while specifying inheritance!" end parentName = node.values[2] parent = configuration.module( parentName ) if parent.nil? raise ConfigurationError, "Module %s attempts to inherit from not existant module %s" % [ name, parentName ] end @module.parent = parent end # Only add a module to a feature if it is the first time declared. if !@module.found_in_recipe if !configuration.active_feature.nil? @module.feature = configuration.active_feature configuration.active_feature.modules << @module end end # Mark the module as found in the recipe @module.found_in_recipe = true # Work on the child nodes. begin self.visit_nodes( stmts ) rescue ConfigurationError => e raise ConfigurationError, "Module #{@module.name}: #{e.to_s}" end end def visit_ModuleInstallPrefixNode( node ) @module.install_prefix = node.value end def visit_ModuleLocalPathNode( node ) @module.local_path = node.value end def visit_ModuleTemplateNode( node ) @module.is_template = true end def visit_ShortDescriptionNode( node ) if !@module.description.nil? logger.warn "Overwriting short description for module #{@module.name}" end @module.description = node.values end def visit_SvnDeclarationNode( node ) vcs = BuildTool::VCS::SvnConfiguration.new if node.options[:inheritance] if not @module.vcs_configuration raise ConfigurationError, "#{@module.name} has no previous svn configuration!" end if @module.vcs_configuration.name != "svn" raise ConfigurationError, "#{@module.name} has a #{@module.vcs_configuration.name} configuration!" end vcs.parent = @module.vcs_configuration end @module.vcs_configuration = vcs visitor = SvnDeclarationNodeVisitor.new( configuration, vcs ) node.accept( visitor ) end def visit_UseBuildSystemNode( node ) # logger.debug2( "Inherit from build-system #{node.value} for #{@module.name}" ) @module.build_system = configuration.build_system_adjust( node.value, configuration.build_system_defaults( node.value ) ) @module.build_system.module = @module end def visit_UseEnvironmentNode( node ) @module.environment = configuration.environment( node.value ) if @module.environment.nil? raise ConfigurationError, "Unknown environment #{node.value} configured for module #{@module.name}!" end end def visit_UseVcsNode( node ) @module.vcs_configuration = configuration.vcs(node.value) end end # class ModuleDeclarationNodeVisitor class RepositoryDeclarationNodeVisitor < ListVisitor def visit_RepositoryDeclarationNode( node ) name = node.values[0] @repository = configuration.repository(name) if @repository.nil? @repository = BuildTool::Repository.new( name ) configuration.add_repository( @repository ) end stmts = node.values[1] visit_nodes( stmts ) return @repository end def visit_RepositoryPathNode( node ) @repository.path = node.value end def visit_RepositoryUserNode( node ) @repository.user = node.value end def visit_ServerDeclarationNode( node ) visitor = ServerDeclarationNodeVisitor.new( configuration ) @repository.server = node.accept(visitor) end def visit_SshKeyDeclarationNode( node ) @repository.sshkey = node.value end def visit_UseServerNode( node ) @repository.server = configuration.server( node.value ) if @repository.server.nil? raise ConfigurationError, "Unknown server #{node.value} configured for repository #{@repository.name}!" end end def visit_UseSshKeyNode( node ) name = node.value @repository.sshkey = configuration.sshkey(name) raise ConfigurationError, "Unknown ssh-key #{name} configured for repository #{@repository.name}!" if @repository.sshkey.nil? end end # class RepositoryDeclarationNodeVisitor class ServerDeclarationNodeVisitor < ListVisitor def initialize( configuration ) super( configuration ) @server = nil end def visit_ServerDeclarationNode( node ) name = node.values[0] # Check if a server with that name already exists @server = configuration.server(name) # Create a new one if not @server = Server.new( name ) if @server.nil? statements = node.values[1] self.visit_nodes(statements.values) configuration.add_server( @server ) return @server end def visit_ServerHostNode( node ) @server.host = node.value end def visit_ServerPathNode( node ) @server.path = node.value end def visit_ServerProtocolNode( node ) @server.protocol = node.value end def visit_ServerStatementList( node ) self.visit( node ) end def visit_SshKeyDeclarationNode( node ) @server.sshkey = node.value end def visit_UseSshKeyNode( node ) name = node.value @server.sshkey = configuration.sshkey(name) raise ConfigurationError, "Unknown ssh-key #{name} configured for server #{@server.name}!" if @server.sshkey.nil? end end # class ServerDeclarationNodeVisitor class SshKeyDeclarationNodeVisitor < ListVisitor def initialize( configuration ) super @sshkey = nil end def visit_SshKeyDeclarationNode( node ) name = node.values[0] stmts = node.values[1] @sshkey = BuildTool::SshKey.new( name ) configuration.add_sshkey( @sshkey ) visit_nodes( stmts ) return @sshkey end def visit_SshKeyFileNode( node ) @sshkey.file = File.expand_path( node.value ) end end class SvnDeclarationNodeVisitor < ListVisitor def initialize( configuration, vcs ) super( configuration ) @vcs = vcs end def visit_SvnDeclarationNode( node ) visit_nodes( node.values ) return @vcs end def visit_SvnCheckoutOnlyNode( node ) @vcs.only = node.value.split( / +/ ) end def visit_RepositoryDeclarationNode( node ) visitor = RepositoryDeclarationNodeVisitor.new( configuration ) @vcs.repository = node.accept( visitor ) end def visit_UseRepositoryNode( node ) repo = configuration.repository( node.value ) if repo.nil? raise ConfigurationError, "Unknown repository #{node.value}!" end @vcs.repository = repo end def visit_RemotePathNode( node ) @vcs.remote_path = node.value end end # class SvnDeclarationNodeVisitor end; end