Component Definition .cdef

This topic provides details about Legato's Component Definition file.

Component.cdef files can contain these sections:

assets

The asset section has now been removed as it is not supported with AVC 2.0. See the AirVantage 1.0 to 2.0 Migration Guide to see the changes between AVC 1.0 and AVC 2.0, including the new way to model the asset data.

bundles

Lists additional files or directories to be copied from the build host into the App so they’re available to the App at runtime (e.g., audio files, web pages, executable scripts or programs built using some external build system).

bundles:
{
file:
{
// Include the web server executable (built using some other build tool) in the app's /bin.
[x] 3rdParty/webServer/bin/wwwServ /bin/
 
// Put the company logo into the app's /var/www/ for read-only access by the web server.
images/abcCorpLogo.jpg /var/www/
 
// Make the appropriate welcome page for the product appear at /var/www/index.html.
webContent/$PRODUCT_ID/welcome.html /var/www/index.html
 
// Create a file to record persistent custom audio messages into.
[w] audio/defaultMessage.wav /usr/share/sounds/customMessage.wav
}
 
dir:
{
// Recursively bundle the directory containing all the audio files into the app.
// It will appear to the app read-only under /usr/share/sounds/.
audio /usr/share/sounds
}
}

Three things need to be specified for each file or directory:

  • access permissions
  • build system path
  • target path

Access permissions - any combination of one or more of the following letters, enclosed in square brackets:

  • r = readable
  • w = writeable
  • x = executable

If permissions values are not specified, then read-only ([r]) is the default.

Note
For security reasons, files and directories cannot be both writable and executable.

Directories always have executable permission set so they can be traversed. Setting the [x] permission in the dir: subsection causes the files under the directory to be made executable.

Setting [w] in the dir: subsection causes all files under that directory to be writable, but the directory itself will not be writable.

Note
Check with your module vendor to see if your target supports disk quotas. If not, directories in the persistent (flash) file system are never made writable because the on-target flash file system does not support usage quotas (yet).

Build system path - file system path on the build PC where the file is located at build time.

The path can be relative to the directory where the .adef file is located.

Note
Environment variables can be used inside these paths.

Target path - file system path on the target where the file will appear at runtime.

It's an absolute path inside the app's sandbox file system.

If the path ends with '/', it means the directory path where the source object (file or directory) will be copied. The destination object will have the same name as the source object.

If the path doesn't end in a '/', it's a full destination object path. The destination object could have a different name than the source object.

Note
If the app is running unsandboxed, the bundled files and directories can be found in their installation location under /legato/systems/current/apps/xxxx, where xxxx is replaced by the app name.

Quoting Paths

File paths can be enclosed in quotation marks (either single ' or double "). This is required when the file path contains spaces or comment start sequences

"//" or  "/*"

File Ownership and Set-UID Bits

When the app is installed on a target:

  • the owner and group are set to root on all files in the app.
  • the setuid bit is cleared on everything in the app.

cflags

Provides a way to specify command-line arguments to pass to the compiler when compiling C source code files. These flags will be added to the flags specified on the command-line and in other definition files.

Flags are separated by whitespace.

cflags:
{
-g -O0
-DDEBUG=1
}

cxxflags

Provides a way to specify command-line arguments to pass to the compiler when compiling C++ source code files. These flags will be added to the flags specified on the command-line and in other definition files.

Flags are separated by whitespace.

cxxflags:
{
-std=c++0x
-g -O0
}

externalBuild

Specifies a list of commands required to build this component using an external build process. These commands will be executed in order by the shell, similarly to make recipes. This may be used to build third-party libraries which need to be built from source but are not part of of the target filesystem.

Note
As with make, sh is used as the shell for executing commands regardless of what shell the user uses. If an alternate shell or long scripts are needed, these can be placed in a separate shell script, and that shell script called here.

If externalBuild: section is used in a component, the sources: section cannot be used.

Note
CC, CXX, CFLAGS, CXXFLAGS, and LDFLAGS are set to the target compiler and compiler flags when running the external build commands. The current directory for the command will be the component's object directory. This makes it easy to build and configure packages based on GNU autotools. See example below.
Warning
Any files which need to be copied into the App must be listed in a bundles: file: section. To avoid compiling successfully even though expected files are missing, bundles: dir: sections cannot be used.

To use a library built from a component with an externalBuild section, two things need to be done. The component needs to be added to the requires: component: section, and the library has to be added to requires: lib: section.

For example, suppose a component requires the libwarpspeed.so library, which is a third-party library built using autoconf and make. First create a Component.cdef for the warpspeed library in that library's directory:

bundles:
{
file:
{
[x] libwarpspeed.so /usr/lib
}
}
 
externalBuild:
{
"${CURDIR}/configure --target=$${TARGET_TOOLCHAIN_PREFIX%-}"
make
}

Then add

requires:
{
component:
{
warpspeed
}
 
lib:
{
libwarpspeed.so
}
}

to the Component.cdef of any component that needs this library.

ldflags

Linker flags provide a way to specify command-line arguments to pass to the compiler when linking C/C++ object (.o) files together into a component shared library (.so) file. These flags will be added to the flags specified on the command-line and in other definition files.

Flags are separated by whitespace.

ldflags:
{
-Lfoo/bar
}

pools

Warning
This feature not yet implemented.

Specifies the number of memory pool blocks that each memory pool should contain.

pools:
{
myPool = 45
}

provides

Lists things this component provides (exports) to other software either inside or outside of the App.

The only subsection supported today is the api subsection.

api

Lists IPC services provided by this component to other components.

Contents use the same syntax as the requires: api section, except the options are different.

Here's a code sample where greet.api defines a function called Send() where the C source code for the component (in greetServer.c) is implement a function called greet_Send().

provides:
{
api:
{
greet.api // We offer the Greet API to others so they can say “hello” to the world.
heat = digitalOutput.api
cool = digitalOutput.api
}
}
 
sources:
{
greetServer.c
tempControl.c
}

The component must implement the API functions being provided.

In C, the source code must #include “interfaces.h” to get the auto-generated function prototype definitions and type definitions. The function and type names defined in the .api files are prefixed with the interface name and an underscore (similar to required APIs).

[manual-start]

To reduce the initialization code a component writer needs to write, the build tools automatically try to advertise the service when the executable is run. Sometimes this is not the preferred behaviour.

The [manual-start] option tells the build tools not to automatically advertise this API with the Service Directory when the process starts. If [manual-start] option is used, the component can control when it wants to start offering the service to others by calling the xxxx_AdvertiseService() function explicitly in the component source code when it's ready.

provides:
{
api:
{
foo.api [manual-start]
}
}

[async]

The server of a service can also implement the functions as if they were called directly by the client (even though the client may be running inside another process). When the client calls an API function, the server's API function gets called, and when the server returns from the function, the function returns in the client process.

Sometimes the server needs to hold onto the client request and do other things (like handing requests from other clients in the meantime) before sending a response back. This is called asynchronous mode, and is enabled using the [async] keyword on the end of the api section entry:

provides:
{
api:
{
bar.api [async]
}
}

When asynchronous mode is enabled for a server-side interface, the generated code changes as follows:

  • commandRef parameter is added to the beginning of all the API functions' parameter lists.
  • return value is removed from every API function.
  • Respond() function is generated for every API function.

In async mode, the server responds to the client's call to API function F() by calling the associated FRespond() function.

The Respond functions all take the commandRef as their first parameter. If an API function has a return value, that return value is sent to the client through the second parameter of the Respond function. Any output parameters defined in the API function are also passed as parameters to the Respond function.

See API Files for more information, or try it and have a look at the generated header files.

requires

The requires: section specifies things the component needs from its runtime environment.

It can contain various subsections.

api

Lists IPC APIs used by this component.

Here's a code sample of a component using the Configuration Data API (defined in le_cfg.api) to read its configuration data:

requires:
{
api:
{
le_cfg.api
}
}

This creates a client-side IPC interface called le_cfg on this component, and it makes the functions and data types defined inside le_cfg.api available for use in the component's program code.

The name of the .api file (minus the .api extension) is the name of the interface, and in C code, the names of functions and data types defined in the .api file are prefixed with the name of the interface with an underscore separator.

requires:
{
api:
{
print.api // WriteLine() from the API will appear in my C code as "print_WriteLine()".
}
}

To rename the interface, an interface name followed by an equals sign ('=') can be added in front of the .api file path.

requires:
{
api:
{
hello = greet.api // Send() from the API will appear as "hello_Send()" in my code.
}
}

Multiple instances of the same API listed in the api: section must have unique instance names, and appear as separate functions with different prefixes.

requires:
{
api:
{
heat = digitalOutput.api // Used to turn on and off the heater.
cool = digitalOutput.api // Used to turn on and off the cooling (A/C).
}
}

If digitalOutput.api defines two functions On() and Off(), the component’s source code would have four functions available to it: heat_On(), heat_Off(), cool_On(), and cool_Off().

C/C++ source code must #include “interfaces.h” to use the auto-generated function definitions. The build tools will automatically generate a version of interfaces.h customized for your component that includes all declarations for all the interfaces the component uses.

The build tools search for the interface definition (.api) file based on the interface search path.

options

To reduce the amount of initialization code a component needs to write, the build tools automatically generate the client-side IPC code for that API, and automatically try to connect to the server when the executable is run. There are a couple of options that can be used to suppress this behaviour.

The [types-only] option tells the build tools the client only wants to use type definitions from the API. This means the client-side IPC code will not be generated for this API, but the types defined in the API will still be available to the component (through interfaces.h in C/C++).

The [manual-start] option tells the build tools not to automatically connect to this API's server when the process starts. This means the component can control when it wants to connect to the server by calling the ConnectService() function for this interface explicitly in the component source code.

requires:
{
api:
{
foo.api [types-only] // Only need typedefs from here. Don't need IPC code generated.
bar.api [manual-start] // I'll start this when I'm ready by calling bar_ConnectService().
}
}

In addition, mksys and mkapp will check to make sure that all client-side IPC interfaces are bound to some service. If you want to allow a client-side interface to not be bound sometimes, the [optional] option can be used. Use of [optional] also implies [manual-start].

Also, if [optional] is used on an interface that would normally get automatically bound (le_cfg.api or le_wdog.api) the automatic binding will be suppressed.

At runtime, the component can try to use the interface by calling the TryConnectService() function for the interface. If the interface is not bound to anything, an error code will be returned.

requires:
{
api:
{
baz.api [optional] // May not be bound. I'll try it by calling baz_TryConnectService().
}
}

File

Declares:

  • specific files that reside on the target outside of the app, but made accessible to the app.
  • location inside the app's sandbox where the file will appear.

Things listed in requires are expected to be found on the target at runtime. They're not copied into the app at build time; they are made accessible to the app inside of its sandbox at runtime.

Each entry consists of two file system paths:

  • path to the object in the file system outside of the app, which must be an absolute path (beginning with ‘/’).
  • absolute file system path inside the app’s sandbox where the object will appear at runtime.

A file path can be enclosed in quotation marks (either single ' or double "). This is required when it contains spaces or character sequences that would start comments.

The first path can't end in a '/'.

If the second path ends in a '/', then it's specifying the directory where the object appears, and the object has the same name inside the sandbox as it has outside the sandbox.

requires:
{
file:
{
// I get character stream input from outside via a named pipe (read-only)
/var/run/someNamedPipe /var/run/
 
// I need to be able to play back audio files installed in /usr/local/share/audio.
"/usr/local/share/audio/error message.wav" /usr/share/audio/
'/usr/local/share/audio/success message.wav' /usr/share/audio/
}
}
Note
Even though the file system object appears in the app's sandbox it still needs permissions settings on the file. File permissions (both DAC and MAC) and ownership (group and user) on the original file in the target system remain in effect inside the sandbox. Within a sandbox files can only be read and writen to, new files can not be created.

It's also possible to give the object a different names inside and outside of the sandbox by adding a name to the end of the second path.

requires:
{
file:
{
// Program uses /var/run/someNamedPipe which it calls /var/run/externalPipe.
/var/run/someNamedPipe /var/run/externalPipe
}
}
Warning
When something is accessible from inside an app sandbox, there are potential security risks (e.g., access to the object could be exploited by the app, or hacker, to access sensitive information or launch a denial-of-service attack on other apps within the target device or other devices connected to the target device).

device

Declares:

  • device files that reside on the target outside of the app, but made accessible to the app.
  • location inside the app's sandbox where the file will appear.
  • access permissions the app is given to the device file.

Things listed in requires are expected to be found on the target at runtime. They're not copied into the app at build time; they are made accessible to the app inside of its sandbox at runtime.

Each entry consists of two file system paths and a set of optional access permissions:

  • access permissions, readable ([r]) and/or writeable ([w]). Executable is not allowed on device files. If permission values are not specified, then read-only ([r]) is the default.
  • path to the object in the file system outside of the app, which must be an absolute path (beginning with ‘/’). This must be a path to a valid character or block device file.
  • absolute file system path inside the app’s sandbox where the object will appear at runtime.

A file path can be enclosed in quotation marks (either single ' or double "). This is required when it contains spaces or character sequences that would start comments.

The first path can't end in a '/'.

If the second path ends in a '/', then it's specifying the directory where the object appears, and the object has the same name inside the sandbox as it has outside the sandbox.

requires:
{
device:
{
// I get read-only access to the SPI port.
[r] /dev/sierra_spi /dev/sierra_spi
 
// I get read-only access to the NMEA port.
/dev/nmea /dev/nmea
 
// I get read and write access to the I2C port.
[rw] /dev/sierra_i2c /dev/
}
}

Note that if a hot-plug device is unplugged and plugged back in, the app must be restarted before it can access the device.

It's also possible to give the object a different names inside and outside of the sandbox by adding a name to the end of the second path.

requires:
{
device:
{
/dev/ttyS0 /dev/port1 // Program uses /dev/port1, but UART0 is called /dev/ttyS0.
}
}
Warning
When something is accessible from inside an app sandbox, there are potential security risks (e.g., access to the object could be exploited by the app, or hacker, to access sensitive information or launch a denial-of-service attack on other apps within the target device or other devices connected to the target device).
This section is experimental. Future releases of may not support this section.
Note
If an access to the directory /dev/shm is required, it should be listed in the subsection dir: because it is treated like a directory even if it is located in /dev.

dir

Specifies directories on target device to make accessible to the app.

The location inside the app's sandbox at which the directory will appear is also specified.

Things listed here are expected to be found on the target at runtime. They are not copied into the app at build time; they are made accessible to the app inside of its sandbox at runtime.

Each entry consists of two file system paths:

  • The first path is the path to the directory outside of the app. This must be an absolute path (beginning with ‘/’) and can never end in a '/'.
  • The second path is the absolute path inside the app’s sandbox where the directory will appear at runtime.

Paths can be enclosed in quotation marks (either single ' or double "). This is required when it contains spaces or character sequences that would start comments.

If the second path ends in a '/', then it's specifying the directory into which the object will appear, and the object will have the same name inside the sandbox as it has outside the sandbox.

requires:
{
dir:
{
// I need access to /proc for debugging.
/proc /
 
// For now, I want access to all executables and libraries in /bin and /lib.
// Later I'll remove this and replace with just the files I really need in the field.
// Also, I don't want to hide the stuff that the tools automatically bundle into my app's
// /bin and /lib for me, so I'll make the root file system's /bin and /lib accessible as
// my app's /usr/bin and /usr/lib.
/bin /usr/bin
/lib /usr/lib
}
}
Note
Although the directory appears in the app's sandbox, it doesn't mean the app can access it. The directory permissions settings must also allow it. File permissions (both DAC and MAC) and ownership (group and user) on the original files in the target system remain in effect inside the sandbox. Within a sandbox files can only be read and writen to, new files can not be created.
Warning
Any time anything is accessible from inside an app sandbox, the security risks must be considered carefully. Ask yourself if access to the object can be exploited by the app (or a hacker who has broken into the app) to access sensitive information or launch a denial-of-service attack on other apps within the target device or other devices connected to the target device?
Note
It's not possible to put anything inside of a directory that was mapped into the app from outside of the sandbox. If you require /bin to appear at /usr/bin, you can't then bundle a file into /usr/bin or require something to appear in /usr/bin; that would have an effect on the contents of the /bin directory outside of the app. This also means that you can not create files in a mapped directory from within the sandbox.
If an access to the directory /dev/shm is required, it should be listed in this subsection as below mentioned, because it is treated like a directory even if it is located in /dev.
requires:
{
dir:
{
/dev/shm /dev/shm
}
}
If your requires: dir: section includes /dev/shm then, inside your legato application after calling: shm_open (sharedMemRegion, ...) you have to run: chmod ("/dev/shm/sharedMemRegion", S_IRWXU | S_IRWXG | S_IRWXO);

lib

The lib: subsection of the requires: section is used to specify that a shared (dynamic) library is required by any executable that the component is part of.

The required library will be linked with executables that the component is a part of.

Specifying a shared library file's path will result in "-L" and "-l" arguments being added to the linker's command line.

This is useful when linking to libraries that are not part of the target's sysroot. (If the library is part of the target's sysroot, then the ldflags: section can be used instead.)

On the target device at runtime, the dynamic linker will look for the library, so it must be made available inside the app sandbox, somewhere in the dynamic linker's library search path. (The dynamic linker will typically look in the /lib and /usr/lib directories for libraries at runtime.)

The library file can be bundled as a part of the app using the bundles: section of the .cdef file.

bundles:
{
file:
{
// Bundle the "foo" library as part of the app (in the app's /lib directory).
libfoo.so.3 /lib/
libfoo.so.3.1.1 /lib/
}
}

Or, if the library is already present on the target, then the files: or dirs: subsection of the requires: section of either the .cdef or .adef file can be used to make the library visible from inside the app sandbox.

requires:
{
file:
{
// Make the "foo" library available inside the App sandbox (in the App's /lib directory).
/usr/local/lib/libfoo.so.3 /lib/
/usr/local/lib/libfoo.so.3.1.1 /lib/
}
}

For backward compatibility, it is also possible to specify the library name "xml" without the leading "lib" or the trailing ".so". This will result in "-lxml" being passed to the linker when linking any executables that include this component, but will not add a '-L' option.

requires:
{
lib:
{
xml // I need access to libxml.so which is expected to already be on the target.
}
}

This is equivalent to using the "ldflags:" section to add "-lxml" to the linker command-line arguments.

ldflags:
{
-lxml
}

component

Declares this component depends on another component.

requires:
{
component:
{
foo
bar
}
}

Any app that uses a component will also use any other components that component requires, and any components they require, etc.

Specifying a dependency on another component ensures that calls to component initialization functions ( COMPONENT_INIT in C/C++ components ) are sorted in the correct order. If component A depends on component B, then component B will be initialized first.

Dependency loops are not allowed: component C can't depend on another component that (either directly or indirectly) depends on component C. The build tools detect dependency loops and report any error.

javaPackage

You create a javaPackage: section in the @ .cdef listing Java packages to build. It should look something like this:

javaPackage:
{
io.legato.samples
}

The build tools will look for Java to code under COMPONENT_DIR/src/io/legato/samples/ *.java

It won't recurse automatically into subdirectories; if you want subdirectories, they also must added to the JavaPackage section.

javaPackage:
{
io.legato.samples
io.legato.samples.foo
io.legato.samples.bar
}

The first Java package listed is assumed to be the main component package as it contains a class with the same name of the component, and it implements the interface (io.legato.Component).

A hello world Java app folder structure should look something like this:

+-- javaHelloComponent
| +-- Component.cdef
| +-- src
| +-- io
| +-- legato
| +-- samples
| +-- javaHelloComponent.java
+-- jHello.adef

sources

Contains a list of source code files.

If C or C++ code, one source file must implement a COMPONENT_INIT function. The framework will automatically call that function at start-up.

sources:
{
foo.c
bar.c
init.c // This one implements the COMPONENT_INIT
}