Create Apps

This beginning tutorial will walk you through creating Hello World by:

Walking through this tutorial will introduce you to the basic concepts that make up the Legato Application Framework. Once you've completed this you can then walk through: Add Things, Explore Apps, Exes and Processes, and Deploy Multiple Components.

Sample Directories and Files

Apps have a hierarchy of folders and files, with the base folder being the name of the app. Each component is encapsulated in it's own subdirectory and will include the .c files and a Component Definition .cdef (more on how to create def files in a bit). The app directory will include an Application Definition .adef, which is used to combine all the components into executables and configure the process for the executable as well as define the configuration for your app.

Example directory structure for Hello World:

helloWorld/
├── helloComponent/
│   ├── Component.cdef
│   └── helloWorld.c
└── helloWorld.adef

Create Component

Creating a component will walk you through: Create Directory, Create C Source File, and Create cdef File

Create Directory

Create a directory for your component: the directory name will become the component name (this is done for ease of organization and becomes important when you are including multiple components):

$ mkdir helloComponent

Create C Source File

This looks similar to the original C helloWorld program from Kernighan's and Richie's "The C Programming Language", except that:

  • #include <stdio.h> is replaced with #include "legato.h"
  • main() is replaced with COMPONENT_INIT

Create the file for helloWorld:

$ vim helloComponent/helloWorld.c # use any editor of your choosing

Include legato.h

First you must include legato.h. legato.h is the main legato library, and includes stdio.h along with other system headers and Legato Framework headers. It reduces the amount of time you have to spend including header files to get access to the functions and data types that you need. List of includes in "legato.h".

Also include "interfaces.h". interfaces.h is a C header file that automatically includes all client and server interface definition that your client imports and exports. If you need to import or export any other API you will need to explicitly include "interfaces.h" into your C or C++ code. It is not used in helloWorld now, but it is a Legato best practice to include it in all of your Legato Component C or C++ code.

#include "legato.h"
#include "interfaces.h"

Component Initializer

The COMPONENT_INIT macro is used to identify your component initializer. Every component must have a component initializer.

By using component initializers instead of having each component implement their own main() function, it's possible to run multiple components in the same executable and even share a thread between those components.

Note
The main process thread (the main() function auto-generated by the build tools) will automatically call the component's initializer at the appropriate time during the process start-up sequence (based on the inter-dependencies between components). If component A is used by component B, then component A's initializer will be run before component B's initializer. Then component B can safely call the API functions of component A knowing that component A has already been initialized. This won't work if both components depend on each other (directly or indirectly through other components). That's one reason why dependency loops are not permitted between components. The framework will detect inter-component dependency loops at build time and terminate the build.

Component initializers don't take any parameters and don't return anything, but they must return in order to enable the event loop. Unless it experiences a fatal error, in which case it must terminate the process with a non-zero exit code (which can be done using LE_FATAL(), LE_ASSERT(), etc. ).

Note
If a component's initializer doesn't return, the process will not be able to finish initializing all the components that have been deployed to it and the process's main thread will never process any events that are queued to it.

In our "Hello World" example, we use our component initializer to print Hello World! to the log using LE_INFO(). The component initializer will run LE_INFO() at start-up. LE_INFO() logs a message at the INFO severity level.

Add the following lines to helloWorld.c:

{
LE_INFO("Hello world!");
}

Your helloWorld.c should look like this:

#include "legato.h"
#include "interfaces.h"
 
{
LE_INFO("Hello world!");
}

That is all that our helloWorld component needs. You can now close and save the file.

The build tools figure out what language the source code is written in by looking at the filename extension. So, because our file helloWorld.c ends in .c, the build tools will try to use a C compiler to compile it into a library when helloWorld.c gets included in an executable.

Create cdef File

Legato definition files (.sdef, .adef, .cdef and .mdef) are used as instructions to the Build Tools (mk tools) and tell the mk tools how to bind the components, apps and systems together to create the Legato Runtime Environment which then gets installed on a target.

Component definition files are used to specify the external interfaces and internal content of the components as well as defining the requirements from the system for the component (e.g; What access does the component need, what API will the component access, what external files will it access). Each component must have a corresponding Component.cdef, which must be included in the same folder as the rest of the component code.

Create a cdef file called Component.cdef:

$ vim helloComponent/Component.cdef

Since our helloWorld component doesn't have any external interfaces and we will be using all the default build locations we can create a simple .cdef file and only need to define the sources:

Add the following to your Component.cdef file and then save and exit.

sources:
{
helloWorld.c
}

You have now successfully created your first Legato Component. Congratulations!

Create App

Next lets turn your helloWorld component into an App. Apps are the execution environment for your code, they contain one ore more executables that run together in a "Sandbox". Your executables are made by combining one or more components. Processes are the running executables that are actively running in memory. Apps may expose external API's but generally contain all the components, executables, setting and files to run within it's own environment.

The Application Definition .adef builds the instructions to tell the mktools how to build your app. In the helloWorld app we need to tell the mktools to create an executable out of the helloComponent component and then tells the app to run that executable in a process.

Create a .adef file called helloWorld.adef:

$ vim helloWorld.adef

First we are going to define an executable named helloWorld for the helloComponent Component. This turns your source code into an executable binary.

executables:
{
helloWorld = ( helloComponent )
}

Next we need to tell the app to start the helloWorld executable within a process.

processes:
{
run:
{
( helloWorld )
}
}
Note
If your executable has multiple components, add more components inside the parentheses after the executable name: "helloWorld = ( helloComponent helloSun helloMoon )".

For this example your .adef file will look like the following:

executables:
{
helloWorld = ( helloComponent )
}
 
processes:
{
run:
{
( helloWorld )
}
}

Build App

Now that all your files are created and you have provided a .cdef and an .adef for your app you are ready to use the Build Tools mktools to build your app into a binary and deploy it on your target.

You should have the following directory structure for your app:

helloWorld/
├── helloComponent/
│   ├── Component.cdef
│   └── helloWorld.c
└── helloWorld.adef

Before you run mkapp make sure that you have configured the Framework for a specific target and have run bin/legs or sourced bin/configlegatoenv.

mkapp is the tool that is used to build the app into a binary package that can then be installed on your target. It is important to specify the target that you are building for as mkapp will include all the libraries and dependencies that are needed for your specific target.

Note
If you do not use the -t option mkapp will build for the default, localhost which is only used for testing purposes.

From the helloWorld directory run the following command (the target in this example is the wp85):

$ mkapp -t <target> helloWorld.adef

Output:

$ mkapp -t wp750x helloWorld.adef
[9/9] Packaging app

A new directory _build_helloWorld and an update file will be created in your helloWorld directory.

├── _build_helloWorld/
│   └── wp85
│       ...
├── helloComponent/
│   ├── Component.cdef
│   └── helloWorld.c
├── helloWorld.adef
└── helloWorld.wp85.update

The _build directory is the intermediate directory where the complier will place all staging files created during the build process. It can be removed after helloWorld.wp85.update has successfully been created, though it is helpful in troubleshooting build problems.

helloWorld.wp85.update is the package that is needed to install helloWorld onto your target.

Install App to Target

Use app install to install the app on your target, the app command is available both on your dev machine and target but when using from your dev machine you must supply the IP address of your target:

$ app install helloWorld.<target>.update <ip addr>

The Framework supports an environment variable called DEST_IP, if you set this to the IP of your target most target machine commands will default to using DEST_IP and you will not have to enter in your IP address every time. To set DEST_IP:

$ export DEST_IP=192.168.2.2  # or the IP of your target

Now you can update your target by typing:

$ app install helloWorld.wp85.update   # add the target IP to the command if DEST_IP is not set

Test helloWorld

The helloWorld app logs Hello world! to the syslog everytime the app is started. To test this out we need to use the logread command to tail the logs.

SSH to your target:

$ ssh root@192.168.2.2

Run logread (on your target):

# logread -f | grep helloWorld

From your workspace: (you must have configlegatoenv sourced, or run bin/legs and have DEST_IP set)

$ app status                            # check that <appName> is listed
$ app start <appName>                   # e.g., app start helloWorld

In the log you should be able to see logging statements similar to:

# logread -f | grep "helloWorld"
<TIME> <target> user.info Legato:  INFO | supervisor[465]/supervisor T=main | app.c CreateTmpFs() 1204 | Mounted tmpfs at /legato/systems/current/appsWriteable/helloWorld/tmp.
<TIME> <target> user.info Legato:  INFO | supervisor[465]/supervisor T=main | app.c CreateFileLink() 1504 | Created file link '/tmp/legato/serviceDirectoryServer' to '/legato/systems/current/appsWriteable/helloWorld/tmp/legato/serviceDirectoryServer'.
<TIME> <target> user.info Legato:  INFO | supervisor[465]/supervisor T=main | app.c CreateFileLink() 1504 | Created file link '/tmp/legato/serviceDirectoryClient' to '/legato/systems/current/appsWriteable/helloWorld/tmp/legato/serviceDirectoryClient'.
<TIME> <target> user.debug Legato:  DBUG | _UNKNOWN_[1518]/<invalid> T=main | _componentMain.c _helloWorldComponent_Init() 26 | Initializing helloWorldComponent component library.
<TIME> <target> user.info Legato:  INFO | helloWorld[1518]/helloWorldComponent T=main | helloWorldComponent.c _helloWorldComponent_COMPONENT_INIT() 5 | Hello, world.

Stop the App (if needed):

$ app stop helloWorld                   # add the target IP if DEST_IP not set

Run app --help for all options on controlling apps on your target from your dev machine.

app list - lists all the installed apps on the target app remove <appName> removes the app from the target

Next we are going to add a second app, communicate between the two apps and build the apps into a system.