Oyranos/Code Generator

From ColourWiki

Table of contents

How it works

Overview

The oyAPIGenerator executable takes 3 arguments as input.

  • A directory the templates files using the grantlee format.
  • A source directory with all included code from the templates.
  • An output directory were all auto-generated code is created.

All are optional, and in an in source build none of them is actually used, because they get the default values (respectively):

  • ./templates
  • ./sources
  • ./API_generated

Actually, the extract_sources.sh shell script is used for convenience. It cleans the API_generated/ directory, creates the code, and then builds it. The paths work for an in source build.

The details

Template system

The templates are written in the Django template language (http://docs.djangoproject.com/en/dev/topics/templates/). A Django template is a plain text file with some specially formatted tags and variables. So, a template is just a C or C++ file that contains some extra {% tag %} and {{ variable }} embedded code that is replaced by the code generator. This is much like php code is replaced in html files. The built in tags and variables are documented here (http://docs.djangoproject.com/en/dev/ref/templates/builtins/). The Django template engine is written in python and can be used in stand alone mode (http://docs.djangoproject.com/en/dev/ref/templates/api/#configuring-the-template-system-in-standalone-mode). Instead, the generator is using the Grantlee template system (http://www.grantlee.org/), which uses the same template language, but is written in C++ and depends on Qt (http://qt.nokia.com/).

So, how is a new source code file auto-generated?

  • First the generator scans the templates/ directory for template files. They have a special file name in the form of file_name.template.ext, e.g. oyranos_module.template.h
  • Then the various template variables are loaded (taken from the source code metadata) and the template is rendered in memory
  • A new file is created in API_generated/ with the .template. removed, e.g. oyranos_module.h

Template files

There are mainly two kinds of template files.

  • A base template.
  • A child template that extends a base template. Most templates are child templates.

The child templates use the {% extends %} tag, which is how template inheritance (http://docs.djangoproject.com/en/dev/topics/templates/#template-inheritance) is implemented in the django language. There is a 1-1 relationship between the class inheritance of the Oyranos object system and the django template inheritance. For example, that means if oyCMMapi10_s inherits from oyCMMapiFilter_s, then CMMapi10_s.template.c will extend CMMapiFilter_s.template.c. And if oyCMMapiFilter_s extends oyCMMapi_s, then CMMapiFilters_s.template.h will extend CMMapi_s.template.h.


Inheritance Example Table - Common Class
Oyranos Objects Template Files
oyStruct_s Struct_s.template.h
Base_s.h
oyCMMapi_s CMMapi_s.template.h
oyCMMapiFilter_s CMMapiFilter_s.template.h
oyCMMapi10_s CMMapi10_s.template.h
The example above shows the inheritance graph for a public header file,
of a common Oyranos class.


Inheritance Example Table - List Class
Oyranos Objects Template Files
oyStruct_s
Base_s_.c
BaseList_s_.c
oyOptions_s_ Options_s_.template.c
The example table above shows the inheritance for a private implementation file,
of a special kind of Oyranos class - a "list" class. Note that the Base_s_.c
template does not extend a supposed oyStruct_s_.template.c file, because oyStruct_s
only has a public interface and so no private oyStruct_s_.* implementation files.

If in doubt, each generated source file has it's inheritance graph embedded, at the top of the @file doxygen comment.

Template file auto-generation

As stated above, there are two types of template files.

  • The base template files: Base_s*[ch] and BaseList_s*[ch]. These templates contain all the generic template code. All template files that are used to generate a C source file, inherit (directly or indirectly) one of the respective Base* files. These files should not be edited, unless a global change to all generated source files is needed.
  • Files named as <class_name>*.template.* . Each one of these files is used to auto-generate a source file in API_Generated/ (with the ".template" stripped from the filename). Each time the generator is run, it checks the sources/<class_name>.dox file for the [notemplates] tag. If it's not there, the <class_name>*.template.* file is auto-generated from the respective Class_s.[ch] file. Their usage is for overriding the inherited blocks, eg to insert include files, etc.

So, the second type of templates is autogenerated at first, and then edited by the user (if needed).

Code Organisation

File Structure

API_generated/

All the files in this directory are auto-generated from the templates.

Classes

Each class is implemented by four files.

oyClass_s.h
This is the public header file that exports the class API
oyClass_s.c
The implementation of all the public class parts.
oyClass_s_.h
Private declarations
oyClass_s_.c
Private definitions
Misc
oyranos_object.h
This file includes some vital oyranos headers and also contains
definitions necessary for, but not part of the object system.
It is included by all the oyClass_s.h headers.
oyranos_object_internal.h
This file includes some private oyranos headers and definitions necessary for,
but not part of the object system.
It is included by the private oyClass_s_.c implementation files.
oyTest.[cc|h]
Tests using the Qt test framework. For every generated class the basic functionality
like create, copy destroy is tested.
CMakeLists.txt
The generated file for building the code with cmake.
Modules
oyranos_generic.h
For everything that belongs to the Generic Objects API and is not part of a specific class.
oyranos_generic_internal.h
All Generic Objects API helper code, that is for internal usage only.
It is included by all the private implementations (oyClass_s_.c sources) that belong to the Generic Objects group.
oyranos_generic.c
For private/public functions that belong to the Generic Objects API but do not belong to a specific class.
oyranos_devices.h
Exports all public declarations that are part of the Device API.
oyranos_devices.c
The implementation for the Device API.
oyranos_devices_internal.[ch]
All Device API code that is for internal usage.
oyranos_module.h
All declarations that are part of the Module APIs, but not part of a specific class, and should be exported.
oyranos_module_internal.h
Used for all members of the Module APIs that are not part of any class and should not be exported.
It is included by all the private implementations (oyClass_s_.c sources) that belong to the Module APIs group.
oyranos_profile.h
All declarations that are are part of the Profile API, but not part of a specific class, and should be exported.
oyranos_conversion.h
All public declarations of the Conversion API's that do not belong to a specific class, go here.
oyranos_image.h
All public definitions of the Image API that do not belong to a specific class, go here.
oyranos_image_internal.h
All private definitions of the Image API that do not belong to a specific class, go here.

sources/

Here is all the source code that does not need to be inside the templates.

<class>.dox

This is the Doxygen description for the class, with some additional tags.

/** @struct  oyClass_s
 *  @ingroup some_group
 *  @extends oyStruct_s
 *  @brief   Brief description
 *  @internal
 *
 *  Multi line description
 *  @note New templates will not be created automaticly [notemplates]
 *  @note Create templates using "opaque pointer" [opaquepointer]
 *  @note This class holds a list of objects [list]
 *
 *  @version Oyranos: x.x.x
 *  @since   YYYY/MM/DD (Oyranos: x.x.x)
 *  @date    YYYY/MM/DD
 */

The basic idea is that the generator needs to know all kinds of information (metadata) about the class and these are provided here. Along with the doxygen tags, a few additional are also needed and are put in the @note tag.

[notemplates]
Each class has a template file for each generated source file.
At class creation, these are also created automaticly and are read-only.
When for any reason you want to override some default template block and
edit the class templates, change their permissions to read-write and
remove the [notemplates] tag
[list]
This tag specifies that the class is a special kind of class, a list of values. The
convention is that the class name is in plural (ends with s) and the list item type
is the class with the same name without the s. E.g. oyFilterPlugs_s -> oyFilterPlug_s
[opaquepointer]
This is an alternative way of the API and should not be used now.

NOTE: This file will be embedded in the oyClass_s.h file.

<class>.members.h

A list of the class members, but not inherited members from parent classes, e.g. for CMMapi6.members.h

 /** oyCMMapi4_s::context_type typic data; e.g. "oyDL" */
 char           * data_type_in;
 /** oyCMMapi7_s::context_type specific data; e.g. "lcCC" */
 char           * data_type_out;
 oyCMMdata_Convert_f oyCMMdata_Convert;

NOTE: This file will be embedded in the oyClass_s_.h file.

<class>.public.h

Here goes code for the oyClass_s.h public header file, e.g. for Options.public.h

typedef oyStruct_s * (*oyStruct_Copy_f ) ( oyStruct_s *, oyPointer );
typedef int       (*oyStruct_Release_f ) ( oyStruct_s ** );
typedef oyPointer (*oyStruct_LockCreate_f)(oyStruct_s * obj );
...
extern oyStruct_LockCreate_f   oyStruct_LockCreateFunc_;
extern oyLockRelease_f         oyLockReleaseFunc_;
extern oyLock_f                oyLockFunc_;
extern oyUnLock_f              oyUnLockFunc_;
...

NOTE: This file will be embedded in the oyClass_s.h file.

<class>.public_methods_declarations.h

All declarations of the class's public interface, that is not generated automaticly. E.g, for Profile.public_methods_declarations.h:

...
OYAPI oyProfile_s * OYEXPORT
                   oyProfile_FromMD5(  uint32_t          * md5,
                                       oyObject_s          object );
OYAPI int OYEXPORT
         oyProfile_GetChannelsCount ( oyProfile_s * colour );
OYAPI icSignature OYEXPORT
             oyProfile_GetSignature (  oyProfile_s       * profile,
                                       oySIGNATURE_TYPE_e  type );
...

NOTE: This file will be embedded in the oyClass_s.h file.

<class>.public_methods_definitions.c

The implementation file of the above public interface.

NOTE: This file will be embedded in the oyClass_s.c file.

<class>.private.h

All class definitions, enums, structs, etc that should not be exported should go here.

NOTE: This file will be embedded in the oyClass_s_.h file.

<class>.private_methods_declarations.h

NOTE: This file will be embedded in the oyClass_s_.h file.

<class>.private_methods_definitions.c

NOTE: This file will be embedded in the oyClass_s_.c file.

<class>.private_custom_definitions.c

NOTE: This file will be embedded in the oyClass_s_.c file.

templates/

Files
Base_s.h
Base_s.c
Base_s_.h
Base_s_.c
BaseList_s.h
BaseList_s.c
BaseList_s_.h
BaseList_s_.c
CMakeLists.template.txt
oyTest.template.h / oyTest.template.cc


Directories

The directories are named by the group name (@ingroup tag) and hold the template files of the classes that belong to that group.

Naming conventions

Function prototypes

Public member functions
All input/output variables use the public class interface. E.g:

OYAPI oyProfile_s* OYEXPORT
  oyProfile_Copy( oyProfile_s *profile, oyObject_s obj );

Private member functions
The input/output variables of the function class type use the private class interface. E.g:

oyProfile_s_*
  oyProfile_Copy_( oyProfile_s_ *profile, oyObject_s object);

The rest variables use their public interface. E.g:

oyProfileTag_s * oyProfile_GetTagByPos_ ( oyProfile_s_    * profile,
                                          int                 pos );
int                oyProfile_TagMoveIn_ ( oyProfile_s_      * profile,
                                          oyProfileTag_s   ** obj,
                                          int                 pos );

How to import a new class

Steps to use

1. Initial commit

Directory
sources/
  • (a) cp Class.dox <class>.dox & edit
  • (b) run generator
  • (c) Edit <class>.members.h
  • (h) run generator
NOTE Skip steps (c)&(h) for list classes.
git short comment
* Create skeleton files for oyClass_s

2. Import class members like enums,typedefs,...

Search for them in oyranos sources
grep 'memberof *oy<class>_s' * -B3
  • Private ones in sources/<class>.private.h
    git short comment
    [sources] Import oy<class>_s private [enums,typedefs,...]
  • Public ones in sources/<class>.public.h
    git short comment
    [sources] Import oy<class>_s public [enums,typedefs,...]
  • Add proper include files in oyClass_s.h and oyClass_s_.h
    Find them by trying to compile the object files
    cd /API_generated/
    make oyClass_s.o
    make oyClass_s_.o
    git short comment
    [templates] Add include files to oyClass_s.h

3. Implement constructor [oyClass_New]

files
sources/<class>.private_custom_definitions.c
git short comment
[review] [sources] Implement the constructor for oyClass_s

4. Implement copy constructor [oyClass_Copy]

Files:

sources/<class>.private_custom_definitions.c
git short comment
[review] [sources] Implement the copy constructor for oyClass_s

5. Implement destructor [oyClass_Release]

sources/

.private_custom_definitions.c

[review] [sources] Implement the destructor for oyClass_s

6. Import private methods for oyClass_s

sources/

.private_methods_declarations.h
.private_methods_definitions.c

[sources] Import private methods for oyClass_s

7. Adopt oyClass_s private methods

sources/

.private_methods_declarations.h
.private_methods_definitions.c

Make a different commit for each refactored function:

[review] [sources] Adopt oyClass_XXX_() to "hidden struct" interface.

8. Import public methods for oyClass_s

sources/

.public_methods_declarations.h
.public_methods_definitions.c

[sources] Import public methods for oyClass_s

9. Adopt oyClass_s public methods

sources/

.public_methods_declarations.h
.public_methods_definitions.c

Make a different commit for each refactored function:

[review] [sources] Adopt oyClass_XXX() to "hidden struct" interface.

Tips n' Tricks

How to insert include files

To use include files in e.g. oyClass_s.c override the LocalIncludeFiles block in the Class_s.template.c template file. There are 2 blocks available for every template.

LocalIncludeFiles
For local tree include files
GlobalIncludeFiles
For system files

For example, to include a system icc34.h file in oyProfile_s_.h, add the following in Profile_s_.template.h:

{% block GlobalIncludeFiles %}
{{ block.super }}
#include <icc34.h>
{% endblock %}

To also include oyStructList_s.h, oyProfileTag_s.h, oyConfig_s.h:

{% block LocalIncludeFiles %}
{{ block.super }}
#include "oyStructList_s.h"
#include "oyProfileTag_s.h"
#include "oyConfig_s.h"
{% endblock %}

The above blocks will render as (in oyProfile_s_.h):

...
#define oyProfilePriv_m( var ) ((oyProfile_s_*) (var))
#include <icc34.h>
#include <oyranos_object.h>
#include "oyStructList_s.h"
#include "oyProfileTag_s.h"
#include "oyConfig_s.h"
#include "oyProfile_s.h"
typedef struct oyProfile_s_ oyProfile_s_;
...

How to use the f_compare.sh script

This script can show you the differences of a function definition between two branches. The function should have a doxygen header (even an empty one).

Usage: extras/f_compare.sh [-h|-s] <function> [branch:]<file1> [branch:]<file2>
-h      Show this help message
-s      Show just the diff output

If branch is ommited, the current branch is assumed. Two files are created at the working directory, each having the function body as it's contents. Without the -s switch, vimdiff is run on those two files. They are named as: branchname-filename-funcname.c

So, why is this script useful? When a function is imported to the template system, it is copied from the oyranos_alpha.c or oyranos_cmm.c files, into the relevant one in the sources/ directory. That happens in the gsoc2011 branch. If the function is changed in master, before the working branch gets merged back, then the copied function in sources/ has to be updated, too.

For example, oyConfig_Find() is imported from oyranos_alpha.c in sources/Config.public_methods_definitions.c. If we are in the gsoc2011 branch and want to check if the function has been updated in master, we have to call the f_compare.sh script like:

 ./extras/f_compare.sh oyConfig_Find master:oyranos_alpha.c sources/Config.public_methods_definitions.c

Now you can add the updates to the function in vimdiff, save the file, and then replace the function in sources/Config.public_methods_definitions.c with the gsoc2011-Config.public_methods_definitions.c-oyConfig_Find.c file.