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:
- A startup MakeMe file: start.me
- A platform output directory of the form: build/OS-ARCH-PROFILE
- A platform specific MakeMe file: build/OS-ARCH-PROFILE/platform.me
- A source definitions header: me.h
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:
- src/paks/NAME/NAME.me
- ~/.paks/NAME/NAME.me
- /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:
- 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.
- 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.
- 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.
- 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:
- config — Function to locate component resources and define component settings.
- description — Short, one-sentance description of the component.
- defines — Array of C preprocessor definitions for targets using this component.
- depends — Array of targets from which to inherit compiler, defines, libraries, libpaths and linker settings. May contain component names.
- discovers — Array of other components that should be discovered and utilized by this component if available.
- enable — Boolean true|false value to enable or disable the component. May be initially set to a script or function that is run to yield a boolean result.
- ifdef — Array of other components that must be available for this component to be enabled.
- imports — Libraries, files and resources to import into the local source tree.
- includes — Array of include paths necessary for targets using this component.
- libpaths — Array of linker library search paths for targets using this component.
- libraries — Array of required libraries for targets using this component.
- linker — Array of linker options for targets using this component.
- name — Component target name. Should equal the component collection property name..
- requires — A component may specify a list of other components to configure.
- path — Path to primary component resource or directory. May be the path to the binary for tools.
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.