• Interfacing with the Language Plugin API for Tessel 2

    Tuesday, August 9, 2016

    This month we merged one of the most significant pull requests for the Tessel project: adding Rust deployment support. For the past three years we’ve focused exclusively on deploying JavaScript on microcontrollers. This is our first step investing in a new language, a new ecosystem, and a new community. However, the purpose of this post is not to dive into the benefits of Rust, but rather to showcase the ease of adding new language support to the Tessel CLI.

    Despite the significance of this addition to the CLI codebase, the amount of code we modified was relatively small. We simply added two main files (along with minor modifications to a handful of other tests): one with the implementation and one with unit tests. The simplicity of the feature addition is a testament @rwaldron’s hard work building the CLI’s language plugin system. In this post, I want to demonstrate how straightforward it is to add support for a new language to the CLI using this plugin system.

    You can deploy an arbitrary code bundle to Tessel 2 (in any language) with the same six general functions:

    1. Determine project location in the file system
    2. Run any compilation/pre-processing steps (optional)
    3. Bundle the executable code into a tarball
    4. Run any pre-execution steps (optional)
    5. Execute the code/binary
    6. Run any post-execution steps (optional)

    These functions are more concisely named checkConfiguration, preBundle, tarBundle, preRun, shell/binary, and postRun (respectively). Here is an explanation of each of those interfaces.

    1. checkConfiguration

    checkConfiguration accepts the directory that was deployed with t2 run ... and returns the path to the entry point of the project. With Rust, we do this by parsing the Cargo.toml and with JavaScript we parse the project’s package.json. It is expected to return an object with a basename and optional program property (for the name of the project).

    // checkConfiguration to confirm project deployment files
    exportables.checkConfiguration = (pushdir, basename, program) => {
      // Should return the file information for the program
      return {
        basename,
        program
      };
    };
    

    2. preBundle

    preBundle is an optional step for any processes that need to be run prior to packaging the directory that will be sent to the Tessel 2. For example, when deploying JavaScript, this function is integral to the binary compilation stage (where we inject our pre-compiled binaries in place of existing project binary dependencies). For Rust deployment, this step is less critital but we use it to set some optional parameters (which we could do in the next function as well).

    // Optional function to process options prior to bundling project
    exportables.preBundle = function(options) {
      // Should return a Promise which gets resolved when the code is ready to be tarred
      return Promise.resolve();
    };
    

    3. tarBundle

    tarBundle is a required step which should resolve a Promise with a Buffer that represents the tarballed code or binary. The CLI will automatically untar the bundle after it is deployed on the Tessel 2.

    In the case of Rust, this was the biggest addition. In order to compile Rust code for Tessel, we needed to set up a special cross-compiler that could compile a binary that runs on Tessel’s MIPS architecture. The easiest solution was to have the CLI send out the project to a preconfigured remote cross-compilation server. The server then compiles and returns the binary to the CLI.

    // This must implement a Promise that resolves with a Buffer
    // that represents a DIRECTORY containing the desired bundle.
    exportables.tarBundle = function(options) {
      // Should return a Promise which eventually gets resolved with the bundle
      return Promise.resolve(bundle);
    };
    

    4. preRun

    preRun is an optional step that’s useful if you need to call any shell functions on Tessel itself before the project is initialized. For example, with Rust, we set the permissions of the binary to ensure it’s executable. It’s unused for JavaScript.

    // Optionally make changes within Tessel's Linux environment before starting the program
    exportables.preRun = function(tessel, options) {
      // Should return a Promise which eventually gets resolved
      return Promise.resolve();
    };
    

    5. shell/binary

    t2 run

    binary is a string that represents the engine that runs the program. It is used as the first argument in a shell command to start the program when a project is run. For JavaScript, this string is node and for Rust it’s ..

    t2 push

    shell is a function that could have been named begin because it’s responsible for actually getting the program going when you push the project. The reason it’s called shell instead is because the function needs to return a string representing the contents of a shell script that starts the program.

    exportables.binary = 'YOUR_EXECUTION_METHOD';
    exportables.shell = (options) => {
      return tags.stripIndent `
        #!/bin/sh
        exec YOUR_EXECUTION_METHOD /app/remote-script/${opts.resolvedEntryPoint}
      `;
    };
    

    6. postRun

    postRun is another optional step that’s useful if there is code you need to run on Tessel after the program has started (but not necessarily before the program has ended). It’s largely unused thus far so far but was added for future-proofness.

    // Optionally make changes on Tessel after starting the program
    exportables.postRun = function(tessel, options) {
      // Should return a Promise which eventually gets resolved
      return Promise.resolve();
    };
    

    Conclusion

    That’s just about all there is to adding language support to the CLI. There are several more steps that need to be taken to build full hardware support for a language on Tessel. You can follow Rust’s progress in that process here.

    If you have questions about the unit tests or the implementation of new languages in general, feel free to reach out in the #engineering channel of Tessel Slack. Let us know if there are any extra plugin functions you think the CLI needs to accomodate or if you need help building a plugin for the language you’d like to build hardware with.

    • Jon

    #tessel rust programming language plugins deployment compilation

February 2017

November 2016

October 2016

September 2016

August 2016

July 2016

June 2016

April 2016

March 2016

February 2016

November 2015

September 2015

August 2015

July 2015

June 2015

May 2015

March 2015

February 2015

January 2015

December 2014

November 2014

October 2014

September 2014

August 2014

July 2014

June 2014

May 2014

April 2014

March 2014

February 2014

January 2014

December 2013

November 2013

October 2013

September 2013

August 2013

July 2013