# -*- coding: UTF-8 -*- require 'mj/tools/ssh' require 'active_record' require 'fileutils' module BuildTool # # Represents the information associated with a buildable module. # class Module < ActiveRecord::Base # The name of the module validates_uniqueness_of :name # Modules are sorted by name def <=>( other ) self.name <=> other.name end # A command can have many module events has_many :module_logs, :class_name => "BuildTool::History::ModuleLog", :order => :id, :foreign_key => :module, :primary_key => :name, :dependent => :destroy # Custom initialization after_initialize :my_initialize def my_initialize() if name.nil? raise StandardError, "Module name is required!" end @active = nil @patches = Array.new @local_path = nil @build_prefix = nil @remote_path = nil @environment = nil @build_system = nil @install_prefix = nil @is_template = false @vcs_configuration = nil @feature = nil @default_active = true @parent = nil @long_description = nil @description = nil @last_success = nil end # ### ATTRIBUTES # attr_accessor :feature attr_reader :patches # The previous version of this module. attr_accessor :parent # The default state of the feature attr_accessor :default_active # Is the module active by default? def default_active? @default_active end # Is the module active? def active? # If the module is explicitely de/activated that wins if active.nil? # if the module is active by default let the feature decide if present. if default_active? if @feature.nil? return true else return @feature.active? end else # The feature is not active by default. return false end else return active end end # not inherited def build_directory build_prefix_required.join("bld", local_path) end def build_prefix=( path ) if path path = Pathname.new( path.sub( /\$HOME/, '~' ) ) if path.to_s[0] != '~' and path.relative? raise ConfigurationError, "Build-prefix '#{path}' is relative!" end @build_prefix = path.expand_path else @build_prefix = nil end end def build_prefix # Return our own buildsystem if there is one return @build_prefix if @build_prefix # Return our parents buildsystem if there is one return parent.build_prefix if parent && parent.build_prefix # Nothing nil end # Will throw a exception if build_prefix is not set def build_prefix_required return self.build_prefix if self.build_prefix raise ConfigurationError, "No build prefix configured for #{name}!" end # Build system attr_writer :build_system def build_system # Return our own buildsystem if there is one return @build_system if @build_system # Return our parents buildsystem if there is one if parent && parent.build_system @build_system = parent.build_system.dup @build_system.module = self return @build_system end # Nothing nil end def our_build_system @build_system end # Will throw a exception if build_system is not set def build_system_required return self.build_system if self.build_system # *TODO* try to guess the build system raise ConfigurationError, "No build system configured for #{name}!" end # Return true if the last build failed for whatever reason def broken? return true if lastlog.empty? lastlog[0].module_logs.where( :module => name ).each do |e| if e.state != History::ModuleLog::FINISHED_SUCCESSFUL return true end end return false end def checkedout? vcs_required.checkedout? end def configured? if build_system return build_system.configured? else return false end end attr_writer :description def description @description end # Environment attr_writer :environment def environment # Return our own buildsystem if there is one return @environment if @environment # Return our parents buildsystem if there is one return parent.environment if parent && parent.environment # Nothing nil end # Will throw a exception if environment is not set def environment_required return self.environment if self.environment raise ConfigurationError, "No environment configured for #{name}!" end # Garbage collect def gc vcs.gc end # Installation prefix def install_prefix=( path ) if path path = Pathname.new( path.sub( /\$HOME/, '~' ) ) if path.to_s[0] != '~' and path.relative? raise ConfigurationError, "Install-prefix '#{path}' is relative!" end @install_prefix = path.expand_path else @install_prefix = nil end end def install_prefix # Return our own buildsystem if there is one return @install_prefix if @install_prefix # Return our parents buildsystem if there is one return parent.install_prefix if parent && parent.install_prefix # Nothing nil end # Will throw a exception if install_prefix is not set def install_prefix_required return self.install_prefix if self.install_prefix raise ConfigurationError, "No install prefix configured for #{name}!" end attr_writer :is_template def is_template? @is_template end def local_path=( local_path ) raise ConfigurationError, "Attempt to set local_path for module template #{name}" if is_template? @local_path = local_path end def local_path @local_path || name end attr_writer :long_description def long_description @long_description end # Remote path def remote_path=( remote_path ) raise ConfigurationError, "Attempt to set remote_path for module template #{name}" if is_template? @remote_path = remote_path end def remote_path @remote_path || name end def source_directory build_prefix_required.join("src", local_path) end alias source_directory_required source_directory # Return the logfile of the last compilation attempt def lastlog return BuildTool::History::CommandLog.last_by_module( name ) end # Returns a current state in string format def state return 'UNKNOWN' if lastlog.empty? lastlog[0].module_logs.where( :module => name ).each do |e| if e.state != History::ModuleLog::FINISHED_SUCCESSFUL return "#{e.state_str} (#{e.event})" end end return History::ModuleLog::state_str( History::ModuleLog::FINISHED_SUCCESSFUL ) end # Return the current state as one char. def state_char return '?' if lastlog.empty? lastlog[0].module_logs.where( :module => name ).each do |e| if e.state != History::ModuleLog::FINISHED_SUCCESSFUL return "#{e.state_char}" end end return History::ModuleLog::state_char( History::ModuleLog::FINISHED_SUCCESSFUL ) end def last_success return @last_success if @last_success lastlog = BuildTool::History::CommandLog.last_success_by_module( name ) if not lastlog.nil? @last_success = lastlog[:finished_at] end if @last_success.nil? @last_success = DateTime.new end return @last_success end def active_char if active? ANSI::Code.green { "A" } else "I" end end def vcs return vcs_configuration.vcs( self ) if vcs_configuration nil end attr_writer :vcs_configuration def vcs_configuration return @vcs_configuration if @vcs_configuration if parent && parent.vcs_configuration # puts "copying vcs for #{name} from #{parent.name}" vc = parent.vcs_configuration.class.new vc.copy_configuration( parent.vcs_configuration ) @vcs_configuration = vc return vc end nil end def vcs_configuration_required vc = vcs_configuration if vc.nil? raise ConfigurationError, "No version control system configure for module #{name}." end return vc end def vcs_required if source_directory.nil? raise ConfigurationError, "No source directory specified for module #{name}." end return vcs_configuration_required.vcs( self ) end # ### ACTIONS # def clean build_system_required.make( "clean" ) end def remove_build_directory build_system_required.remove_build_directory end def remove_source_directory build_system_required.remove_source_directory end # Clone the repository. def clone vcs_required.clone if !patches.empty? if !vcs.patches_supported? raise NotImplementedError, "Patch support not implemented for vcs #{vcs.name}" end vcs_required.apply_patches( patches ) end end # Fetch changes from the remote repository. Do not change the local # checkout. def fetch( verbose = false ) vcs_required.fetch( verbose = verbose ) end # Update the local changes with remote changes. Do not fetch changes # from the remote repository. def rebase( verbose ) vcs_required.rebase( verbose ) if !patches.empty? if !vcs.patches_supported? raise NotImplementedError, "Patch support not implemented for vcs #{vcs.name}" end vcs_required.apply_patches( patches ) end build_system_required.after_rebase end def configure build_system_required.configure end def reconfigure build_system_required.reconfigure end # Call make def make( target = nil ) build_system_required.make( target ) end def install( fast = false ) build_system_required.install( fast ) end # Cleanup after vcs_access def cleanup_after_vcs_access if MJ::Tools::SSH::has_keys? logger.info "" logger.info "#### Cleaning up the ssh keys" MJ::Tools::SSH::keys.each do |file| logger.info " - Removing key #{file}" end MJ::Tools::SSH::cleanup() end end # Check if an ssh-key is required and active it if necessary def prepare_for_fetch if vcs return vcs.prepare_for_fetch end true end # Check if an ssh-key is required and active it if necessary def prepare_for_rebase if vcs return vcs.prepare_for_rebase end true end # Check if an ssh-key is required and active it if necessary def ready_for_fetch if vcs return vcs.ready_for_fetch end true end def ready_for_rebase if vcs return vcs.ready_for_rebase end true end def prepare_for_installation return build_system_required.prepare_for_installation end def shell( command = nil, options = {} ) envvars = options[ :envvars ] || {} options[ :envvars ] = envvars.merge( { 'BT_SOURCE' => source_directory.to_s } ) environment.shell( command, options ) end def to_s "#{object_id}: #{name}" end end # Module end # module BuildTool