Build Scripts

MakeMe targets can run scripts at various stages of the processing lifecycle. Scripts can be used to create outputs, or modify the results of standard processing rules.

Script Engines

Scripts can be written to execute in any installed script interpreter. This may be the Unix bash shell, Perl or Ruby. MakeMe also includes a powerful Javascript engine based on the Ejscript language environment. This allows portable, scalable scripting without needing to create external process. Ejscripts can run portably on Windows and Unix like systems. Ejscript also has an extensive Library of routines that scale well when building projects without turning into a mess of shell script.

Script Forms

MakeMe target scripts can also be specified using three forms:

  1. Shell scripts — that run using bash for Unix and Cygwin systems
  2. Action target scripts — that use Ejscript
  3. Build target scripts — that run for default builds using Ejscript

Shell Scripts

Shell scripts can be easily specified via the target shell property:

cleanup: {
    shell: '
        date >now.txt 
        echo hello world
    ',
},

This translates into the following low level form. The script runs in the directory containing the MakeMe file, using the bash shell script.

cleanup: {
    scripts: {
        type: 'script',
        build: [
            {
                home: '.',
                interpreter: 'bash',
                script: "
                    date >now.txt 
                    echo hello world
                "
            },
        ],
    ',
    goals: [ 'cleanup' ],
},

Because the goals property is set to cleanup, this script will be run only when the when goal 'cleanup' is specified on the MakeMe command line.

me cleanup

If you need a shell script to run when a default build is initiated, use the low level form and set the goals property to ['cleanup', 'all'] See the Low level Scripts section above for details.

Action Scripts

An action script runs when explicitly requested. It operates similarly to a shell script except that it uses the Ejscript interpreter for portable scripting.

Action scripts are defined using the action property. For example:

prep: {
    action: "
        require ejs.tar
        let update = Http.fetch('http://example.com/update.tar)
        let tmp = Path().temp()
        tmp.write(update)
        Tar(tmp).extract()
    ",
},

Like shell script targets, actions do not run by for default builds. Rather, they are run when they are explicitly invoked on the command line or another goal/target is invoked that references them as a dependency.

Note that in Ejscript, quoted literals can span multiple lines. No backquoting at the end of the line is required. Also, semicolon end-of-line terminators are optional.

An action script translates into a target with a type of action and an event of build.

prep: {
    scripts: {
        type: 'script',
        build: [
            {
                home: '.',
                interpreter: 'ejs',
                script: "
                    require ejs.tar
                    let update = Http.fetch('http://example.com/update.tar)
                    let tmp = Path().temp()
                    tmp.write(update)
                    Tar(tmp).extract()
                "
            },
        ],
    },
    goals: ['prep'],
},

An action script can also be a pure Javascript function. For example:

prep: {
    action: function() {
        require ejs.tar
        let update = Http.fetch('http://example.com/update.tar)
        let tmp = Path().temp()
        tmp.write(update)
        Tar(tmp).extract()
    },
},

It is sometimes easier to manage quotes if using a function instead of a string to specify the script.

Build Scripts

Build scripts are a convenient way to express actions that should run for default builds. Build scripts use Ejscript and are defined via the build property. For example:

building: {
    build: "
        Http().post('http://example.com/buildbot', 'building')
    ",
}

When me is invoked without specifying any goals on the command line, the script for the building target will be run. The precise order of when the target runs will be dictated by its order in the MakeMe file and whether other targets depend upon it.

A build script translates into a target with a type of build and an event of build.

building: {
    scripts: {
        type: 'script',
        build: [
            {
                home: '.',
                interpreter: 'ejs',
                script: "
                    Http().post('http://example.com/buildbot', 'building')
                "
            },
        ],
    ',
    goals: ['all', 'building'],
},

An build script can also be a pure Javascript function.

Low Level Script Forms

As you have seen, the action, build and shell script forms are translated into a lower level form. You may use the lower level form directly, if you require more control.

The MakeMe DOM defines a collection of scripts to run at various stages of the MakeMe processing life cycle. When a specific event is triggered, the corresponding scripts are run. For each event, there can be multiple scripts that are run in-order. Each script has a home directory that becomes the current directory for the script and it has a specified interpreter to execute the script. If unspecified, the default directory is the directory containing the MakeMe file that specified the script. The default interpreter is ejs (Ejscript).

'build-complete': {
    type: 'script',
    scripts: {
        build: [ 
            {
                home: '.',
                interpreter: 'bash',
                script: "git pull ",
            },
            {
                home: '.',
                interpreter: 'ejs',
                script: "print('Building started at ' + Date())",
            },
        ],
    },
    goals: [ 'all', 'build-complete' ]
},

Because the goals property includes 'all', this target and the scripts will be run for a default MakeMe invocation (without any explicit targets specified on the command line).

The target.scripts property is a collection that is indexed by an event named for when the script should run. The standard events are described in MakeMe Events.

Here is another example of a shell script that runs just prior to building a library.

libglow: {
    type: 'lib',
    sources: '*.c',
    scripts: {
        prebuild: [ {
            interpreter: 'bash',
            script: './build-prep',
        ],
    },
    goals: ['libglow'],
}

Global Scripts

Most events apply to targets, but there are some events which are global. The MakeMe DOM defines a global set of scripts in the me.scripts property collection. These scripts follow the same property definition as the target scripts. The me.scripts property is a collection that is indexed by an event named for when the script should run. The global events are described in MakeMe Global Events.

Target Paths

You can make a build script run only when it is out-of-date by adding a path property for a physical file that will be created by the target. In this way, the target will only be run if the designated file does not exist or another target that depends on this target is more up-to-date than the file.

'build-complete': {
    path: 'complete.tmp',
    build: "
        Path('complete.tmp').write('Completed at: ' + Date())
    ",
}

Quoting

First a general note about quoting. Scripts can be expressed on one line or over multiple lines. You can choose whether you use single quotes (''), double ("") or back-tick (``) quotes around your script. Inside the script you can then freely use alternate quote characters without needing to back-quote. If you need to use the outside quote within the script, quote it with a backslash \ character.

It is typically best to use back-tick quotes around a shell script so that single and double quotes can be used inside. For example:

shell: `echo "Dont't panic"`, 

Useful MakeMes

Here are some useful parts of Ejscript to use in your scripts:

List of Files

Get a list of all files in all subdirectories

let files = Path('some/dir').files('**')

Run an external command

Get a list of all files in all subdirectories

run('/usr/bin/program')

Write a file

Get a list of all files in all subdirectories

Path('filename').write('Hello World')

Read a file

Read a file into a variable as a string

let data = Path('filename').readString

Read a JSON file

Read a JSON file as an object

let name = Path('filename').readJSON().name

Work with Paths

Here are some useful Path manipulation routines. Ejscript is great at this.

let p = Path('/some/file.c')
print('Basename', p.basename)
print('Dirname', p.dirname)
print('Extension', p.extension)
print('Joining', p.dirname.join('other.c'))

Fetch from the Web

let data = Http.fetch('http://example.com/something.html')

See the Ejscript Path class for full details of manipulating paths. Similarly, for URLs, see: URI class, and for Http requests, see the Http class.

To learn more, read about Copying Files.

© Embedthis Software. All rights reserved.