Configurable Projects

MakeMe supports you in creating highly configurable projects, whereby users can customize the project to suit the requirements and their local system. Users can select and deselect components via the "me -configure . " command.

Configure Script

Developers typically provide a familiar configure script to invoke MakeMe for configuration.

# configure script
me configure "$@"

When configure is run, it examines the local system to determine and verify which components can be supported and whether required components are present. Users can also select extra components via the -with switch and deselect via -without. For Example:

configure -with ssl --without php

Components

MakeMe configurable components are configurable targets that provide libraries, programs, or any other desired resource. Example components are: the ESP web framework or the OpenSSL SSL stack.

Configurable components are implemented as MakeMe targets with the configurable property set to true. This means that any MakeMe target can be a configurable component selectable by the user.

For example, here is a component MakeMe file for a mythical "rocket" component:

Me.load({
    rocket: {
        configurable: true,
        description: 'Rocket Engine',
        libraries: [ 'librocket' ],
        includes: [ 'rocket.h' ],
        defines: [ 'SATURNV' ],
    },
})

Referencing Components

If a target wishes to utilize component, it can specify it in its depends property. In this manner, the the component properties: defines, includes, libraries, linker and libpaths, will be inherited by the target.

If the target specifies the component via its "ifdef" property, the target will only be built if the component is enabled. Otherwise, the target will (silently) be omitted from the build.

For example, consider the earlier Rocket component definition. A target desiring to use this component could be specified like this:

mars_mission: {
    type: 'exe',
    sources: 'mars.c',
    depends: ['rocket'],
    ifdef: ['rocket'],
}

If a target may also depend on optional components by specifying the component in its "uses" property. In this manner, the component properties: defines, includes, libraries, linker and libpaths, will be inherited by the target if the component is configured and enabled.

Configurable Projects

Configurable projects are different from stand-alone MakeMe projects in that the start.me is generated based on a template MakeMe file called main.me. The main.me file supplies the full project definition including a list of required and optional components suitable for the project.

In the project's main.me file, the set of possible components is specified. For example:

configure: {
    requires:  [ 'pcre' ],
    discovers: [ 'cgi', 'dir', 'esp', 'sqlite' ],
    extra:     [ 'php' ],
}

Components are grouped into sets that the project requires, components that the project discovers or extra components that may be used.

Required components are essential for the basic operation of the product. Discoverable components are optional components for which MakeMe will search the local system to see if the component resources are present. Extra components will not be automatically discovered and must be explicitly requested by the user when configuring via the --with switch. For example:

configure -with php
configure -with php=/path/to/php

Configure Outputs

When the user runs configure, MakeMe will find the set of components to incorporate and will then create the following files and directories:

These files fully describe the desired build characteristics with the selected components and options. Subsequently when MakeMe is run, MakeMe will load the start.me file which in turn loads the OS-ARCH-PROFILE.me file to manage the build.

Component Discovery

Under the hood, a component is really just a MakeMe target with a configurable property set to true. Components may be defined in any project MakeMe file. Alternatively, if not defined inline, the component target may be defined in a separate MakeMe file that is only loaded when configure is run.

If a component is specified either by the user via --with component or via the main.me configure properties, MakeMe will first look for an inline configurable target of the same name as the requested component. If an inline component target is found, it will be used. If not found, then a search begins for a MakeMe file describing the component. MakeMe searches for a component file in the following order:

  1. src/paks/NAME/NAME.me
  2. ~/.paks/NAME/NAME.me
  3. /usr/local/lib/makeme/latest/bin/paks/NAME/NAME.me

Standard Components

Here is a partial list of the standard components supplied with MakeMe.

Component Description
compiler Compiler detection
lib Library archive program (ar, lib)
link Linker program
rc Resource compiler tool on windows
vxworks WindRiver VxWorks Development Environment
winsdk Windows SDK

MakeMe also uses plugins to implement internal functions such as: O/S specifics, configuration, and project generation.

Here is a partial list of the components available from the Pak Catalog.

Component Description
ejs Ejscript Language Library
esp ESP C Language Web Framework
mpr Multithreaded Portable Runtime
osdep Embedthis O/S Dependent Abstraction
pcre PCRE regular expression library
sqlite SQLite library
ssl SSL Interface. Includes component references for Est and OpenSSL
zlib Zlib Compression library

Component Resolution

Component resolution is the process to determine which components should be included in the build.

The resolution process takes several steps:

  1. Loading — Component MakeMe files are located and loaded as specified by the main.me properties: configure.requires configure.discovers and configure.extra. A target is created for component with the configurable property set to true. Inline components must set define this property explicitly.
  2. Exclusion — Components that have been explicitly excluded via a configure --without option will have their optional "without" function property invoked. This permits a component to take specific actions when it is excluded. The function is passed the component target object as its only parameter.
  3. Configuration — If the component defines a "config" function property, it is invoked to search for the component resources and determine the component's target settings. The function is passed the component target object as its only parameter. This function should return a Javascript object containing the properties to define for the target. This typically includes "libraries", "includes", "defines", but may include any desired target property. If the component cannot be supported, the function should throw a suitable message to deselect the component.
  4. Location — If the component defines a "path" function property, it is invoked. This can be a convenient way to configure simple program tools that only need to be located. The component path should be the filename of the program if the component represents a simple tool (like zip). Otherwise, it should be the directory to the component if it represents something more complex like an SDK.

The end result of this resolution process is the component's target.enable being set to true or false, and the optionaltarget.path being set to the location of the component.

Notes for Component Property Functions

If configure is run and a component is explicitly requested via --with=PATH, that path is provided to the component target in its target.withpath property. For example, if configure is run using --with:

me -configure . --with NAME=/path

Then the component script can access the requested path via its target.withpath. A common pattern for probing for component resources is to use the withpath as the default before probing:

path: function (target) {
    return { 
        path: target.withpath || '/usr/local/bin/programName' 
    }
},  

Exceptions

If an exception is thrown by any component code during configuration, the exception will be caught and the component will be disabled. This is a common pattern, to throw exceptions if the component cannot be fully configured and enabled.

The Probe API

The "probe" function is useful to search for a program on the local system. It searches for a named program and takes options for the search path (search), the default to return if the program cannot be found (default), and whether to return the full path or just the dirname portion (fullpath).

let path = probe('gzip', { 
    search: ['/bin', '/usr/bin' ],
    default: 'bin/gzip'
})

Component Target Properties

As a configurable component is just a special case of target, you can use any target property in a component. Here are the key properties used by components:

Settings

The other part of the configuration process is defining settings that the software and selected components will use when building and running. These options are defined in the settings collection an may be modified via the -set option. For example:

configure -set mpr.logging=true

The default settings are specified in the main.me file. By convention, a component may create a named collection under settings for its own properties. For example:

settings: {
    mode: 'fast',
    mpr: {
        logging: true,
    },
},

Note that settings may be nested inside other collections to any depth. In such cases the feature is selected using the normal dot notation.

During configuration, MakeMe it will convert every value in the settings collection into a definition in the me.h header. Dots are converted to underscores. For example: mpr.logging is converted to "ME_MPR_LOGGING". True|False values are converted to 1 or 0 as appropriate.

Usage Message

Developers can tailor the usage message emitted by MakeMe to describe the various configurable options. Users can get configuration help by invoking configure with a help target. For example:

$ configure help
Usage: me [options] [targets|actions] ...
  Options:
  --benchmark  # Measure elapsed time
  ...

Additional usage messages may be specified in main.me in the usage property collection. For example:

usage: {
    'ejs.db':        'Enable database support, ejs.db (true|false)',
    'ejs.mail':      'Enable mail support, ejs.mail (true|false)',
    'ejs.mapper':    'Enable database mapper support, ejs.mapper (true|false)',
},

Generating Projects

When generating conditional Makefiles, a component cannot know in advance what resources will be available on the local system where the Makefile will be run. So the component script cannot sleuth the system to determine the appropriate configuration.

To address this problem, components should have special code to handle the case when generating projects. The components "config" callback can test the "me.options.gen" property which will be set if generating. The config function can then attempt to generate generic component configuration that will be suitable, if not optimial for all systems. For example:

openssl: {
    config: function (target) {
        if (me.options.gen) {
            return {
                path = '/opt/lib/openssl'
                libpaths = [ '/opt/lib' ]
                includes = [ '/usr/include/openssl' ],
                libraries = [ 'crypto', 'ssl' ],
            }
        } else {
            /* Normal case */
        }
    },
}

Component Paks

Components can be conveniently packaged and delivered using the Pak tool. Pak is a package manager and distribution tool for application components. These may be delivered either as part of the project, or they may be separately downloaded and installed by the user just prior to configuring. Component paks define their detection and configuration logic inline in their MakeMe file.

Importing MakeMe

For developers who wish to freeze the MakeMe support for their product and guarantee the version of MakeMe they have designed for, the MakeMe configuration can be imported into the product source tree via:

me -import

This will copy the entire MakeMe directory into the local source under a "me" directory. This copied configuration will be used by MakeMe in preference over the install MakeMe files. The various MakeMe components and operating system configuration can be modified once imported.

Under the me directory will be the primary MakeMe script me.es and its compiled form me.mod. If you need to hack me.es, remove the me.mod file and MakeMe will utilize your modifications. When ready, you can compile me.es to create a new me.mod via:

/usr/local/bin/makeme/latest/bin/ejsc --optimize 9 --out me.mod me.es

Samples

Here is a sample configurable component in a separate MakeMe file.

Me.load({
    myssl: {
        configurable: true,
        description: 'MySSL',
        config: function (target) {
            let path = target.withpath || Path('/usr/lib/myssl')
            if (!path.exists) {
                throw 'Cannot find MySSL'
            }
            return {
                path: path,
                includes: [ path.join('include') ],
                libraries: [ 'myssl' ],
                libpaths: [ path.join('lib') ],
            }
        },
        ifdef: [ 'ssl' ],
    },
})

Samples

The standard compnents are a good source of samples. View in the repository at MakeMe Components.

© Embedthis Software. All rights reserved.