# -*- coding: UTF-8 -*- require 'mj/tools/editor' require 'build-tool/vcs/git' require 'build-tool/vcs/git-svn' require 'build-tool/vcs/svn' require 'build-tool/vcs/archive' require 'build-tool/vcs/mercurial' require 'build-tool/vcs/bazar' require 'build-tool/model/module' require 'build-tool/model/feature' require 'build-tool/model/setting' require 'build-tool/build-system/cmake' require 'build-tool/build-system/qt' require 'build-tool/build-system/autoconf' require 'build-tool/build-system/custom' require 'build-tool/build-system/none' require 'build-tool/build-system/kdel10n' require 'build-tool/build-system/qmake' require 'stringio' require 'inifile' module BuildTool class Configuration def self.edit( only = [] ) settings = BuildTool::Setting::export( BuildTool::Application.instance.configuration.settings, only ) text = StringIO.new settings.each do |s| text << '# ' << s[:description].gsub( /\n(?!\z)/, "\n# " ) text << s[:name] << ' = ' << s[:value] text << "\n" text << "\n" end editor = MJ::Tools::TmpFileEditor.new( text.string ) # Give the user the possibility to edit the file editor.edit() if not Pathname.new( editor.path() ).exist? logger.info( "File deleted!" ) return 0 end # Reparse it values = Array.new file = IniFile.load( editor.path() ) file.each do | section, param, value | if section != 'global' logger.error( 'section is not global?' ) end values << { name: param, value: value } end # Write it back BuildTool::Setting::import( BuildTool::Application.instance.configuration.settings, values ) return 0 end attr_accessor :recipe attr_reader :modules attr_reader :environments attr_reader :features attr_accessor :active_feature attr_reader :settings def log_directory return @log_directory if @log_directory raise BuildTool::ConfigurationError, "No log directory configured" end def log_directory=( path ) path = Pathname.new( path.sub( /\$HOME/, '~' ) ) if path.to_s[0] != '~' and path.relative? raise ConfigurationError, "Log directory '#{path}' is relative!" end @log_directory = path.expand_path end def environment( name ) @environments[name] end def add_environment( env) @environments[env.name] = env end def feature( name ) @features[name] end def create_feature( name, parent = active_feature ) if parent path = "#{parent.path}/#{name}" else path = "#{name}" end if @features.has_key?( path ) raise StandardError, "Attempt to create already existing feature #{name}" end if @cached_features.has_key?( path ) # Found in cache. Add it for real and remove from cache. logger.debug( "Getting feature #{path} from cache" ) add_feature( @cached_features[ path] ) @cached_features.delete( path) else logger.debug( "Creating feature #{path}" ) add_feature( BuildTool::Feature.create( :name => name, :parent => parent ) ) end @features[path] end def module( name ) @module[name] end def create_module( name ) if @module.has_key?( name ) raise StandardError, "Attempt to create already existing module #{name}" end if @cached_modules.has_key?( name ) # Found in cache. Add it for real and remove from cache. logger.debug( "Getting module #{name} from cache" ) add_module( @cached_modules[ name ] ) @cached_modules.delete( name ) else logger.debug( "Creating module #{name}" ) add_module( BuildTool::Module.create( { :name => name } ) ) end @module[name] end def repository( name ) @repository[name] end def add_repository( repo ) @repository[repo.name] = repo end def server( name ) @server[name] end def add_server( server ) @server[ server.name ] = server end def sshkey( name ) @sshkey[name] end def add_sshkey( key ) @sshkey[key.name] = key end def add_setting( s ) @settings[s.name] = s end def initialize() @recipe = nil @server = {} @log_directory = nil @environments = {} @module = {} @modules = [] @build_system = {} @repository = {} @sshkey = {} @active_feature = nil @features = {} @settings = {} @cached_modules = {} @cached_features = {} base_load() end def vcs( name ) case name when "git-svn" return BuildTool::VCS::GitSvnConfiguration.new when "git" return BuildTool::VCS::GitConfiguration.new when "svn" return BuildTool::VCS::SvnConfiguration.new when "archive" return BuildTool::VCS::ArchiveConfiguration.new when "mercurial" return BuildTool::VCS::MercurialConfiguration.new when "bazar" return BuildTool::VCS::BazarConfiguration.new else raise StandardError, "Unknown Version Control System #{name}" end end def create_build_system( name, parent = nil, *args ) return case name when "none" BuildTool::BuildSystem::None.new( parent, *args ) when "cmake" BuildTool::BuildSystem::CMake.new( parent, *args ) when "kdel10n" BuildTool::BuildSystem::KdeL10n.new( parent, *args ) when "qt" BuildTool::BuildSystem::Qt.new( parent, *args ) when "qmake" BuildTool::BuildSystem::QMake.new( parent, *args ) when "custom" BuildTool::BuildSystem::Custom.new( parent, *args ) when "autoconf" BuildTool::BuildSystem::AutoConf.new( parent, *args ) else raise StandardError, "Unknown Version Control System #{name}" end end def build_system_adjust( name, parent = nil, *args ) bs = create_build_system( name, parent ) bs.defaults = build_system_defaults( name ) return bs end def build_system_defaults( name, *args ) return @build_system[name] if @build_system[name] add_build_system( create_build_system( name ) ) end def add_build_system( bs ) return @build_system[bs.name] = bs end def save logger.debug "Saving features to database." @features.each do |name, f| logger.debug2( " Feature %s (%s)" % [ f.name, f.parent_id ] ) if f.changed? f.save! if f.changed? end logger.debug "Saving modules to database." @modules.each do |m| logger.debug2( " Module %s" % [ m.name ] ) if m.changed? m.save! if m.changed? end logger.debug "Saving settings to database." @settings.each do |n, s| logger.debug2( " Setting %s: %s" % [ s.name, s.value ] ) if s.changed? s.save! if s.changed? end end def truncate logger.debug "Deleting all features from database." BuildTool::Feature.delete_all() @features = {} @cached_features = {} logger.debug "Deleting all modules from database." BuildTool::Module.delete_all() @module = {} @modules = [] @cached_modules = {} logger.debug "Deleting all settings from database." BuildTool::Setting.delete_all() @settings = {} end # Execute the necessary steps to adapt the configuration to the current state of the # recipe. # # The recipe has to be loaded before calling this method. With the configuration given. # The method removes obsolete stuff from the database. def migrate() logger.debug( 'Checking for obsolete modules in db.' ) for mod in @cached_modules.values() do logger.info 'Module %s no longer supported by recipe. Removing it from db.' % [ mod.name ] # Remove it from the db mod.destroy() end @cached_modules = {} logger.debug( 'Checking for obsolete features in db.' ) for feat in @cached_features.values() do logger.info 'Feature %s no longer supported by recipe. Removing it from db.' % [ feat.path ] # Remove it from the db feat.destroy() end @cached_features = {} logger.debug( 'Checking for obsolete settings in db.' ) settings.each do |n, s| if not s.seen and not n.start_with? 'BUILD_TOOL.' logger.info 'Setting %s no longer supported by recipe. Removing it from db.' % [ s.name ] s.destroy() end end end # Complete buildsets def complete_buildsets( name ) if name == ':all' return complete_modules( '*' ) end end def complete_module( name, include_templates = false, all = false, resume_from = nil, resume_after = nil ) modules = complete_modules( name, include_templates, all, resume_from, resume_after ) case modules.size when 0 then return nil when 1 then return modules[0] else raise BuildTool::ConfigurationError, "#{name} is ambiguous. Please be more specific" end end def complete_modules( name, include_templates = false, all = false, resume_from = nil, resume_after = nil ) if name.start_with? ':' return complete_buildsets( name ) end res = [] found = false should_be_unique = false resume = ( resume_from.nil? and resume_after.nil? ) modules.each do |mod| take_module = false next if ( !include_templates and mod.is_template? ) # We match on the following conditions: # 1. name = mod.name # 2. name/ matches beginning of mod.name # 3. name equals last part of mod.name (behind /) and is unique if name.end_with?('/') and ( mod.name.index "/#{name}" or mod.name.start_with? name ) found = true # Now check if it is active. next if !( mod.active? || all ) take_module = true elsif mod.name == name or mod.name.end_with?( "/#{name}" ) found = true should_be_unique = true take_module = true elsif name == '*' found = true # Now check if it is active. next if !( mod.active? || @all ) take_module = true end if not resume and resume_from and mod.name == resume_from.name resume = true end res << mod if resume and take_module if not resume and resume_after and mod.name == resume_after.name resume = true end end # Raise an error if the module was not found if !found raise BuildTool::ConfigurationError, "Unknown module/package #{name}" end # Raise an error if the result should be unique but is not. if should_be_unique if res.size > 1 raise BuildTool::ConfigurationError, "#{name} is ambiguous (#{res.map { |m| m.name }.join( ', ' ) })." end mod = res[0] # If the module is inactive make sure the feature is active if not ( mod.active? || mod.feature.nil? || mod.feature.active? ) raise BuildTool::ConfigurationError, "Can't select module %s from inactive feature %s" % [ mod.name, mod.feature.name ] end end # Give a warning if all modules where warn( "All modules for #{name} are inactive! Will ignore it." ) if res.empty? return res end ####### private ####### def base_load() logger.debug "Loading features from database." BuildTool::Feature.all.each do |f| logger.debug2 " - Feature %s" % [ f.path ] @cached_features[f.path] = f end logger.debug "Loading modules from database." BuildTool::Module.all.each do |m| logger.debug2 " - Module %s" % [ m.name ] @cached_modules[m.name] = m end logger.debug "Loading settings from database." BuildTool::Setting.all.each do |s| logger.debug2 " - Setting %s: %s" % [ s.name, s.value ] add_setting( s ) end end def add_module( mod ) @module[mod.name] = mod @modules << mod end def add_feature( feature ) @features[feature.path] = feature end end # Configuration class BuildToolConfiguration < Configuration def initialize( name, settings_file ) super() load( name, settings_file ) end ####### private ####### def load( name, settings_file ) # ### MIGRATION FROM 0.5 START # # First load the old settings file if it exists. For new style the settings are # correctly loaded from the database already. if ( not settings_file.nil? ) and settings_file.exist? logger.info "Loading old style settings file #{file}." YAML.load( File.open( file, 'r:UTF-8' ) ).each do |n, v| case n when 'RECIPE' n = "BUILD_TOOL.RECIPE" logger.debug( " %s='%s'" % [ n, v ] ) s = ( settings[ n ] or Setting.new( :name => n ) ) s.value = v add_setting( s ) when 'SETTINGS' v.each do |n, v| logger.debug( " %s='%s'" % [ n, v ] ) s = ( settings[ n ] or Setting.new( :name => n ) ) s.value = v add_setting( s ) end else logger.warn( 'Unknown setting %s found in %s' % [ n, file ] ) end end save() # Now rename the old file. file.rename( file.to_s + '_0.5' ) logger.info( "Renamed old style settings file to #{file}_0.5. Please delete later!" ) end # ### MIGRATION FROM 0.5 END # # Find the recipe. Will throw an exception if recipe is invalid. @recipe = BuildTool::Recipe.new( settings['BUILD_TOOL.RECIPE'].value ) logger.debug( 'Determined associated recipe as %s ( %s )' % [ recipe.name, recipe.global_path ] ) # Initialize the settings from the settings.yaml file from the recipe logger.debug( 'Loading configuration values from recipe:settings.yaml' ) YAML.load( File.open( recipe.global_config_file_path( 'settings.yaml' ), 'r:UTF-8' ) ).each do |v| sname = v['name'] logger.debug2( ' - Setting %s' % sname ) s = ( settings[ sname ] or Setting.new( :name => sname ) ) s.description = v['description'] s.default = v['default'] s.seen = true add_setting( s ) end # Load the recipe logger.debug( 'Loading recipe %s' % recipe.name ) recipe.load( name, self ) # Save possible changes to the configuration. save() # Migrate the recipe (if necessary) migrate() end end end # module BuildTool