Make moveAndRotate not trigger extra loc update requests.

Remove immediate updates to velocity when movement is requested since
we start reevaluation anyway and the first reeval is always
performed. With this fix we don't cause duplicate update requests.

/*  Sirikata
 *  movementAndRotate.em
 *
 *  Copyright (c) 2011, Ewen Cheslack-Postava
 *  All rights reserved.
 *
 *  Redistribution and use in source and binary forms, with or without
 *  modification, are permitted provided that the following conditions are
 *  met:
 *  * Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 *  * Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in
 *    the documentation and/or other materials provided with the
 *    distribution.
 *  * Neither the name of Sirikata nor the names of its contributors may
 *    be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
 * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
 * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
 * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

system.require('movement.em');

/** MoveAndRotate gives you intuitive control over moving and
 *  rotating. The most common use for this is when you want to combine
 *  movement and rotation simultaneously, but prefer to specify
 *  velocity post-rotation. For example, if you want to specify that
 *  an object continues to move 'forward' by specifying a single
 *  velocity, but also have the object continue even as rotation
 *  occurs.
 */
std.movement.MoveAndRotate = system.Class.extend(
    {
        UPDATE_TYPE_MOVEMENT: 1,
        UPDATE_TYPE_ROTATION: 2,
        UPDATE_TYPE_ALL: 3,

        /** Initialize the controller with a presence. The optional parameter
         *  update_cb is a function which is invoked periodically when the
         *  position or orientation are updated. This is useful if you derive
         *  some other information (e.g. camera position) from this state. The
         *  final parameter controls what kind of updates you get callbacks for:
         *  'all', 'rotation', or 'movement'. It defaults to 'all'.
         */
        init: function(pres, update_cb, update_type) {
            this._pres = pres;
            this._updatecb = update_cb; // May be undefined
            if (update_type == 'movement')
                this._update_type = this.UPDATE_TYPE_MOVEMENT;
            else if (update_type == 'rotation')
                this._update_type = this.UPDATE_TYPE_ROTATION;
            else
                this._update_type = this.UPDATE_TYPE_ALL;

            this._handleSource = 0;
            this._rotateCommands = [];

            this._localVel = pres.velocity;
            this._localOrientVel = pres.orientationVel;

            this._moving = this.moving();
            this._rotating = this.rotating();
        },
        presence: function () {
            return this._pres;
        },
        moving: function() {
            return this._pres.velocity.lengthSquared() > 1e-08;
        },
        rotating: function() {
            return !this._pres.orientationVel.isZero();
        },
        move: function(vel, scaling, overwrite) {
            var scaled_vel = vel.mul(scaling);
            if (overwrite) {
                this._localVel = scaled_vel;
            }else {
                this._localVel = this._localVel.add(scaled_vel);
            }
            this._startReeval(true);
        },
        rotate: function(axis, angle) {
            var newDiffVel = new util.Quaternion(axis, angle);
            var handle = {
                'id' : this._handleSource++,
                'vel' : newDiffVel
            };
            this._rotateCommands.push(handle);
            var newVel = this._localOrientVel.mul(newDiffVel);
            this._pres.orientationVel = newVel;
            this._localOrientVel = newVel;
            this._startReeval(true);
            return handle;
        },
        cancelRotate: function(handle) {
            // Remove
            for(var i in this._rotateCommands) {
                if (handle.id == this._rotateCommands[i].id) {
                    this._rotateCommands.splice(i, 1);
                    break;
                }
            }

            // Recompute velocity
            var newVel = new util.Quaternion();
            for(var i in this._rotateCommands) {
                newVel = newVel.mul(this._rotateCommands[i].vel);
            }

            this._pres.orientationVel = newVel;
            this._localOrientVel = newVel;
            this._startReeval(true);
        },
        rotateLocalOrientation: function(orientationVelocity, overwrite) {
            var newVel = orientationVelocity;
            if (!overwrite) {
                newVel=this._localOrientVel.mul(orientationVelocity);
            }
            this._pres.orientationVel = newVel;
            this._localOrientVel = newVel;
            this._startReeval(true);
        },
        _startReeval: function(is_first) {
            this._moving = this.moving();
            this._rotating = this.rotating();

            // Usually people expect that holding down forward and left/right
            // will result in moving in a curve, i.e. that the velocity is in
            // local coordinates and gets updated automatically with rotation.
            // Further, we also provide callbacks if requested, so we may need
            // to setup timeouts for those, even if we don't need to update the
            // info.

            // These various conditions mean we can trigger reevaluation due to
            // a number of conditions.
            if ((this._update_type == this.UPDATE_TYPE_ALL && (this._moving || this._rotating)) ||
                (this._update_type == this.UPDATE_TYPE_MOVEMENT && this._moving) ||
                (this._update_type == this.UPDATE_TYPE_ROTATION && this._rotating) ||
                (this._moving && this._rotating || is_first) // Real reeval
               ) {
                   // is_first tracks whether this is the first
                   // request for reeval. We do this specially to
                   // force reevaluation immediately upon setting
                   // values. We need to do this because when the
                   // orientation changes we want to make sure the
                   // velocity also gets the corresponding
                   // update. Otherwise, the deltas used can become
                   // incorrect so a sequence like [ move forward,
                   // rotate right, rotate_left, move backward ],
                   // which should have all elements cancel out, can
                   // end up with a small velocity because the move
                   // backwards is computed using the right local
                   // orientation but the presence's velocity might
                   // not have been updated yet (still waiting for
                   // first timeout).
                   if (is_first)
                       this._reeval(is_first);
                   else
                       system.timeout(.05, std.core.bind(this._reeval, this));
               }
        },
        reeval: function() {
            this._reeval(true);
        },
        _reeval: function(is_first) {
            // Only perform reeval if we really need it
            if (this._moving && this._rotating || is_first)
                this._pres.velocity = this._pres.orientation.mul(this._localVel);

            // Then do callbacks
            if (this._updatecb &&
                (is_first ||
                 this._update_type == this.UPDATE_TYPE_ALL ||
                 (this._update_type == this.UPDATE_TYPE_MOVEMENT && this._moving) ||
                 (this._update_type == this.UPDATE_TYPE_ROTATION && this._rotating))) {
                this._updatecb();
            }

            // Loop, if necessary.
            this._startReeval();
        }
    }
);

/*  Sirikata
 *  movementAndRotate.em
 *
 *  Copyright (c) 2011, Ewen Cheslack-Postava
 *  All rights reserved.
 *
 *  Redistribution and use in source and binary forms, with or without
 *  modification, are permitted provided that the following conditions are
 *  met:
 *  * Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 *  * Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in
 *    the documentation and/or other materials provided with the
 *    distribution.
 *  * Neither the name of Sirikata nor the names of its contributors may
 *    be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
 * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
 * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
 * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

system.require('movement.em');

/** MoveAndRotate gives you intuitive control over moving and
 *  rotating. The most common use for this is when you want to combine
 *  movement and rotation simultaneously, but prefer to specify
 *  velocity post-rotation. For example, if you want to specify that
 *  an object continues to move 'forward' by specifying a single
 *  velocity, but also have the object continue even as rotation
 *  occurs.
 */
std.movement.MoveAndRotate = system.Class.extend(
    {
        UPDATE_TYPE_MOVEMENT: 1,
        UPDATE_TYPE_ROTATION: 2,
        UPDATE_TYPE_ALL: 3,

        /** Initialize the controller with a presence. The optional parameter
         *  update_cb is a function which is invoked periodically when the
         *  position or orientation are updated. This is useful if you derive
         *  some other information (e.g. camera position) from this state. The
         *  final parameter controls what kind of updates you get callbacks for:
         *  'all', 'rotation', or 'movement'. It defaults to 'all'.
         */
        init: function(pres, update_cb, update_type) {
            this._pres = pres;
            this._updatecb = update_cb; // May be undefined
            if (update_type == 'movement')
                this._update_type = this.UPDATE_TYPE_MOVEMENT;
            else if (update_type == 'rotation')
                this._update_type = this.UPDATE_TYPE_ROTATION;
            else
                this._update_type = this.UPDATE_TYPE_ALL;

            this._handleSource = 0;
            this._rotateCommands = [];

            this._localVel = pres.velocity;
            this._localOrientVel = pres.orientationVel;

            this._moving = this.moving();
            this._rotating = this.rotating();
        },
        presence: function () {
            return this._pres;
        },
        moving: function() {
            return this._pres.velocity.lengthSquared() > 1e-08;
        },
        rotating: function() {
            return !this._pres.orientationVel.isZero();
        },
        move: function(vel, scaling, overwrite) {
            var scaled_vel = vel.mul(scaling);
            if (overwrite) {
                this._localVel = scaled_vel;
                this._pres.velocity = this._pres.orientation.mul(scaled_vel);
            }else {
                this._localVel = this._localVel.add(scaled_vel);
                this._pres.velocity = this._pres.velocity.add(
                    this._pres.orientation.mul(scaled_vel)
                );
            }
            this._startReeval(true);
        },
        rotate: function(axis, angle) {
            var newDiffVel = new util.Quaternion(axis, angle);
            var handle = {
                'id' : this._handleSource++,
                'vel' : newDiffVel
            };
            this._rotateCommands.push(handle);
            var newVel = this._localOrientVel.mul(newDiffVel);
            this._pres.orientationVel = newVel;
            this._localOrientVel = newVel;
            this._startReeval(true);
            return handle;
        },
        cancelRotate: function(handle) {
            // Remove
            for(var i in this._rotateCommands) {
                if (handle.id == this._rotateCommands[i].id) {
                    this._rotateCommands.splice(i, 1);
                    break;
                }
            }

            // Recompute velocity
            var newVel = new util.Quaternion();
            for(var i in this._rotateCommands) {
                newVel = newVel.mul(this._rotateCommands[i].vel);
            }

            this._pres.orientationVel = newVel;
            this._localOrientVel = newVel;
            this._startReeval(true);
        },
        rotateLocalOrientation: function(orientationVelocity, overwrite) {
            var newVel = orientationVelocity;
            if (!overwrite) {
                newVel=this._localOrientVel.mul(orientationVelocity);
            }
            this._pres.orientationVel = newVel;
            this._localOrientVel = newVel;
            this._startReeval(true);
        },
        _startReeval: function(is_first) {
            this._moving = this.moving();
            this._rotating = this.rotating();

            // Usually people expect that holding down forward and left/right
            // will result in moving in a curve, i.e. that the velocity is in
            // local coordinates and gets updated automatically with rotation.
            // Further, we also provide callbacks if requested, so we may need
            // to setup timeouts for those, even if we don't need to update the
            // info.

            // These various conditions mean we can trigger reevaluation due to
            // a number of conditions.
            if ((this._update_type == this.UPDATE_TYPE_ALL && (this._moving || this._rotating)) ||
                (this._update_type == this.UPDATE_TYPE_MOVEMENT && this._moving) ||
                (this._update_type == this.UPDATE_TYPE_ROTATION && this._rotating) ||
                (this._moving && this._rotating || is_first) // Real reeval
               ) {
                   // is_first tracks whether this is the first
                   // request for reeval. We do this specially to
                   // force reevaluation immediately upon setting
                   // values. We need to do this because when the
                   // orientation changes we want to make sure the
                   // velocity also gets the corresponding
                   // update. Otherwise, the deltas used can become
                   // incorrect so a sequence like [ move forward,
                   // rotate right, rotate_left, move backward ],
                   // which should have all elements cancel out, can
                   // end up with a small velocity because the move
                   // backwards is computed using the right local
                   // orientation but the presence's velocity might
                   // not have been updated yet (still waiting for
                   // first timeout).
                   if (is_first)
                       this._reeval(is_first);
                   else
                       system.timeout(.05, std.core.bind(this._reeval, this));
               }
        },
        reeval: function() {
            this._reeval(true);
        },
        _reeval: function(is_first) {
            // Only perform reeval if we really need it
            if (this._moving && this._rotating || is_first)
                this._pres.velocity = this._pres.orientation.mul(this._localVel);

            // Then do callbacks
            if (this._updatecb &&
                (is_first ||
                 this._update_type == this.UPDATE_TYPE_ALL ||
                 (this._update_type == this.UPDATE_TYPE_MOVEMENT && this._moving) ||
                 (this._update_type == this.UPDATE_TYPE_ROTATION && this._rotating))) {
                this._updatecb();
            }

            // Loop, if necessary.
            this._startReeval();
        }
    }
);


add g++ and c++ symlinks

add g++ and c++ symlinks

@[if os_name == 'ubuntu' and os_code_name in ['trusty', 'utopic', 'vivid'] or os_name == 'debian' and os_code_name in ['jessie']]@
@# We need ccache 3.2 or higher: https://github.com/ros-infrastructure/buildfarm_deployment/issues/113
RUN python3 -u /tmp/wrapper_scripts/apt-get.py update-and-install -q -y curl gcc make
RUN curl https://www.samba.org/ftp/ccache/ccache-3.2.4.tar.bz2 -o /tmp/ccache.tar.gz
RUN tar -xf /tmp/ccache.tar.gz --directory /tmp
RUN mkdir -p /usr/lib/ccache
RUN cd /tmp/ccache-3.2.4 && ./configure && make && make install
RUN ln -s /usr/local/bin/ccache /usr/lib/ccache/c++
RUN ln -s /usr/local/bin/ccache /usr/lib/ccache/cc
RUN ln -s /usr/local/bin/ccache /usr/lib/ccache/g++
RUN ln -s /usr/local/bin/ccache /usr/lib/ccache/gcc
@[else]@
RUN python3 -u /tmp/wrapper_scripts/apt-get.py update-and-install -q -y ccache
@[end if]@

@[if os_name == 'ubuntu' and os_code_name in ['trusty', 'utopic', 'vivid'] or os_name == 'debian' and os_code_name in ['jessie']]@
@# We need ccache 3.2 or higher: https://github.com/ros-infrastructure/buildfarm_deployment/issues/113
RUN python3 -u /tmp/wrapper_scripts/apt-get.py update-and-install -q -y curl gcc make
RUN curl https://www.samba.org/ftp/ccache/ccache-3.2.4.tar.bz2 -o /tmp/ccache.tar.gz
RUN tar -xf /tmp/ccache.tar.gz --directory /tmp
RUN mkdir -p /usr/lib/ccache
RUN cd /tmp/ccache-3.2.4 && ./configure && make && make install
RUN ln -s /usr/local/bin/ccache /usr/lib/ccache/gcc
RUN ln -s /usr/local/bin/ccache /usr/lib/ccache/cc
@[else]@
RUN python3 -u /tmp/wrapper_scripts/apt-get.py update-and-install -q -y ccache
@[end if]@


Make simpleStorage handle failure to connect presences by throwing an error warning the user and not invoking the normal callback since the object can't safely continue. Fixes #363.

if (typeof(std) == 'undefined')
    std = {};

if (typeof(std.simpleStorage) != 'undefined')
    throw new Error('Error in simple storage.  Should only import once.');

(function()
{
    system.require('std/shim/restore/persistService.em');
    std.simpleStorage = {  };

    var allKeysKeyname = 'allKeys';
    var prepender      = 'toPrependWith_';

    var scriptKeyName =   'pScriptKeyName';

    //contains the script to execute on restore in its script field.
    var mScript = { };

    //query backend with this index to retrieve mPres object, which lists
    //all presences that we have.
    var presKeyName   =     'pPresKeyName';
    //prevents more than one presence from being stored at a time because
    //we didn't expose batching erases and sets in the same set unfortunately
    var presOperInProgress = false;
    //last elements of the array contain next elements to be processed
    var queuedPresOpers= [];
    //Keys contain all presence sporefs.  Can use these to restore our presences.
    var mPres   = { };

    std.simpleStorage.setField = function(fieldName,fieldVal)
    {
        var keyName = prepender + fieldName;
        std.persist.checkpointPartialPersist(fieldVal,keyName);
    };
    std.simpleStorage.write = std.simpleStorage.setField;

    std.simpleStorage.eraseField = function(fieldName)
    {
        system.storageErase(prepender + fieldName);
    };
    std.simpleStorage.erase = std.simpleStorage.eraseField;

    std.simpleStorage.readField = function(fieldName,cb,def)
    {
        std.persist.restoreFromAsync(prepender + fieldName,
                                     function(success,val)
                                     {
                                         if (success)
                                             cb(val);
                                         else
                                             cb(def);
                                     });
    };
    std.simpleStorage.read = std.simpleStorage.readField;

    std.simpleStorage.setPresence = function (presToRestore)
    {
        //cannot process current presence because already performing a
        //presence operation.
        if (presOperInProgress)
        {
            queuedPresOpers.unshift(['add',presToRestore]);
            return;
        }

        //locks other presence operations from occurring.
        presOperInProgress = true;

        //ensures this presence is added to our list of presences
        var presName = presToRestore.toString();
        mPres[presName] = true;

        // //callback to execute if setPresence works correctly.
        // //essentially all it does is that it begins processing
        // //next presence event.
        var finishedCB = function(success)
        {
            if (!success)
                delete mPres[presName];

            presOperInProgress = false;
            processNextPresenceEvent();
        };

        std.persist.persistMany([[mPres,presKeyName],
                                [presToRestore,presName]], finishedCB);

    };
    std.simpleStorage.writePresence = std.simpleStorage.setPresence;

    std.simpleStorage.erasePresence = function (presToErase)
    {
        //cannot process current presence because already performing a
        //presence operation.
        if (presOperInProgress)
        {
            queuedPresOpers.unshift(['remove',presToErase]);
            return;
        }

        //locks other presence operations from occurring.
        presOperInProgress = true;

        var presName = presToRestore.toString();

        //FIXME: currently cannot batch erase and write operations simultaneously.
        //As a result, mPres and actual presences in backend could get out of sync.
        //When this bug is resolved, the following finished cb will look a lot more
        //like the one in the above func.
        var finishedErasedPres = function(success)
        {
            var finishedUpdatedMPres = function(success)
            {
                if (!success)
                {
                    system.print('\nError in simpleStorage.  Could not delete presence correctly\n');
                }
                presOperInProgress = false;
                processNextPresenceEvent();
            };

            if (success)
            {
                delete mPres[presName];
                std.persist.checkpointPartialPersist(mPres,presKeyName);
                return;
            }
            presOperInProgress = false;
            processNextPresenceEvent();
        };

        system.storageErase(presName,finishedErasePres);
    };

    /**
     Pops the next operation to be processed
     */
    function processNextPresenceEvent()
    {
        if (queuedPresOpers.length == 0)
            return;

        var toProcess = queuedPresOpers.pop();

        if (toProcess[0] == 'add')
            std.simpleStorage.setPresence(toProcess[1]);
        else
            std.simpleStorage.removePresence(toProcess[1]);
    }

    std.simpleStorage.setScript = function(newScriptFunc,executeOnSet)
    {
        var newScript =  ("(" + newScriptFunc + ")();");
        if (typeof(executeOnSet) === 'undefined')
            executeOnSet = false;

        mScript.script = newScript;
        system.setRestoreScript('system.require("std/shim/restore/simpleStorage.em");');

        var cbFunc = function(){ };
        if (executeOnSet)
            cbFunc = function(){ system.eval(newScript);  };

        std.persist.checkpointPartialPersist(mScript,scriptKeyName,cbFunc);
    };

    std.simpleStorage.readScript = function(cb,def)
    {
        std.persist.restoreFromAsync(scriptKeyName,
                                     function(success,val)
                                     {
                                         if ((success) && ('script' in val))
                                             cb(val.script);
                                         else
                                             cb(def);
                                     });
    };

    //evaluates the script once it has been restored.
    function onRestoreScript(scriptToEval)
    {
        system.eval(scriptToEval);
    };

    //restores the script
    function finishOnRestorePresences()
    {
        std.simpleStorage.readScript(onRestoreScript,"");
    }

    /**
     Called when we regain our list of presences.
     Initiates restoring each presence separately.
     */
    function onPresKeyRestored(success,allPres)
    {
        if (success)
            mPres = allPres;

        restorePresences();
    }

    //keeps track of all the presences that we have already restored.
    var allRestored = {};

    //called each time
    function restorePresences()
    {

        // Callback & data to handle synchronizing all the parallel restoration + connection requests
        var num_outstanding = 0;
        var finishRestorePresence = function(presName,success,pres) {
            if (!success) {
                throw new Error('Failed to restore or connect presence ' + presName + ". Bailing on restoration -- this object's script will *not* be executed.");
            }

            num_outstanding--;
            if (num_outstanding == 0) {
                finishOnRestorePresences();
            }
        };

        // Fire off requests for any presences not yet connected
        for (var s in mPres) {
            //if we've already restored this presence ignore it.
            if (s in allRestored)
                continue;
            num_outstanding++;
            allRestored[s] = true;
            std.persist.restoreFromAsync(s,std.core.bind(finishRestorePresence,undefined,s));
        }

        // Special case: no presences to restore.
        if (num_outstanding == 0) finishOnRestorePresences();
    }

    std.persist.restoreFromAsync(presKeyName,onPresKeyRestored);

})();

if (typeof(std) == 'undefined')
    std = {};

if (typeof(std.simpleStorage) != 'undefined')
    throw new Error('Error in simple storage.  Should only import once.');

(function()
{
    system.require('std/shim/restore/persistService.em');
    std.simpleStorage = {  };

    var allKeysKeyname = 'allKeys';
    var prepender      = 'toPrependWith_';

    var scriptKeyName =   'pScriptKeyName';

    //contains the script to execute on restore in its script field.
    var mScript = { };

    //query backend with this index to retrieve mPres object, which lists
    //all presences that we have.
    var presKeyName   =     'pPresKeyName';
    //prevents more than one presence from being stored at a time because
    //we didn't expose batching erases and sets in the same set unfortunately
    var presOperInProgress = false;
    //last elements of the array contain next elements to be processed
    var queuedPresOpers= [];
    //Keys contain all presence sporefs.  Can use these to restore our presences.
    var mPres   = { };

    std.simpleStorage.setField = function(fieldName,fieldVal)
    {
        var keyName = prepender + fieldName;
        std.persist.checkpointPartialPersist(fieldVal,keyName);
    };
    std.simpleStorage.write = std.simpleStorage.setField;

    std.simpleStorage.eraseField = function(fieldName)
    {
        system.storageErase(prepender + fieldName);
    };
    std.simpleStorage.erase = std.simpleStorage.eraseField;

    std.simpleStorage.readField = function(fieldName,cb,def)
    {
        std.persist.restoreFromAsync(prepender + fieldName,
                                     function(success,val)
                                     {
                                         if (success)
                                             cb(val);
                                         else
                                             cb(def);
                                     });
    };
    std.simpleStorage.read = std.simpleStorage.readField;

    std.simpleStorage.setPresence = function (presToRestore)
    {
        //cannot process current presence because already performing a
        //presence operation.
        if (presOperInProgress)
        {
            queuedPresOpers.unshift(['add',presToRestore]);
            return;
        }

        //locks other presence operations from occurring.
        presOperInProgress = true;

        //ensures this presence is added to our list of presences
        var presName = presToRestore.toString();
        mPres[presName] = true;

        // //callback to execute if setPresence works correctly.
        // //essentially all it does is that it begins processing
        // //next presence event.
        var finishedCB = function(success)
        {
            if (!success)
                delete mPres[presName];

            presOperInProgress = false;
            processNextPresenceEvent();
        };

        std.persist.persistMany([[mPres,presKeyName],
                                [presToRestore,presName]], finishedCB);

    };
    std.simpleStorage.writePresence = std.simpleStorage.setPresence;

    std.simpleStorage.erasePresence = function (presToErase)
    {
        //cannot process current presence because already performing a
        //presence operation.
        if (presOperInProgress)
        {
            queuedPresOpers.unshift(['remove',presToErase]);
            return;
        }

        //locks other presence operations from occurring.
        presOperInProgress = true;

        var presName = presToRestore.toString();

        //FIXME: currently cannot batch erase and write operations simultaneously.
        //As a result, mPres and actual presences in backend could get out of sync.
        //When this bug is resolved, the following finished cb will look a lot more
        //like the one in the above func.
        var finishedErasedPres = function(success)
        {
            var finishedUpdatedMPres = function(success)
            {
                if (!success)
                {
                    system.print('\nError in simpleStorage.  Could not delete presence correctly\n');
                }
                presOperInProgress = false;
                processNextPresenceEvent();
            };

            if (success)
            {
                delete mPres[presName];
                std.persist.checkpointPartialPersist(mPres,presKeyName);
                return;
            }
            presOperInProgress = false;
            processNextPresenceEvent();
        };

        system.storageErase(presName,finishedErasePres);
    };

    /**
     Pops the next operation to be processed
     */
    function processNextPresenceEvent()
    {
        if (queuedPresOpers.length == 0)
            return;

        var toProcess = queuedPresOpers.pop();

        if (toProcess[0] == 'add')
            std.simpleStorage.setPresence(toProcess[1]);
        else
            std.simpleStorage.removePresence(toProcess[1]);
    }

    std.simpleStorage.setScript = function(newScriptFunc,executeOnSet)
    {
        var newScript =  ("(" + newScriptFunc + ")();");
        if (typeof(executeOnSet) === 'undefined')
            executeOnSet = false;

        mScript.script = newScript;
        system.setRestoreScript('system.require("std/shim/restore/simpleStorage.em");');

        var cbFunc = function(){ };
        if (executeOnSet)
            cbFunc = function(){ system.eval(newScript);  };

        std.persist.checkpointPartialPersist(mScript,scriptKeyName,cbFunc);
    };

    std.simpleStorage.readScript = function(cb,def)
    {
        std.persist.restoreFromAsync(scriptKeyName,
                                     function(success,val)
                                     {
                                         if ((success) && ('script' in val))
                                             cb(val.script);
                                         else
                                             cb(def);
                                     });
    };

    //evaluates the script once it has been restored.
    function onRestoreScript(scriptToEval)
    {
        system.eval(scriptToEval);
    };

    //restores the script
    function finishOnRestorePresences()
    {
        std.simpleStorage.readScript(onRestoreScript,"");
    }

    /**
     Called when we regain our list of presences.
     Initiates restoring each presence separately.
     */
    function onPresKeyRestored(success,allPres)
    {
        if (success)
            mPres = allPres;

        restorePresences();
    }

    //keeps track of all the presences that we have already restored.
    var allRestored = {};

    //called each time
    function restorePresences(success,pres)
    {

        for (var s in mPres)
        {
            //if we've already restored this presence ignore it.
            if (s in allRestored)
                continue;
            //restore this presence, but call return so that we
            //don't yet restore script (in finishOnRestorePresences).
            allRestored[s] = true;
            std.persist.restoreFromAsync(s,restorePresences);
            return;
        }
        //if had no presences to restore, or they all had already been restored,
        //will call finishOnRestorePresences(), which just restores the script.
        finishOnRestorePresences();
    }

    std.persist.restoreFromAsync(presKeyName,onPresKeyRestored);

})();


Remove old cmake function

Remove old cmake function

function(ros_enable_rpath target)
   # Set ${target} with RPATH built in so that we can install it suid
   set_target_properties(${target} PROPERTIES SKIP_BUILD_RPATH FALSE)

   # Set the install RPATH to the install path
   set(RPATH "${CMAKE_INSTALL_PREFIX}/${CATKIN_PACKAGE_LIB_DESTINATION}")

   # If LD_LIBRARY_PATH is set, add it to the install RPATH
   #  this works in a normal catkin environment, but fails if the user unsets
   #  their LD_LIBRARY_PATH manually for some reason
   if(DEFINED ENV{LD_LIBRARY_PATH})
      set(RPATH "${RPATH}:$ENV{LD_LIBRARY_PATH}")
   endif()

   message("Install RPATH for ${target} is ${RPATH}")

   # Apply our computed RPATH to the target
   set_target_properties(${target} PROPERTIES INSTALL_RPATH ${RPATH})

   # Don't use the final RPATH in devel space
   set_target_properties(${target} PROPERTIES BUILD_WITH_INSTALL_RPATH FALSE)

endfunction()


update plugin version

update plugin version

    <org.jvnet.hudson.plugins.groovypostbuild.GroovyPostbuildRecorder plugin="groovy-postbuild@@2.2">
      <script plugin="script-security@@1.12">
        <script>@ESCAPE(script)</script>
        <sandbox>false</sandbox>
      </script>
      <behavior>2</behavior>
      <runForMatrixParent>false</runForMatrixParent>
    </org.jvnet.hudson.plugins.groovypostbuild.GroovyPostbuildRecorder>

    <org.jvnet.hudson.plugins.groovypostbuild.GroovyPostbuildRecorder plugin="groovy-postbuild@@2.1">
      <script plugin="script-security@@1.10">
        <script>@ESCAPE(script)</script>
        <sandbox>false</sandbox>
      </script>
      <behavior>2</behavior>
      <runForMatrixParent>false</runForMatrixParent>
    </org.jvnet.hudson.plugins.groovypostbuild.GroovyPostbuildRecorder>


missing empy delimiter

missing empy delimiter

Signed-off-by: Mikael Arguedas <a16f295d1139a1392bb19262902c32cf3e999ce4@gmail.com>

@[if 'vcs' in locals()]@
@[  if vcs]@
@[    for i, (imports_name, imports) in enumerate(vcs.items())]@
@{
if imports['repos'] is None:
    imports['repos'] = "https://raw.githubusercontent.com/ros2/ros2/$ROS_DISTRO/ros2.repos"
}@
RUN wget @(imports['repos']) \
    && vcs import @(ws) < @(imports['repos'].split('/')[-1])
@[    end for]@
@[  end if]@
@[end if]@

@[if 'vcs' in locals()]@
@[  if vcs]@
@[    for i, (imports_name, imports) in enumerate(vcs.items())]@
if imports['repos'] is None:
    imports['repos'] = "https://raw.githubusercontent.com/ros2/ros2/$ROS_DISTRO/ros2.repos"
RUN wget @(imports['repos']) \
    && vcs import @(ws) < @(imports['repos'].split('/')[-1])
@[    end for]@
@[  end if]@
@[end if]@


Mark CI jobs unstable based on static analysis (#953)

The threshold for this gate is '1' on ci.ros2.org, and I'm not sure why
it was ever left unset here.

<project>
  <actions/>
  <description>Generated at @ESCAPE(now_str) from template '@ESCAPE(template_name)'@
@[if disabled]
but disabled since the package is blacklisted (or not whitelisted) in the configuration file@
@[end if]@
@ </description>
  <keepDependencies>false</keepDependencies>
  <properties>
@(SNIPPET(
    'property_authorization-matrix',
    project_authorization_xml=project_authorization_xml,
))@
@(SNIPPET(
    'property_log-rotator',
    days_to_keep=730,
    num_to_keep=100,
))@
@[if job_priority is not None]@
@(SNIPPET(
    'property_job-priority',
    priority=job_priority,
))@
@[end if]@
@(SNIPPET(
    'property_rebuild-settings',
))@
@(SNIPPET(
    'property_requeue-job',
))@
@{
parameters = [
    {
        'type': 'boolean',
        'name': 'skip_cleanup',
        'description': 'Skip cleanup of build artifacts as well as rosdoc index',
    },
    {
        'type': 'string',
        'name': 'install_packages',
        'default_value': ' '.join(install_packages),
        'description': 'Package(s) to be installed prior to any packages detected for installation by rosdep (space-separated)',
    },
    {
        'type': 'string',
        'name': 'repos_file_urls',
        'default_value': ' '.join(repos_file_urls),
        'description': 'URL(s) of repos file(s) containing the list of packages to be built (space-separated)',
    },
    {
        'type': 'string',
        'name': 'repository_names',
        'default_value': ' '.join(repository_names),
        'description': 'Repositories names from the rosdistro to be built (space-separated)',
    },
    {
        'type': 'string',
        'name': 'package_names',
        'default_value': ' '.join(package_names),
        'description': 'Released package names from the rosdistro to be built (space-separated)',
    },
    {
        'type': 'boolean',
        'name': 'package_dependencies',
        'default_value': package_dependencies,
        'description': 'If selected, dependencies for packages in package_names will also be built',
    },
    {
        'type': 'string',
        'name': 'test_branch',
        'default_value': test_branch or '',
        'description': 'Branch to attempt to checkout before doing batch job',
    },
    {
        'type': 'string',
        'name': 'package_selection_args',
        'default_value': package_selection_args or '',
        'description': 'Package selection arguments passed to colcon to specify which packages should be built and tested, or blank for ALL',
    },
    {
        'type': 'string',
        'name': 'build_tool_args',
        'default_value': build_tool_args or '',
        'description': 'Arbitrary arguments passed to the build tool',
    },
    {
        'type': 'string',
        'name': 'build_tool_test_args',
        'default_value': build_tool_test_args or '',
        'description': 'Arbitrary arguments passed to the build tool during testing',
    },
]
}@
@(SNIPPET(
    'property_parameters-definition',
    parameters=parameters,
))@
@(SNIPPET(
    'property_job-weight',
    weight=job_weight,
))@
  </properties>
  <scm class="hudson.scm.NullSCM"/>
  <scmCheckoutRetryCount>2</scmCheckoutRetryCount>
  <assignedNode>@(node_label)</assignedNode>
  <canRoam>false</canRoam>
  <disabled>@('true' if disabled else 'false')</disabled>
  <blockBuildWhenDownstreamBuilding>false</blockBuildWhenDownstreamBuilding>
  <blockBuildWhenUpstreamBuilding>false</blockBuildWhenUpstreamBuilding>
  <triggers>
@[if trigger_jobs]@
@(SNIPPET(
    'trigger_reverse-build',
    upstream_projects=trigger_jobs,
))@
@[end if]@
@[if trigger_timer is not None]@
@(SNIPPET(
    'trigger_timer',
    spec=trigger_timer,
))@
@[end if]@
  </triggers>
  <concurrentBuild>true</concurrentBuild>
  <builders>
@(SNIPPET(
    'builder_system-groovy_check-free-disk-space',
))@
@(SNIPPET(
    'builder_shell_docker-info',
))@
@(SNIPPET(
    'builder_check-docker',
    os_name=os_name,
    os_code_name=os_code_name,
    arch=arch,
))@
@(SNIPPET(
    'builder_shell_clone-ros-buildfarm',
    ros_buildfarm_repository=ros_buildfarm_repository,
    wrapper_scripts=wrapper_scripts,
))@
@(SNIPPET(
    'builder_shell_key-files',
    script_generating_key_files=script_generating_key_files,
))@
@[if underlay_source_jobs]@
@(SNIPPET(
    'builder_shell',
    script='\n'.join([
        'echo "# BEGIN SECTION: Prepare package underlay"',
    ]),
))@
@[for i, underlay_source_job in enumerate(underlay_source_jobs, 1)]@
@(SNIPPET(
    'copy_artifacts',
    artifacts=[
      '*.tar.bz2',
    ],
    project=underlay_source_job,
    target_directory='$WORKSPACE/underlay%d' % (i),
))@
@[end for]@
@(SNIPPET(
    'builder_shell',
    script='\n'.join([
        'tar -xjf $WORKSPACE/underlay%d/*.tar.bz2 -C $WORKSPACE/underlay%d' %
        (i + 1, i + 1) for i in range(len(underlay_source_jobs))
    ] + [
        'echo "# END SECTION"',
    ]),
))@
@[end if]@
@(SNIPPET(
    'builder_shell',
    script='\n'.join([
        'rm -fr $WORKSPACE/docker_generating_dockers',
        'mkdir -p $WORKSPACE/docker_generating_dockers',
        '',
        '# monitor all subprocesses and enforce termination',
        'python3 -u $WORKSPACE/ros_buildfarm/scripts/subprocess_reaper.py $$ --cid-file $WORKSPACE/docker_generating_dockers/docker.cid > $WORKSPACE/docker_generating_dockers/subprocess_reaper.log 2>&1 &',
        '# sleep to give python time to startup',
        'sleep 1',
        '',
        '# create a unique dockerfile name prefix',
        '[ -z "$EXECUTOR_NUMBER" ] && export EXECUTOR_NUMBER=0',
        'export DOCKER_IMAGE_PREFIX=executor_$EXECUTOR_NUMBER',
        '',
        '# generate Dockerfile, build and run it',
        '# generating the Dockerfiles for the actual CI tasks',
        'echo "# BEGIN SECTION: Generate Dockerfile - CI tasks"',
        'export TZ="%s"' % timezone,
        'export PYTHONPATH=$WORKSPACE/ros_buildfarm:$PYTHONPATH',
        'if [ "$package_dependencies" = "true" ]; then package_dependencies_arg=--package-dependencies; fi',
        'python3 -u $WORKSPACE/ros_buildfarm/scripts/ci/run_ci_job.py' +
        ' ' + rosdistro_name +
        ' ' + os_name +
        ' ' + os_code_name +
        ' ' + arch +
        ' ' + ' '.join(repository_args) +
        ' --build-tool ' + build_tool +
        ' --ros-version ' + str(ros_version) +
        ' --env-vars ' + ' '.join([v.replace('$', '\\$',) for v in build_environment_variables]) +
        ' --dockerfile-dir $WORKSPACE/docker_generating_dockers' +
        ' --repos-file-urls $repos_file_urls' +
        ' --repository-names $repository_names' +
        ' --package-names $package_names' +
        ' $package_dependencies_arg' +
        ' --test-branch "$test_branch"' +
        ' --skip-rosdep-keys ' + ' '.join(skip_rosdep_keys) +
        ' --install-packages $install_packages' +
        ' --workspace-mount-point /tmp/ws' + ''.join([
            ' /tmp/ws%d' % (i + 2) for i in range(len(underlay_source_paths))
        ]) +
        ' --package-selection-args $package_selection_args' +
        ' --build-tool-args $build_tool_args' +
        ' --build-tool-test-args $build_tool_test_args',
        'echo "# END SECTION"',
        '',
        'echo "# BEGIN SECTION: Build Dockerfile - generating CI tasks"',
        'cd $WORKSPACE/docker_generating_dockers',
        'python3 -u $WORKSPACE/ros_buildfarm/scripts/misc/docker_pull_baseimage.py',
        'docker build --force-rm -t $DOCKER_IMAGE_PREFIX.ci_task_generation.%s .' % (rosdistro_name),
        'echo "# END SECTION"',
        '',
        'echo "# BEGIN SECTION: Run Dockerfile - generating CI tasks"',
        'rm -fr $WORKSPACE/docker_create_workspace',
        'rm -fr $WORKSPACE/docker_build_and_install',
        'rm -fr $WORKSPACE/docker_build_and_test',
        'mkdir -p $WORKSPACE/docker_create_workspace',
        'mkdir -p $WORKSPACE/docker_build_and_install',
        'mkdir -p $WORKSPACE/docker_build_and_test',
        'docker run' +
        ' --rm ' +
        ' --cidfile=$WORKSPACE/docker_generating_dockers/docker.cid' +
        ' -e=HOME=/home/buildfarm' +
        ' -v $WORKSPACE/ros_buildfarm:/tmp/ros_buildfarm:ro' +
        ' -v $WORKSPACE/docker_create_workspace:/tmp/docker_create_workspace' +
        ' -v $WORKSPACE/docker_build_and_install:/tmp/docker_build_and_install' +
        ' -v $WORKSPACE/docker_build_and_test:/tmp/docker_build_and_test' +
        ' $DOCKER_IMAGE_PREFIX.ci_task_generation.%s' % (rosdistro_name),
        'cd -',  # restore pwd when used in scripts
        'echo "# END SECTION"',
    ]),
))@
@(SNIPPET(
    'builder_shell',
    script='\n'.join([
        '# monitor all subprocesses and enforce termination',
        'python3 -u $WORKSPACE/ros_buildfarm/scripts/subprocess_reaper.py $$ --cid-file $WORKSPACE/docker_create_workspace/docker.cid > $WORKSPACE/docker_create_workspace/subprocess_reaper.log 2>&1 &',
        '# sleep to give python time to startup',
        'sleep 1',
        '',
        '# create a unique dockerfile name prefix',
        'export DOCKER_IMAGE_PREFIX=$(date +%s.%N)',
        '',
        'echo "# BEGIN SECTION: Build Dockerfile - create workspace"',
        '# build and run create_workspace Dockerfile',
        'cd $WORKSPACE/docker_create_workspace',
        'python3 -u $WORKSPACE/ros_buildfarm/scripts/misc/docker_pull_baseimage.py',
        'docker build --force-rm -t $DOCKER_IMAGE_PREFIX.ci_create_workspace.%s .' % (rosdistro_name),
        'echo "# END SECTION"',
        '',
        'echo "# BEGIN SECTION: Run Dockerfile - create workspace"',
    ] + [
        'export UNDERLAY%d_JOB_SPACE=$WORKSPACE/underlay%d/ros%d-linux' % (i + 1, i + 1, local_ros_version)
        for i, local_ros_version in zip(range(len(underlay_source_jobs)), [ros_version] * len(underlay_source_jobs))
    ] + [
        'rm -fr $WORKSPACE/ws/src',
        'mkdir -p $WORKSPACE/ws/src',
    ] + [
        'mkdir -p %s' % (dir) for dir in underlay_source_paths
    ] + [
        'docker run' +
        ' --rm ' +
        ' --cidfile=$WORKSPACE/docker_create_workspace/docker.cid' +
        ' -v $WORKSPACE/ros_buildfarm:/tmp/ros_buildfarm:ro' +
        ''.join([
            ' -v %s:/tmp/ws%s/install_isolated:ro' % (space, i if i > 1 else '')
            for i, space in enumerate(underlay_source_paths, 1)
        ]) +
        ' -v $WORKSPACE/ws:/tmp/ws%s' % (len(underlay_source_paths) + 1 if len(underlay_source_paths) else '') +
        ' $DOCKER_IMAGE_PREFIX.ci_create_workspace.%s' % (rosdistro_name),
        'cd -',  # restore pwd when used in scripts
        'echo "# END SECTION"',
    ]),
))@
@(SNIPPET(
    'builder_shell',
    script='\n'.join([
        'echo "# BEGIN SECTION: Copy dependency list"',
        '/bin/cp -f $WORKSPACE/ws/install_list_build.txt $WORKSPACE/docker_build_and_install/install_list_build.txt',
        '/bin/cp -f $WORKSPACE/ws/install_list_build.txt $WORKSPACE/docker_build_and_test/install_list_build.txt',
        '/bin/cp -f $WORKSPACE/ws/install_list_test.txt $WORKSPACE/docker_build_and_test/install_list_test.txt',
        'echo "# END SECTION"',
    ]),
))@
@(SNIPPET(
    'builder_shell',
    script='\n'.join([
        '# monitor all subprocesses and enforce termination',
        'python3 -u $WORKSPACE/ros_buildfarm/scripts/subprocess_reaper.py $$ --cid-file $WORKSPACE/docker_build_and_install/docker.cid > $WORKSPACE/docker_build_and_install/subprocess_reaper.log 2>&1 &',
        '# sleep to give python time to startup',
        'sleep 1',
        '',
        '# create a unique dockerfile name prefix',
        'export DOCKER_IMAGE_PREFIX=$(date +%s.%N)',
        '',
        'echo "# BEGIN SECTION: Build Dockerfile - build and install"',
        '# build and run build and install Dockerfile',
        'cd $WORKSPACE/docker_build_and_install',
        'python3 -u $WORKSPACE/ros_buildfarm/scripts/misc/docker_pull_baseimage.py',
        'docker build --force-rm -t $DOCKER_IMAGE_PREFIX.ci_build_and_install.%s .' % (rosdistro_name),
        'echo "# END SECTION"',
        '',
    ] + ([
        'echo "# BEGIN SECTION: ccache stats (before)"',
        'mkdir -p $HOME/.ccache',
        'docker run' +
        ' --rm ' +
        ' --cidfile=$WORKSPACE/docker_build_and_install/docker_ccache_before.cid' +
        ' -e CCACHE_DIR=/home/buildfarm/.ccache' +
        ' -v $HOME/.ccache:/home/buildfarm/.ccache' +
        ' $DOCKER_IMAGE_PREFIX.ci_build_and_install.%s' % (rosdistro_name) +
        ' "ccache -s"',
        'echo "# END SECTION"',
        '',
    ] if shared_ccache else []) + [
        'echo "# BEGIN SECTION: Run Dockerfile - build and install"',
    ] + [
        'export UNDERLAY%d_JOB_SPACE=$WORKSPACE/underlay%d/ros%d-linux' % (i + 1, i + 1, local_ros_version)
        for i, local_ros_version in zip(range(len(underlay_source_jobs)), [ros_version] * len(underlay_source_jobs))
    ] + [
        'docker run' +
        ' --rm ' +
        ' --cidfile=$WORKSPACE/docker_build_and_install/docker.cid' +
        ((' -e CCACHE_DIR=/home/buildfarm/.ccache' +
          ' -v $HOME/.ccache:/home/buildfarm/.ccache') if shared_ccache else '') +
        ' -v $WORKSPACE/ros_buildfarm:/tmp/ros_buildfarm:ro' +
        ''.join([
            ' -v %s:/tmp/ws%s/install_isolated:ro' % (space, i if i > 1 else '')
            for i, space in enumerate(underlay_source_paths, 1)
        ]) +
        ' -v $WORKSPACE/ws:/tmp/ws%s' % (len(underlay_source_paths) + 1 if len(underlay_source_paths) else '') +
        ' $DOCKER_IMAGE_PREFIX.ci_build_and_install.%s' % (rosdistro_name),
        'cd -',  # restore pwd when used in scripts
        'echo "# END SECTION"',
    ] + ([
        '',
        'echo "# BEGIN SECTION: ccache stats (after)"',
        'docker run' +
        ' --rm ' +
        ' --cidfile=$WORKSPACE/docker_build_and_install/docker_ccache_after.cid' +
        ' -e CCACHE_DIR=/home/buildfarm/.ccache' +
        ' -v $HOME/.ccache:/home/buildfarm/.ccache' +
        ' $DOCKER_IMAGE_PREFIX.ci_build_and_install.%s' % (rosdistro_name) +
        ' "ccache -s"',
        'echo "# END SECTION"',
    ] if shared_ccache else [])),
))@
@(SNIPPET(
    'builder_shell',
    script='\n'.join([
        'echo "# BEGIN SECTION: Compress install space"',
        'tar -cjf $WORKSPACE/ros%d-%s-linux-%s-%s-ci.tar.bz2 ' % (ros_version, rosdistro_name, os_code_name, arch) +
        ' -C $WORKSPACE/ws' +
        ' --transform "s/^install_isolated/ros%d-linux/"' % (ros_version) +
        ' install_isolated',
        'echo "# END SECTION"',
    ]),
))@
@(SNIPPET(
    'builder_shell',
    script='\n'.join([
        '# monitor all subprocesses and enforce termination',
        'python3 -u $WORKSPACE/ros_buildfarm/scripts/subprocess_reaper.py $$ --cid-file $WORKSPACE/docker_build_and_test/docker.cid > $WORKSPACE/docker_build_and_test/subprocess_reaper.log 2>&1 &',
        '# sleep to give python time to startup',
        'sleep 1',
        '',
        '# create a unique dockerfile name prefix',
        'export DOCKER_IMAGE_PREFIX=$(date +%s.%N)',
        '',
        'echo "# BEGIN SECTION: Build Dockerfile - build and test"',
        '# build and run build and test Dockerfile',
        'cd $WORKSPACE/docker_build_and_test',
        'python3 -u $WORKSPACE/ros_buildfarm/scripts/misc/docker_pull_baseimage.py',
        'docker build --force-rm -t $DOCKER_IMAGE_PREFIX.ci_build_and_test.%s .' % (rosdistro_name),
        'echo "# END SECTION"',
        '',
    ] + ([
        'echo "# BEGIN SECTION: ccache stats (before)"',
        'mkdir -p $HOME/.ccache',
        'docker run' +
        ' --rm ' +
        ' --cidfile=$WORKSPACE/docker_build_and_test/docker_ccache_before.cid' +
        ' -e CCACHE_DIR=/home/buildfarm/.ccache' +
        ' -v $HOME/.ccache:/home/buildfarm/.ccache' +
        ' $DOCKER_IMAGE_PREFIX.ci_build_and_test.%s' % (rosdistro_name) +
        ' "ccache -s"',
        'echo "# END SECTION"',
        '',
    ] if shared_ccache else []) + [
        'echo "# BEGIN SECTION: Run Dockerfile - build and test"',
    ] + [
        'export UNDERLAY%d_JOB_SPACE=$WORKSPACE/underlay%d/ros%d-linux' % (i + 1, i + 1, local_ros_version)
        for i, local_ros_version in zip(range(len(underlay_source_jobs)), [ros_version] * len(underlay_source_jobs))
    ] + [
        'rm -fr $WORKSPACE/ws/test_results',
        'mkdir -p $WORKSPACE/ws/test_results',
        'docker run' +
        ' --rm ' +
        ' --cidfile=$WORKSPACE/docker_build_and_test/docker.cid' +
        ((' -e CCACHE_DIR=/home/buildfarm/.ccache' +
          ' -v $HOME/.ccache:/home/buildfarm/.ccache') if shared_ccache else '') +
        ' -v $WORKSPACE/ros_buildfarm:/tmp/ros_buildfarm:ro' +
        ''.join([
            ' -v %s:/tmp/ws%s/install_isolated:ro' % (space, i if i > 1 else '')
            for i, space in enumerate(underlay_source_paths, 1)
        ]) +
        ' -v $WORKSPACE/ws:/tmp/ws%s' % (len(underlay_source_paths) + 1 if len(underlay_source_paths) else '') +
        ' $DOCKER_IMAGE_PREFIX.ci_build_and_test.%s' % (rosdistro_name),
        'cd -',  # restore pwd when used in scripts
        'echo "# END SECTION"',
    ] + ([
        '',
        'echo "# BEGIN SECTION: ccache stats (after)"',
        'docker run' +
        ' --rm ' +
        ' --cidfile=$WORKSPACE/docker_build_and_test/docker_ccache_after.cid' +
        ' -e CCACHE_DIR=/home/buildfarm/.ccache' +
        ' -v $HOME/.ccache:/home/buildfarm/.ccache' +
        ' $DOCKER_IMAGE_PREFIX.ci_build_and_test.%s' % (rosdistro_name) +
        ' "ccache -s"',
        'echo "# END SECTION"',
    ] if shared_ccache else [])),
))@
@(SNIPPET(
    'builder_shell',
    script='\n'.join([
        'if [ "$skip_cleanup" = "false" ]; then',
        'echo "# BEGIN SECTION: Clean up to save disk space on agents"',
        'rm -fr ws/build_isolated',
        'rm -fr ws/devel_isolated',
        'rm -fr ws/install_isolated',
        'echo "# END SECTION"',
        'fi',
    ]),
))@
  </builders>
  <publishers>
@(SNIPPET(
    'publisher_warnings',
    build_tool=build_tool,
    unstable_threshold='1',
))@
@(SNIPPET(
    'archive_artifacts',
    artifacts=[
      'ros%d-%s-linux-%s-%s-ci.tar.bz2' % (ros_version, rosdistro_name, os_code_name, arch),
    ] + archive_files + [
      image for images in show_images.values() for image in images
    ],
))@
@[if upload_directory]@
@(SNIPPET(
    'publisher_publish-over-ssh',
    config_name='ci_archives',
    remote_directory=upload_directory,
    source_files=[
        'ros%d-%s-linux-%s-%s-ci.tar.bz2' % (ros_version, rosdistro_name, os_code_name, arch),
    ],
    remove_prefix=None,
))@
@[end if]@
@[if benchmark_patterns]@
@(SNIPPET(
    'publisher_benchmark',
    patterns=benchmark_patterns,
    schema=benchmark_schema,
))@
@[end if]@
@[for title, artifacts in show_images.items()]@
@(SNIPPET(
    'image_gallery',
    title=title,
    image_width='',
    artifacts=artifacts,
))@
@[end for]@
@[if show_plots]@
@(SNIPPET(
    'plot_plugin',
    plots=show_plots,
))@
@[end if]@
@[if xunit_publisher_types]@
@(SNIPPET(
    'publisher_xunit',
    types=xunit_publisher_types,
))@
@[end if]@
  </publishers>
  <buildWrappers>
@[if timeout_minutes is not None]@
@(SNIPPET(
    'build-wrapper_build-timeout',
    timeout_minutes=timeout_minutes,
))@
@[end if]@
@(SNIPPET(
    'build-wrapper_timestamper',
))@
  </buildWrappers>
</project>

<project>
  <actions/>
  <description>Generated at @ESCAPE(now_str) from template '@ESCAPE(template_name)'@
@[if disabled]
but disabled since the package is blacklisted (or not whitelisted) in the configuration file@
@[end if]@
@ </description>
  <keepDependencies>false</keepDependencies>
  <properties>
@(SNIPPET(
    'property_authorization-matrix',
    project_authorization_xml=project_authorization_xml,
))@
@(SNIPPET(
    'property_log-rotator',
    days_to_keep=730,
    num_to_keep=100,
))@
@[if job_priority is not None]@
@(SNIPPET(
    'property_job-priority',
    priority=job_priority,
))@
@[end if]@
@(SNIPPET(
    'property_rebuild-settings',
))@
@(SNIPPET(
    'property_requeue-job',
))@
@{
parameters = [
    {
        'type': 'boolean',
        'name': 'skip_cleanup',
        'description': 'Skip cleanup of build artifacts as well as rosdoc index',
    },
    {
        'type': 'string',
        'name': 'install_packages',
        'default_value': ' '.join(install_packages),
        'description': 'Package(s) to be installed prior to any packages detected for installation by rosdep (space-separated)',
    },
    {
        'type': 'string',
        'name': 'repos_file_urls',
        'default_value': ' '.join(repos_file_urls),
        'description': 'URL(s) of repos file(s) containing the list of packages to be built (space-separated)',
    },
    {
        'type': 'string',
        'name': 'repository_names',
        'default_value': ' '.join(repository_names),
        'description': 'Repositories names from the rosdistro to be built (space-separated)',
    },
    {
        'type': 'string',
        'name': 'package_names',
        'default_value': ' '.join(package_names),
        'description': 'Released package names from the rosdistro to be built (space-separated)',
    },
    {
        'type': 'boolean',
        'name': 'package_dependencies',
        'default_value': package_dependencies,
        'description': 'If selected, dependencies for packages in package_names will also be built',
    },
    {
        'type': 'string',
        'name': 'test_branch',
        'default_value': test_branch or '',
        'description': 'Branch to attempt to checkout before doing batch job',
    },
    {
        'type': 'string',
        'name': 'package_selection_args',
        'default_value': package_selection_args or '',
        'description': 'Package selection arguments passed to colcon to specify which packages should be built and tested, or blank for ALL',
    },
    {
        'type': 'string',
        'name': 'build_tool_args',
        'default_value': build_tool_args or '',
        'description': 'Arbitrary arguments passed to the build tool',
    },
    {
        'type': 'string',
        'name': 'build_tool_test_args',
        'default_value': build_tool_test_args or '',
        'description': 'Arbitrary arguments passed to the build tool during testing',
    },
]
}@
@(SNIPPET(
    'property_parameters-definition',
    parameters=parameters,
))@
@(SNIPPET(
    'property_job-weight',
    weight=job_weight,
))@
  </properties>
  <scm class="hudson.scm.NullSCM"/>
  <scmCheckoutRetryCount>2</scmCheckoutRetryCount>
  <assignedNode>@(node_label)</assignedNode>
  <canRoam>false</canRoam>
  <disabled>@('true' if disabled else 'false')</disabled>
  <blockBuildWhenDownstreamBuilding>false</blockBuildWhenDownstreamBuilding>
  <blockBuildWhenUpstreamBuilding>false</blockBuildWhenUpstreamBuilding>
  <triggers>
@[if trigger_jobs]@
@(SNIPPET(
    'trigger_reverse-build',
    upstream_projects=trigger_jobs,
))@
@[end if]@
@[if trigger_timer is not None]@
@(SNIPPET(
    'trigger_timer',
    spec=trigger_timer,
))@
@[end if]@
  </triggers>
  <concurrentBuild>true</concurrentBuild>
  <builders>
@(SNIPPET(
    'builder_system-groovy_check-free-disk-space',
))@
@(SNIPPET(
    'builder_shell_docker-info',
))@
@(SNIPPET(
    'builder_check-docker',
    os_name=os_name,
    os_code_name=os_code_name,
    arch=arch,
))@
@(SNIPPET(
    'builder_shell_clone-ros-buildfarm',
    ros_buildfarm_repository=ros_buildfarm_repository,
    wrapper_scripts=wrapper_scripts,
))@
@(SNIPPET(
    'builder_shell_key-files',
    script_generating_key_files=script_generating_key_files,
))@
@[if underlay_source_jobs]@
@(SNIPPET(
    'builder_shell',
    script='\n'.join([
        'echo "# BEGIN SECTION: Prepare package underlay"',
    ]),
))@
@[for i, underlay_source_job in enumerate(underlay_source_jobs, 1)]@
@(SNIPPET(
    'copy_artifacts',
    artifacts=[
      '*.tar.bz2',
    ],
    project=underlay_source_job,
    target_directory='$WORKSPACE/underlay%d' % (i),
))@
@[end for]@
@(SNIPPET(
    'builder_shell',
    script='\n'.join([
        'tar -xjf $WORKSPACE/underlay%d/*.tar.bz2 -C $WORKSPACE/underlay%d' %
        (i + 1, i + 1) for i in range(len(underlay_source_jobs))
    ] + [
        'echo "# END SECTION"',
    ]),
))@
@[end if]@
@(SNIPPET(
    'builder_shell',
    script='\n'.join([
        'rm -fr $WORKSPACE/docker_generating_dockers',
        'mkdir -p $WORKSPACE/docker_generating_dockers',
        '',
        '# monitor all subprocesses and enforce termination',
        'python3 -u $WORKSPACE/ros_buildfarm/scripts/subprocess_reaper.py $$ --cid-file $WORKSPACE/docker_generating_dockers/docker.cid > $WORKSPACE/docker_generating_dockers/subprocess_reaper.log 2>&1 &',
        '# sleep to give python time to startup',
        'sleep 1',
        '',
        '# create a unique dockerfile name prefix',
        '[ -z "$EXECUTOR_NUMBER" ] && export EXECUTOR_NUMBER=0',
        'export DOCKER_IMAGE_PREFIX=executor_$EXECUTOR_NUMBER',
        '',
        '# generate Dockerfile, build and run it',
        '# generating the Dockerfiles for the actual CI tasks',
        'echo "# BEGIN SECTION: Generate Dockerfile - CI tasks"',
        'export TZ="%s"' % timezone,
        'export PYTHONPATH=$WORKSPACE/ros_buildfarm:$PYTHONPATH',
        'if [ "$package_dependencies" = "true" ]; then package_dependencies_arg=--package-dependencies; fi',
        'python3 -u $WORKSPACE/ros_buildfarm/scripts/ci/run_ci_job.py' +
        ' ' + rosdistro_name +
        ' ' + os_name +
        ' ' + os_code_name +
        ' ' + arch +
        ' ' + ' '.join(repository_args) +
        ' --build-tool ' + build_tool +
        ' --ros-version ' + str(ros_version) +
        ' --env-vars ' + ' '.join([v.replace('$', '\\$',) for v in build_environment_variables]) +
        ' --dockerfile-dir $WORKSPACE/docker_generating_dockers' +
        ' --repos-file-urls $repos_file_urls' +
        ' --repository-names $repository_names' +
        ' --package-names $package_names' +
        ' $package_dependencies_arg' +
        ' --test-branch "$test_branch"' +
        ' --skip-rosdep-keys ' + ' '.join(skip_rosdep_keys) +
        ' --install-packages $install_packages' +
        ' --workspace-mount-point /tmp/ws' + ''.join([
            ' /tmp/ws%d' % (i + 2) for i in range(len(underlay_source_paths))
        ]) +
        ' --package-selection-args $package_selection_args' +
        ' --build-tool-args $build_tool_args' +
        ' --build-tool-test-args $build_tool_test_args',
        'echo "# END SECTION"',
        '',
        'echo "# BEGIN SECTION: Build Dockerfile - generating CI tasks"',
        'cd $WORKSPACE/docker_generating_dockers',
        'python3 -u $WORKSPACE/ros_buildfarm/scripts/misc/docker_pull_baseimage.py',
        'docker build --force-rm -t $DOCKER_IMAGE_PREFIX.ci_task_generation.%s .' % (rosdistro_name),
        'echo "# END SECTION"',
        '',
        'echo "# BEGIN SECTION: Run Dockerfile - generating CI tasks"',
        'rm -fr $WORKSPACE/docker_create_workspace',
        'rm -fr $WORKSPACE/docker_build_and_install',
        'rm -fr $WORKSPACE/docker_build_and_test',
        'mkdir -p $WORKSPACE/docker_create_workspace',
        'mkdir -p $WORKSPACE/docker_build_and_install',
        'mkdir -p $WORKSPACE/docker_build_and_test',
        'docker run' +
        ' --rm ' +
        ' --cidfile=$WORKSPACE/docker_generating_dockers/docker.cid' +
        ' -e=HOME=/home/buildfarm' +
        ' -v $WORKSPACE/ros_buildfarm:/tmp/ros_buildfarm:ro' +
        ' -v $WORKSPACE/docker_create_workspace:/tmp/docker_create_workspace' +
        ' -v $WORKSPACE/docker_build_and_install:/tmp/docker_build_and_install' +
        ' -v $WORKSPACE/docker_build_and_test:/tmp/docker_build_and_test' +
        ' $DOCKER_IMAGE_PREFIX.ci_task_generation.%s' % (rosdistro_name),
        'cd -',  # restore pwd when used in scripts
        'echo "# END SECTION"',
    ]),
))@
@(SNIPPET(
    'builder_shell',
    script='\n'.join([
        '# monitor all subprocesses and enforce termination',
        'python3 -u $WORKSPACE/ros_buildfarm/scripts/subprocess_reaper.py $$ --cid-file $WORKSPACE/docker_create_workspace/docker.cid > $WORKSPACE/docker_create_workspace/subprocess_reaper.log 2>&1 &',
        '# sleep to give python time to startup',
        'sleep 1',
        '',
        '# create a unique dockerfile name prefix',
        'export DOCKER_IMAGE_PREFIX=$(date +%s.%N)',
        '',
        'echo "# BEGIN SECTION: Build Dockerfile - create workspace"',
        '# build and run create_workspace Dockerfile',
        'cd $WORKSPACE/docker_create_workspace',
        'python3 -u $WORKSPACE/ros_buildfarm/scripts/misc/docker_pull_baseimage.py',
        'docker build --force-rm -t $DOCKER_IMAGE_PREFIX.ci_create_workspace.%s .' % (rosdistro_name),
        'echo "# END SECTION"',
        '',
        'echo "# BEGIN SECTION: Run Dockerfile - create workspace"',
    ] + [
        'export UNDERLAY%d_JOB_SPACE=$WORKSPACE/underlay%d/ros%d-linux' % (i + 1, i + 1, local_ros_version)
        for i, local_ros_version in zip(range(len(underlay_source_jobs)), [ros_version] * len(underlay_source_jobs))
    ] + [
        'rm -fr $WORKSPACE/ws/src',
        'mkdir -p $WORKSPACE/ws/src',
    ] + [
        'mkdir -p %s' % (dir) for dir in underlay_source_paths
    ] + [
        'docker run' +
        ' --rm ' +
        ' --cidfile=$WORKSPACE/docker_create_workspace/docker.cid' +
        ' -v $WORKSPACE/ros_buildfarm:/tmp/ros_buildfarm:ro' +
        ''.join([
            ' -v %s:/tmp/ws%s/install_isolated:ro' % (space, i if i > 1 else '')
            for i, space in enumerate(underlay_source_paths, 1)
        ]) +
        ' -v $WORKSPACE/ws:/tmp/ws%s' % (len(underlay_source_paths) + 1 if len(underlay_source_paths) else '') +
        ' $DOCKER_IMAGE_PREFIX.ci_create_workspace.%s' % (rosdistro_name),
        'cd -',  # restore pwd when used in scripts
        'echo "# END SECTION"',
    ]),
))@
@(SNIPPET(
    'builder_shell',
    script='\n'.join([
        'echo "# BEGIN SECTION: Copy dependency list"',
        '/bin/cp -f $WORKSPACE/ws/install_list_build.txt $WORKSPACE/docker_build_and_install/install_list_build.txt',
        '/bin/cp -f $WORKSPACE/ws/install_list_build.txt $WORKSPACE/docker_build_and_test/install_list_build.txt',
        '/bin/cp -f $WORKSPACE/ws/install_list_test.txt $WORKSPACE/docker_build_and_test/install_list_test.txt',
        'echo "# END SECTION"',
    ]),
))@
@(SNIPPET(
    'builder_shell',
    script='\n'.join([
        '# monitor all subprocesses and enforce termination',
        'python3 -u $WORKSPACE/ros_buildfarm/scripts/subprocess_reaper.py $$ --cid-file $WORKSPACE/docker_build_and_install/docker.cid > $WORKSPACE/docker_build_and_install/subprocess_reaper.log 2>&1 &',
        '# sleep to give python time to startup',
        'sleep 1',
        '',
        '# create a unique dockerfile name prefix',
        'export DOCKER_IMAGE_PREFIX=$(date +%s.%N)',
        '',
        'echo "# BEGIN SECTION: Build Dockerfile - build and install"',
        '# build and run build and install Dockerfile',
        'cd $WORKSPACE/docker_build_and_install',
        'python3 -u $WORKSPACE/ros_buildfarm/scripts/misc/docker_pull_baseimage.py',
        'docker build --force-rm -t $DOCKER_IMAGE_PREFIX.ci_build_and_install.%s .' % (rosdistro_name),
        'echo "# END SECTION"',
        '',
    ] + ([
        'echo "# BEGIN SECTION: ccache stats (before)"',
        'mkdir -p $HOME/.ccache',
        'docker run' +
        ' --rm ' +
        ' --cidfile=$WORKSPACE/docker_build_and_install/docker_ccache_before.cid' +
        ' -e CCACHE_DIR=/home/buildfarm/.ccache' +
        ' -v $HOME/.ccache:/home/buildfarm/.ccache' +
        ' $DOCKER_IMAGE_PREFIX.ci_build_and_install.%s' % (rosdistro_name) +
        ' "ccache -s"',
        'echo "# END SECTION"',
        '',
    ] if shared_ccache else []) + [
        'echo "# BEGIN SECTION: Run Dockerfile - build and install"',
    ] + [
        'export UNDERLAY%d_JOB_SPACE=$WORKSPACE/underlay%d/ros%d-linux' % (i + 1, i + 1, local_ros_version)
        for i, local_ros_version in zip(range(len(underlay_source_jobs)), [ros_version] * len(underlay_source_jobs))
    ] + [
        'docker run' +
        ' --rm ' +
        ' --cidfile=$WORKSPACE/docker_build_and_install/docker.cid' +
        ((' -e CCACHE_DIR=/home/buildfarm/.ccache' +
          ' -v $HOME/.ccache:/home/buildfarm/.ccache') if shared_ccache else '') +
        ' -v $WORKSPACE/ros_buildfarm:/tmp/ros_buildfarm:ro' +
        ''.join([
            ' -v %s:/tmp/ws%s/install_isolated:ro' % (space, i if i > 1 else '')
            for i, space in enumerate(underlay_source_paths, 1)
        ]) +
        ' -v $WORKSPACE/ws:/tmp/ws%s' % (len(underlay_source_paths) + 1 if len(underlay_source_paths) else '') +
        ' $DOCKER_IMAGE_PREFIX.ci_build_and_install.%s' % (rosdistro_name),
        'cd -',  # restore pwd when used in scripts
        'echo "# END SECTION"',
    ] + ([
        '',
        'echo "# BEGIN SECTION: ccache stats (after)"',
        'docker run' +
        ' --rm ' +
        ' --cidfile=$WORKSPACE/docker_build_and_install/docker_ccache_after.cid' +
        ' -e CCACHE_DIR=/home/buildfarm/.ccache' +
        ' -v $HOME/.ccache:/home/buildfarm/.ccache' +
        ' $DOCKER_IMAGE_PREFIX.ci_build_and_install.%s' % (rosdistro_name) +
        ' "ccache -s"',
        'echo "# END SECTION"',
    ] if shared_ccache else [])),
))@
@(SNIPPET(
    'builder_shell',
    script='\n'.join([
        'echo "# BEGIN SECTION: Compress install space"',
        'tar -cjf $WORKSPACE/ros%d-%s-linux-%s-%s-ci.tar.bz2 ' % (ros_version, rosdistro_name, os_code_name, arch) +
        ' -C $WORKSPACE/ws' +
        ' --transform "s/^install_isolated/ros%d-linux/"' % (ros_version) +
        ' install_isolated',
        'echo "# END SECTION"',
    ]),
))@
@(SNIPPET(
    'builder_shell',
    script='\n'.join([
        '# monitor all subprocesses and enforce termination',
        'python3 -u $WORKSPACE/ros_buildfarm/scripts/subprocess_reaper.py $$ --cid-file $WORKSPACE/docker_build_and_test/docker.cid > $WORKSPACE/docker_build_and_test/subprocess_reaper.log 2>&1 &',
        '# sleep to give python time to startup',
        'sleep 1',
        '',
        '# create a unique dockerfile name prefix',
        'export DOCKER_IMAGE_PREFIX=$(date +%s.%N)',
        '',
        'echo "# BEGIN SECTION: Build Dockerfile - build and test"',
        '# build and run build and test Dockerfile',
        'cd $WORKSPACE/docker_build_and_test',
        'python3 -u $WORKSPACE/ros_buildfarm/scripts/misc/docker_pull_baseimage.py',
        'docker build --force-rm -t $DOCKER_IMAGE_PREFIX.ci_build_and_test.%s .' % (rosdistro_name),
        'echo "# END SECTION"',
        '',
    ] + ([
        'echo "# BEGIN SECTION: ccache stats (before)"',
        'mkdir -p $HOME/.ccache',
        'docker run' +
        ' --rm ' +
        ' --cidfile=$WORKSPACE/docker_build_and_test/docker_ccache_before.cid' +
        ' -e CCACHE_DIR=/home/buildfarm/.ccache' +
        ' -v $HOME/.ccache:/home/buildfarm/.ccache' +
        ' $DOCKER_IMAGE_PREFIX.ci_build_and_test.%s' % (rosdistro_name) +
        ' "ccache -s"',
        'echo "# END SECTION"',
        '',
    ] if shared_ccache else []) + [
        'echo "# BEGIN SECTION: Run Dockerfile - build and test"',
    ] + [
        'export UNDERLAY%d_JOB_SPACE=$WORKSPACE/underlay%d/ros%d-linux' % (i + 1, i + 1, local_ros_version)
        for i, local_ros_version in zip(range(len(underlay_source_jobs)), [ros_version] * len(underlay_source_jobs))
    ] + [
        'rm -fr $WORKSPACE/ws/test_results',
        'mkdir -p $WORKSPACE/ws/test_results',
        'docker run' +
        ' --rm ' +
        ' --cidfile=$WORKSPACE/docker_build_and_test/docker.cid' +
        ((' -e CCACHE_DIR=/home/buildfarm/.ccache' +
          ' -v $HOME/.ccache:/home/buildfarm/.ccache') if shared_ccache else '') +
        ' -v $WORKSPACE/ros_buildfarm:/tmp/ros_buildfarm:ro' +
        ''.join([
            ' -v %s:/tmp/ws%s/install_isolated:ro' % (space, i if i > 1 else '')
            for i, space in enumerate(underlay_source_paths, 1)
        ]) +
        ' -v $WORKSPACE/ws:/tmp/ws%s' % (len(underlay_source_paths) + 1 if len(underlay_source_paths) else '') +
        ' $DOCKER_IMAGE_PREFIX.ci_build_and_test.%s' % (rosdistro_name),
        'cd -',  # restore pwd when used in scripts
        'echo "# END SECTION"',
    ] + ([
        '',
        'echo "# BEGIN SECTION: ccache stats (after)"',
        'docker run' +
        ' --rm ' +
        ' --cidfile=$WORKSPACE/docker_build_and_test/docker_ccache_after.cid' +
        ' -e CCACHE_DIR=/home/buildfarm/.ccache' +
        ' -v $HOME/.ccache:/home/buildfarm/.ccache' +
        ' $DOCKER_IMAGE_PREFIX.ci_build_and_test.%s' % (rosdistro_name) +
        ' "ccache -s"',
        'echo "# END SECTION"',
    ] if shared_ccache else [])),
))@
@(SNIPPET(
    'builder_shell',
    script='\n'.join([
        'if [ "$skip_cleanup" = "false" ]; then',
        'echo "# BEGIN SECTION: Clean up to save disk space on agents"',
        'rm -fr ws/build_isolated',
        'rm -fr ws/devel_isolated',
        'rm -fr ws/install_isolated',
        'echo "# END SECTION"',
        'fi',
    ]),
))@
  </builders>
  <publishers>
@(SNIPPET(
    'publisher_warnings',
    build_tool=build_tool,
    unstable_threshold='',
))@
@(SNIPPET(
    'archive_artifacts',
    artifacts=[
      'ros%d-%s-linux-%s-%s-ci.tar.bz2' % (ros_version, rosdistro_name, os_code_name, arch),
    ] + archive_files + [
      image for images in show_images.values() for image in images
    ],
))@
@[if upload_directory]@
@(SNIPPET(
    'publisher_publish-over-ssh',
    config_name='ci_archives',
    remote_directory=upload_directory,
    source_files=[
        'ros%d-%s-linux-%s-%s-ci.tar.bz2' % (ros_version, rosdistro_name, os_code_name, arch),
    ],
    remove_prefix=None,
))@
@[end if]@
@[if benchmark_patterns]@
@(SNIPPET(
    'publisher_benchmark',
    patterns=benchmark_patterns,
    schema=benchmark_schema,
))@
@[end if]@
@[for title, artifacts in show_images.items()]@
@(SNIPPET(
    'image_gallery',
    title=title,
    image_width='',
    artifacts=artifacts,
))@
@[end for]@
@[if show_plots]@
@(SNIPPET(
    'plot_plugin',
    plots=show_plots,
))@
@[end if]@
@[if xunit_publisher_types]@
@(SNIPPET(
    'publisher_xunit',
    types=xunit_publisher_types,
))@
@[end if]@
  </publishers>
  <buildWrappers>
@[if timeout_minutes is not None]@
@(SNIPPET(
    'build-wrapper_build-timeout',
    timeout_minutes=timeout_minutes,
))@
@[end if]@
@(SNIPPET(
    'build-wrapper_timestamper',
))@
  </buildWrappers>
</project>