module OroGen::Gen::RTT_CPP::TaskContextGeneration
Module that is used to add code generation functionality to Spec::TaskContext
In the generated code, two classes are actually generated:
-
the auto-generated code is in
.orogen/tasks/[name]Base.cppand.orogen/tasks/[name]Base.hpp. These files define the various attributes related to the task context (i.e. port and attribute objects) in an[project.name]::[name]Baseclass. -
the user-visible code is in
tasks/[name].cppandtasks/[name].hpp. These files are the ones that define the[project.name]::[name]class, which is a direct subclass of the[project.name]::[name]Baseclass.
By default, the Base class derives from the
RTT::TaskContext class. This can be changed by using the
subclasses method.
For all task context objects (ports, properties, …) there is one attribute, of the right RTT class, added to the generated TaskContext subclass. The attribute name is always the _[object name], so for instance the presence of the following statement
output_port('time', 'double')
will cause a OutputPort<double> attribute named
_time to be added to the generated class (more specifically,
to the Base subclass).
Attributes
The code snippets that have been declared for the toplevel scope of TaskBase.hpp.
It is an array of [include_before, code_snippet] pairs, where
include_before is true if the code should be added before the
Task class definition and false if it should be added after.
- {String=>Set<String,#call>}
-
a set of code snippets that are
inserted in the base task class hooks. It is a mapping from a hook name (such as 'update') to the set of snippets. Snippets can be given as strings or as an object whose call method is going to return the code as a string
The code snippets that have been declared for the toplevel scope of TaskBase.cpp.
It is an array of [include_before, code_snippet] pairs, where
include_before is true if the code should be added before the
Task class definition and false if it should be added after.
The set of generation handlers. See add_generation_handler
- {String=>Set<String,#call>}
-
a set of code snippets that are
inserted in the task class hooks. It is a mapping from a hook name (such as 'update') to the set of snippets. Snippets can be given as strings or as an object whose call method is going to return the code as a string
Public Class Methods
# File lib/orogen/gen/tasks.rb, line 349 def initialize hooks = %w{configure start update error exception fatal stop cleanup} @base_hook_code = Hash.new hooks.each do |hook_name| @base_hook_code[hook_name] = Array.new end @user_hook_code = Hash.new hooks.each do |hook_name| @user_hook_code[hook_name] = Array.new end @generation_handlers = Array.new @base_methods = Array.new @user_methods = Array.new @base_members = Array.new @user_members = Array.new @base_header_code = Array.new @base_implementation_code = Array.new end
Internal helper that validates string, block as a
single code object
The returned object responds to call, where call returns the code string If block is given instead of a string, that block will be called and should return the code as a string
If both are given, an ArgumentError exception is raised.
# File lib/orogen/gen/tasks.rb, line 600 def self.validate_code_object(string, block) if string && block raise ArgumentError, "you can provide either a string or a block, not both" end if string lambda { string.to_str } else block end end
Public Instance Methods
# File lib/orogen/gen/tasks.rb, line 956 def add_base_construction(kind, name, code, &block) add_base_member(kind, name).constructor(code, &block) end
# File lib/orogen/gen/tasks.rb, line 960 def add_base_destruction(kind, name, code) add_base_member(kind, name).destructor(code, &block) end
Add some code that needs to be added to the toplevel scope in TaskBase.hpp
# File lib/orogen/gen/tasks.rb, line 645 def add_base_header_code(string, include_before = true, &block) code = TaskContextGeneration.validate_code_object(string, block) @base_header_code << [include_before, code] end
Add some code that needs to be added to the toplevel scope in TaskBase.cpp
# File lib/orogen/gen/tasks.rb, line 652 def add_base_implementation_code(string, include_before = true, &block) code = TaskContextGeneration.validate_code_object(string, block) @base_implementation_code << [include_before, code] end
Add a code snippet to the generated Base class declaration
# File lib/orogen/gen/tasks.rb, line 936 def add_base_member(kind, name, type = nil) if @base_members.any? { |m| m.kind == kind && m.name == name } raise ArgumentError, "duplicate name #{kind}:#{name} used for base member" end m = GeneratedMember.new(self, kind, name, type) @base_members << m m end
Define a new method on the Base class of this task
return_type is a string representing the C++ return type for
this method, name the method name and signature
the arguments as they would be written in C++, without the parenthesis.
For instance
add_base_method("bool", "isCompleted", "int arg")
Generates the method
bool isCompleted(int arg);
Note that you do not have to do this explicitely if add_user_method is called: add_user_method will add a pure virtual method to the base class
It returns an instance of GeneratedMethod that can be used to setup the method further
# File lib/orogen/gen/tasks.rb, line 876 def add_base_method(return_type, name, signature = "") m = add_method("base_methods", return_type, name, signature) m.in_base = true m end
This function adds @param code [String] AFTER the already defined code on the @param name [String] given method
# File lib/orogen/gen/tasks.rb, line 890 def add_code_to_base_method_after(name,code) self_base_methods.each do |p| if p.name == name p.add_to_body_after(code) return self end end raise ArgumentError "Method #{name} could not be found" end
This function adds @param code [String] BEFORE the already defined code on the @param name [String] given method
# File lib/orogen/gen/tasks.rb, line 902 def add_code_to_base_method_before(name,code) self_base_methods.each do |p| if p.name == name p.add_to_body_before(code) return self end end raise ArgumentError "Method #{name} could not be found" end
Registers a method that should be called at generation time
The provided block will be called at the beginning of the generation process. If the block expects an argument, it will be given the task object
# File lib/orogen/gen/tasks.rb, line 639 def add_generation_handler(&block) generation_handlers << block end
Helper method for {#add_base_method} and {#add_user_method}
# File lib/orogen/gen/tasks.rb, line 845 def add_method(kind, return_type, name, signature) self_set = send("self_#{kind}") if !name.respond_to?(:to_str) raise ArgumentError, "expected a string for 'name', got #{name} (#{name.class})" elsif self_set.any? { |m| m.name == name } raise ArgumentError, "there is already a method called #{name} defined at this level" end m = GeneratedMethod.new(self, return_type, name, signature) self_set << m m end
Add a code snippet to the generated user class declaration
# File lib/orogen/gen/tasks.rb, line 947 def add_user_member(kind, name, type = nil) if @user_members.any? { |m| m.kind == kind && m.name == name } raise ArgumentError, "duplicate name #{kind}:#{name} used for base member" end m = GeneratedMember.new(kind, name, type) @user_members << m m end
Define a new method on the user-part class of this task
It will also add a pure-virtual method with the same signature on the Base class, to ensure that the user does define the method on its side.
It returns an instance of GeneratedMethod that can be used to setup the method further
# File lib/orogen/gen/tasks.rb, line 921 def add_user_method(return_type, name, signature = "") if !has_base_method?(name) # Add a pure virtual method to remind the user that he # should add it to its implementation m = add_base_method(return_type, name, signature) m.doc "If the compiler issues an error at this point, it is probably that", "you forgot to add the corresponding method to the #{self.name} class." end add_method("user_methods", return_type, name, signature) end
Returns the name without an eventual library name
# File lib/orogen/gen/tasks.rb, line 336 def basename self.name.split("::").last end
This method generates the relative basepath for generation of all files
# File lib/orogen/gen/tasks.rb, line 312 def basepath s = File.join(namespace.split("::").join(File::SEPARATOR)) if !s.empty? s = s + File::SEPARATOR end s end
# File lib/orogen/gen/tasks.rb, line 370 def check_uniqueness(name) super # Check if that name is a method name in orocos.rb as well ... # To warn about name clashes if @orocos_rb.nil? begin require 'orocos' @orocos_rb = true rescue Exception @orocos_rb = false end end if name.to_str != 'state' && @orocos_rb && !project.kind_of?(ImportedProject) if Orocos::TaskContext.instance_methods.find { |n| n.to_s == name.to_str } STDERR.puts "WARN: #{name} is a method name used in orocos.rb" STDERR.puts "WARN: if you keep that name, you will not be able to use shortcut access in orocos.rb" STDERR.puts "WARN: for instance, for a property, you will have to do" STDERR.puts "WARN: value = my_task.property('#{name}').read(new_value)" STDERR.puts "WARN: instead of the shorter and clearer" STDERR.puts "WARN: value = my_task.#{name}" end end end
# File lib/orogen/gen/tasks.rb, line 345 def class_name name end
# File lib/orogen/gen/tasks.rb, line 473 def create_dynamic_updater(name, superclass_has_dynamic) add_base_method("bool",name) if superclass_has_dynamic #Call the superclass method if needed, returning false if it fail. Otherwise check our dynamic properties #they are generated in register_for_generation, or returning true in the end add_code_to_base_method_after name," return #{superclass.name}::#{name}();\n" else #No superclass code, so return simply true add_code_to_base_method_after name," return true;\n" end end
# File lib/orogen/gen/tasks.rb, line 327 def full_namespace self.name.split("::")[0..-2].join("::") end
Generate the code files for this task. This builds to classes:
-
a #{task.name}Base class in .orogen/tasks/#{task.name}Base.{cpp,hpp} which is the automatically generated part of the task.
-
a #{task.name} class in tasks/#{task.name}.{cpp,hpp} which is the user-provided part of the task. This class is a public subclass of the Base class.
# File lib/orogen/gen/tasks.rb, line 494 def generate return if external_definition? if superclass.name == "RTT::TaskContext" hidden_operation("getModelName"). returns("std::string"). doc("returns the oroGen model name for this task"). base_body(" return \"#{name}\";"). runs_in_caller_thread add_base_implementation_code("#ifdef HAS_GETTID\n#include <sys/syscall.h>\n#endif") hidden_operation("__orogen_getTID"). returns("int"). base_body(" #ifdef HAS_GETTID\nreturn syscall(SYS_gettid);\n#else\nreturn 0;\n#endif"). doc("returns the PID for this task") else add_base_method("std::string", "getModelName",""). body(" return \"#{name}\";") end if(has_dynamic_properties?) create_dynamic_updater("updateDynamicProperties",superclass.has_dynamic_properties?) end if(has_dynamic_attributes?) create_dynamic_updater("updateDynamicAttributes",superclass.has_dynamic_attributes?) end extensions.each do |ext| if ext.respond_to?(:early_register_for_generation) OroGen.warn "The plugin #{ext.name} defines a \"early_register_for_generation\" hook, this got renamed to \"pre_generation_hook\" please adapt the code or contact the developer." ext.early_register_for_generation(self) end if ext.respond_to?(:pre_generation_hook) ext.pre_generation_hook(self) end end self_properties.each(&:register_for_generation) #needs to be called before operations, because it adds code to them self_attributes.each(&:register_for_generation) new_operations.each(&:register_for_generation) self_ports.each(&:register_for_generation) extensions.each do |ext| if ext.respond_to?(:generation_hook) ext.generation_hook(self) end if ext.respond_to?(:register_for_generation) OroGen.warn "The plugin #{ext.name} defines a \"register_for_generation\" hook, this got renamed to \"generation_hook\" please adapt the code or contact the developer." ext.register_for_generation(self) end end generation_handlers.each do |h| if h.arity == 1 h.call(self) else h.call end end # Make this task be available in templates as 'task' task = self extensions.each do |ext| if ext.respond_to?(:post_generation_hook) ext.post_generation_hook(self) end end base_code_cpp = Generation.render_template 'tasks', 'TaskBase.cpp', binding base_code_hpp = Generation.render_template 'tasks', 'TaskBase.hpp', binding Generation.save_automatic "tasks",basepath, "#{basename}Base.cpp", base_code_cpp Generation.save_automatic "tasks",basepath, "#{basename}Base.hpp", base_code_hpp code_cpp = Generation.render_template "tasks", "Task.cpp", binding code_hpp = Generation.render_template "tasks", "Task.hpp", binding file_cpp = Generation.save_user "tasks",basepath, "#{basename}.cpp", code_cpp file_hpp = Generation.save_user "tasks",basepath, "#{basename}.hpp", code_hpp # Validate constructors of old task files validate_constructors(file_cpp, basename) validate_constructors(file_hpp, basename) fake_install_dir = File.join(project.base_dir, AUTOMATIC_AREA_NAME, project.name) FileUtils.mkdir_p fake_install_dir FileUtils.mkdir_p File.join(fake_install_dir, basepath) FileUtils.ln_sf File.join(project.base_dir, "tasks",basepath, "#{basename}.hpp"), File.join(fake_install_dir, basepath, "#{basename}.hpp") FileUtils.ln_sf File.join(project.base_dir, AUTOMATIC_AREA_NAME, "tasks",basepath, "#{basename}Base.hpp"), File.join(fake_install_dir, basepath ,"#{basename}Base.hpp") self end
Returns true if name is a method defined with add_base_method
or add_user_method
# File lib/orogen/gen/tasks.rb, line 884 def has_base_method?(name) all_base_methods.any? { |m| m.name == name } end
The name of the header file containing the C++ code which defines this task context
# File lib/orogen/gen/tasks.rb, line 322 def header_file project_name = self.name.split("::").first File.join(project_name.downcase, basepath, "#{basename}.hpp") end
Call to add some code to the generated hooks in the Base task classes
# File lib/orogen/gen/tasks.rb, line 668 def in_base_hook(hook, string = nil, &block) in_hook(@base_hook_code, hook, string, &block) end
Call to add some code to the generated hooks in the Base task classes
# File lib/orogen/gen/tasks.rb, line 674 def in_user_hook(hook, string = nil, &block) in_hook(@user_hook_code, hook, string, &block) end
Returns the set of types that are used to define this task context, as an array of subclasses of Typelib::Type.
# File lib/orogen/gen/tasks.rb, line 426 def interface_types each_interface_type.to_a end
True if we are generating for Linux
# File lib/orogen/gen/tasks.rb, line 341 def linux?; project.linux? end
# File lib/orogen/gen/tasks.rb, line 331 def namespace self.name.split("::")[1..-2].join("::") end
# File lib/orogen/gen/tasks.rb, line 932 def self_base_members(&block); @base_members end
# File lib/orogen/gen/tasks.rb, line 933 def self_user_members(&block); @user_members end
The set of task libraries that are required by this task context
This is the set of task libraries that implement our superclasses
# File lib/orogen/gen/tasks.rb, line 416 def used_task_libraries project.used_task_libraries.find_all do |tasklib| tasklib.self_tasks.any? do |task| implements?(task.name) end end end
Returns the set of typekits that define the types used in this task's interface. They are required at compile and link time because of the explicit instanciation of interface templates (ports, …)
# File lib/orogen/gen/tasks.rb, line 434 def used_typekits types = interface_types project.used_typekits.find_all do |tk| types.any? do |type| tk.includes?(type.name) end end.to_set end
Validate the constructors of the task files regarding an fixed initial state This might be the case when needs_configuration has been specified on a later stage, but still the constructors need to be changed
# File lib/orogen/gen/tasks.rb, line 448 def validate_constructors(filename, taskname) if ! fixed_initial_state? return end File.open(filename) do |file| begin while true line = file.readline begin if Regexp.new(taskname + "\(.*\)").match(line) if $1 =~ /TaskCore::TaskState/ puts "\nWarning: 'needs_configuration' has been specified for the task '#{taskname}', but the task's constructor has not been updated after this change.\n\n Note: setting a TaskState is not allowed in combination with using 'needs_configuration'.\n Constructors in #{filename} and corresponding files require adaption." end end rescue ArgumentError => e STDERR.puts "[CRITICAL] Could not parse \'#{line}\' maybe it contains invalid chars?" raise e end end rescue EOFError end end end
True if we are generating for Xenomai
# File lib/orogen/gen/tasks.rb, line 343 def xenomai?; project.xenomai? end