module Origen # In Origen v2 this class was introduced to formally co-ordinate application level # configuration of Origen. # # == Configuration # # See Origen::Application::Configuration for the available options. class Application autoload :Configuration, 'origen/application/configuration' autoload :Target, 'origen/application/target' autoload :Environment, 'origen/application/environment' autoload :PluginsManager, 'origen/application/plugins_manager' autoload :Plugins, 'origen/application/plugins' autoload :LSF, 'origen/application/lsf' autoload :Runner, 'origen/application/runner' autoload :LSFManager, 'origen/application/lsf_manager' autoload :Release, 'origen/application/release' autoload :Deployer, 'origen/application/deployer' autoload :VersionTracker, 'origen/application/version_tracker' autoload :CommandDispatcher, 'origen/application/command_dispatcher' autoload :WorkspaceManager, 'origen/application/workspace_manager' require 'origen/users' include Origen::Users attr_accessor :name attr_accessor :namespace class << self def inherited(base) # Somehow using the old import system and version file format we can get in here when # loading the version, this can be removed in future when the imports API is retired unless caller[0] =~ /version.rb.*/ if base.to_s == 'OrigenGlobalApplication' app = base.instance app.root = Origen.root Origen.register_application(app) else root = Pathname.new(caller[0].sub(/(\\|\/)?config(\\|\/)application.rb.*/, '')).realpath app = base.instance app.root = root.to_s if Origen.plugins_loaded? && !Origen.loading_top_level? # This situation of a plugin being loaded after the top-level app could occur if the app # doesn't require the plugin until later, in that case there is nothing the plugin owner # can do and we just need to accept that this can happen. # Origen.log.warning "The #{app.name} plugin is using a non-standard loading mechanism, upgrade to a newer version of it to get rid of this warning (please report a bug to its owner if this warning persists)" Origen.register_application(app) # Origen.app.plugins << app else Origen.register_application(app) end app.add_lib_to_load_path! # Also blow this cache as a new app has just been added @apps_by_namespace = nil end end end def instance @instance ||= new end def respond_to?(*args) super || instance.respond_to?(*args) end # Returns the application instance (i.e. main app or the plugin) that owns the given class/module # (literal, string or symbol representation is accepted) or object instance. # Returns nil if no matching Origen application can be found. # # Origen::Application.from_namespace(MyApp) # => # Origen::Application.from_namespace(MyApp::MyClass) # => # Origen::Application.from_namespace('MyApp::MyClass') # => # Origen::Application.from_namespace() # => def from_namespace(item) unless item.is_a?(String) if item.is_a?(Module) || item.is_a?(Class) || item.is_a?(Symbol) item = item.to_s else # Assume to be an object instance in this case item = item.class.to_s end end namespace = item.split('::').first @apps_by_namespace ||= {} @apps_by_namespace[namespace] ||= begin return Origen.app if Origen.app.namespace == namespace Origen.app.plugins.each do |plugin| return plugin if plugin.namespace == namespace end nil end end protected def method_missing(*args, &block) instance.send(*args, &block) end end # A simple class to load all rake tasks available to an application, a class is used here # to avoid collision with the Rake namespace method class RakeLoader require 'rake' include Rake::DSL def load_tasks $VERBOSE = nil # Don't care about world writable dir warnings and the like require 'colored' # Load all Origen tasks first Dir.glob("#{Origen.top}/lib/tasks/*.rake").sort.each do |file| load file end # Now the application's own tasks if Origen.app.origen_core? Dir.glob("#{Origen.root}/lib/tasks/private/*.rake").sort.each do |file| load file end else # New application dir structure support Dir.glob("#{Origen.root}/app/lib/tasks/*.rake").sort.each do |file| load file end Dir.glob("#{Origen.root}/lib/tasks/*.rake").sort.each do |file| load file end end # Finally those that the plugin's have given us ([Origen.app] + Origen.app.plugins).each do |plugin| namespace plugin.name do # New application dir structure support Dir.glob("#{plugin.root}/app/lib/tasks/shared/*.rake").sort.each do |file| load file end Dir.glob("#{plugin.root}/lib/tasks/shared/*.rake").sort.each do |file| load file end end end end end # @api private # # Returns a lookup table for all block definitions (app/blocks) that the app contains def blocks_files @blocks_files ||= begin files = {} block_dir = Pathname.new(File.join(root, 'app', 'blocks')) if block_dir.exist? block_dir.children.each do |item| if item.directory? _add_block_files(files, block_dir, item) end end end files end end # @api private def _add_block_files(files, block_dir, current_dir, sub_block = false) fields = current_dir.relative_path_from(block_dir).to_s.split('/') fields.delete('derivatives') fields.delete('sub_blocks') path = fields.join('/') files[path] ||= {} files[path][:_sub_block] = true if sub_block Dir.glob(current_dir.join('*.rb')).each do |file| file = Pathname.new(file) type = file.basename('.rb').to_s.to_sym unless type == :model || type == :controller files[path][type] ||= [] files[path][type] << file.to_s end end derivatives = current_dir.join('derivatives') if derivatives.exist? derivatives.children.each do |item| if item.directory? _add_block_files(files, block_dir, item) end end end sub_blocks = current_dir.join('sub_blocks') if sub_blocks.exist? sub_blocks.children.each do |item| if item.directory? _add_block_files(files, block_dir, item, true) end end end end def current_job current_jobs.last end def current_jobs @current_jobs ||= [] end # Load all rake tasks defined in the application's lib/task directory def load_tasks RakeLoader.new.load_tasks end # Returns a revision controller instance (e.g. Origen::RevisionControl::Git) which has # been configured to point to the local workspace and the remote repository # as defined by Origen.app.config.rc_url. If the revision control URL has not been # defined, or it does not resolve to a recognized revision control system, then this # method will return nil. def revision_controller(options = {}) if current? if config.rc_url begin if config.rc_url =~ /^sync:/ @revision_controller ||= RevisionControl::DesignSync.new( local: root, remote: config.rc_url ) elsif config.rc_url =~ /git/ @revision_controller ||= RevisionControl::Git.new( local: root, # If a workspace is based on a fork of the master repo, config.rc_url may not # be correct remote: (options[:uninitialized] ? config.rc_url : (RevisionControl::Git.origin || config.rc_url)), allow_local_adjustment: true ) end # The rc_url has been defined, but the initial app checkin has not been done yet rescue RevisionControlUninitializedError @revision_controller = nil end elsif config.vault @revision_controller ||= RevisionControl::DesignSync.new( local: root, remote: config.vault ) end else fail "Only the top-level application has a revision controller! #{name} is a plugin" end end alias_method :rc, :revision_controller # This callback handler will fire once the main app and all of its plugins have been loaded def on_loaded config.log_deprecations end # Convenience method to check if the given application instance is Origen core def origen_core? name.to_s.symbolize == :origen_core end def inspect "" end def root=(val) @root = Pathname.new(val) end def require_environment! Origen.deprecate 'Calling app.require_environment! is no longer required, the app environment is now automtically loaded when Origen.app is called' end # Returns a full path to the root directory of the given application # # If the application instance is a plugin then this will point to where # the application is installed within the imports directory def root @root end # Returns a path to the imports directory (e.g. used by the remotes and similar features) for the # application. e.g. if the app live at /home/thao/my_app, then the imports directory will typically # be /home/thao/.my_app_imports_DO_NOT_HAND_MODIFY # # Origen will ensure that this directory is outside of the scope of the current application's revision # control system. This prevents conflicts with the revision control system for the application and those # used to import 3rd party dependencies def imports_directory workspace_manager.imports_directory end alias_method :imports_dir, :imports_directory # Returns a path to the remotes directory for the application. e.g. if the app live # at /home/thao/my_app, then the remotes directory will typically # be /home/thao/.my_app_remotes_DO_NOT_HAND_MODIFY # # Origen will ensure that this directory is outside of the scope of the current application's revision # control system. This prevents conflicts with the revision control system for the application and those # used to import 3rd party dependencies def remotes_directory workspace_manager.remotes_directory end alias_method :remotes_dir, :remotes_directory # Returns the namespace used by the application as a string def namespace @namespace ||= self.class.to_s.split('::').first.gsub('_', '').sub('Application', '') end # Returns array of email addresses in the DEV maillist file def maillist_dev maillist_parse(maillist_dev_file) end # Returns array of email addresses in the PROD maillist file def maillist_prod maillist_parse(maillist_prod_file) end # Returns default location of DEV maillist file (customize locally if needed) def maillist_dev_file Origen.app.root.to_s + '/config/maillist_dev.txt' end # Returns default location of PROD maillist file (customize locally if needed) def maillist_prod_file Origen.app.root.to_s + '/config/maillist_prod.txt' end # Parses maillist file and returns an array of email address def maillist_parse(file) maillist = [] # if file doesn't exist, just return empty array, otherwise, parse for emails if File.exist?(file) File.readlines(file).each do |line| if index = (line =~ /\#/) # line contains some kind of comment # check if there is any useful info, ignore it not unless line[0, index].strip.empty? maillist << Origen::Users::User.new(line[0, index].strip).email end else # if line is not empty, generate an email unless line.strip.empty? maillist << Origen::Users::User.new(line.strip).email end end end end maillist end # Returns an array of users who have subscribed for production release # notifications for the given application on the website def subscribers_prod if server_data @subscribers_prod ||= server_data[:subscribers_prod].map { |u| User.new(u[:core_id]) } else [] end end # Returns an array of users who have subscribed for development release # notifications for the given application on the website def subscribers_dev if server_data @subscribers_dev ||= server_data[:subscribers_dev].map { |u| User.new(u[:core_id]) } else [] end end # Returns the server data packet available for the given application, # returns nil if none is found def server_data if name == :origen @server_data ||= Origen.client.origen else @server_data ||= Origen.client.plugins.find { |p| p[:origen_name].downcase == name.to_s.downcase } end end # Returns true if the given application instance is the # current top level application def current? # If this is called before the plugins are loaded (i.e. by a plugin's application file), then # it is definitely not the top-level app if Origen.application_loaded? Origen.app == self else Origen.root == root end end alias_method :standalone?, :current? alias_method :running_standalone?, :current? # Returns true if the given application instance is # the current plugin def current_plugin? if Origen.application_loaded? Origen.app.plugins.current == self else puts <<-END current_plugin? cannot be used at this point in your code since the app is not loaded yet. If you are calling this from config/application.rb then you can only use current_plugin? within a block: # Not OK if current_plugin? config.output_directory = "#{Origen.root}/output/dir1" else config.output_directory = "#{Origen.root}/output/dir2" end # OK config.output_directory do if current_plugin? "#{Origen.root}/output/dir1" else "#{Origen.root}/output/dir2" end end END exit 1 end end # Returns the current top-level object (the DUT) def top_level @top_level ||= begin t = toplevel_listeners.first t.controller ? t.controller : t if t end end def listeners_for(*args) callback = args.shift max = args.first.is_a?(Numeric) ? args.shift : nil options = args.shift || {} options = { top_level: :first }.merge(options) listeners = callback_listeners if Origen.top_level listeners -= [Origen.top_level.model] if options[:top_level] if options[:top_level] == :last listeners = listeners + [Origen.top_level] else listeners = [Origen.top_level] + listeners end end end listeners = listeners.select { |l| l.respond_to?(callback) }.map do |l| if l.try(:is_an_origen_model?) l.respond_to_directly?(callback) ? l : l.controller else l end end if max && listeners.size > max fail "You can only define a #{callback} callback #{max > 1 ? (max.to_s + 'times') : 'once'}, however you have declared it #{listeners.size} times for instances of: #{listeners.map(&:class)}" end listeners end def version(options = {}) @version = nil if options[:refresh] return @version if @version if Origen.running_globally? @version = Origen.version else load File.join(root, 'config', 'version.rb') if defined? eval(namespace)::VERSION @version = Origen::VersionString.new(eval(namespace)::VERSION) else # The eval of the class is required here as somehow when plugins are imported under the old # imports system and with the old version file format we can end up with two copies of the # same class constant. Don't understand it, but it is fixed with the move to gems and the # namespace-based version file format. @version = Origen::VersionString.new(eval(self.class.to_s)::VERSION) end end @version end # Returns the release note for the current or given application version def release_note(version = Origen.app.version.prefixed) version = VersionString.new(version) version = version.prefixed if version.semantic? capture = false note_started = false note = [] File.readlines("#{Origen.root}/doc/history").each do |line| line = line.strip if capture if note_started if line =~ /^Tag: #{version}Tag: #{version}Tag: #{version}Tag: ([^<]*)fail method with the addition of prepending the application name. # Prepended message: 'Fail in app.name: ' # If no message if provided, message is set to 'Fail in app.name' # @param message [String] Message to print with the exception. If the message option is nil, a default message will be used instead. # @param exception_class [Class] Custom Exception class to throw. May require the full namespace, e.g. Origen::OrigenError instead of just OrigenError. # @raise [RuntimeError, exception_class] Option exception_class is raised, defaulting to RuntimeError. def fail(message: nil, exception_class: RuntimeError) message.nil? ? message = "Fail in #{name}" : message = "Fail in #{name}: #{message}" e = exception_class.new(message) # If the caller is Origen.app.fail!, remove this caller from the backtrace, leaving where Origen.app.fail! was called. # As an aside, if there's an exception raised in Origen.app.fail!, then that would actually raise a Kernel.fail, so there's no concern with masking # out a problem with Origen.app.fail! by doing this. if caller[0] =~ (/lib\/origen\/application.rb:\d+:in `fail!'/) e.set_backtrace(caller[1..-1]) else e.set_backtrace(caller) end Kernel.fail(e) end # Similar to Origen.app.fail, but will instead print the message using Origen.log.error and exit the current process (using exit 1) # UNLESS --debug is used. In those cases, exit will not be used and instead this will behave the same as {Origen::Application#fail}. # Purpose here is to allow fail! for normal usage, but provide more details as to where fail! was used when running in debug. # @param message [String] Message to print with the exception. If the message option is nil, a default message will be used instead. # @param exception_class [Class] Custom Exception class to throw. May require the full namespace. # @param exit_status [Integer] Exit status to use when exiting the application. # @raise [RuntimeError, SystemExit, exception_class] When debug is disabled, SystemExit will be raised. # When debug is enabled, exception_class will be raised, defaulting to RuntimeError. def fail!(message: nil, exception_class: RuntimeError, exit_status: 1) if Origen.debug? # rubocop:disable Style/RedundantSelf self.fail(message: message, exception_class: exception_class) # rubocop:enable Style/RedundantSelf else begin # rubocop:disable Style/RedundantSelf self.fail(message: message, exception_class: exception_class) # rubocop:enable Style/RedundantSelf rescue exception_class => e Origen.log.error(e.message) exit exit_status end end end # This method is called just after an application inherits from Origen::Application, # allowing the developer to load classes in lib and use them during application # configuration. # # class MyApplication < Origen::Application # require "my_backend" # in lib/my_backend # config.i18n.backend = MyBackend # end def add_lib_to_load_path! #:nodoc: [root.join('lib'), root.join('vendor', 'lib'), root.join('app', 'lib')].each do |path| $LOAD_PATH.unshift(path.to_s) if File.exist?(path) && !$LOAD_PATH.include?(path.to_s) end end end end