Introduction

This collection of plugins allows developers to work with Rust in a much more gradlesque way and also easily integrate Rust into a polyglot way of working. Under the hood it utilises Cargo to do the heavy lifting. It does not require Rust or Cargo to be installed as it follows the norm of many new Gradle plugins to bootstrap the installation as part of the build.

The following features are supported:

  • Compiling Rust executable and libraries.

  • Running Rust tests.

  • Defining Cargo dependencies in Gradle’s dependencies block.

This is an incubating project. Until is 1.0 released one day, interfaces and DSL may change between 0.x releases. Please see the [limitations] of the plugin during early development.

For Rust people new to Gradle

This collection of plugins introduces a couple of conventions which might feel strange to people used to developing with Rust. The source layout follows a more Gradle/Maven convention as follows:

Rust source & build layout
.
├── build.gradle
├── settings.gradle
├── src
│   ├── main
│   │   └── rust  (1)
│   ├── test
│   │   └── rust  (2)
│   └── bench
│       └── rust  (3)
└── build
    └── rust-project (4)
1 Application & library code goes in src/main/rust.
2 Test code goes in src/test/rust
3 Benchmark code goes in src/bench/rust
4 Work directory for Rust build.

During the build process Gradle will copy the souce to build/rust-project folder using the processRustSource task. This also allow for additional processing on the Rust source before compilation.

Output from Cargo is not displayed during the build process, but will be captured for later analysis in build/tmp/cargo/${taskName} folders.

The Cargo.toml file is auto-generated during the build process from information which is specified in the compileExeRust & CompileLibRust` tasks as well as defined in the dependencies.rust block.

If you are not yet ready to adopt the above conventions, you can still use Gradle with your existing Rust project, provided it has a standard Cargo directory layout. Read the section on wrapping an existign Rust project for more details.

Limitations

Publishing

Compilation and test are supported, but publishing to Cargo is not yet supported. See #3.

Benchmarks

Compilation and execution of beanchmark code is not supported. See #1.

Cargo

Arbitrary cargo commands cannot be executed. See #2.

Cargo.lock

How Cargo.lock files are interpreted are yet to be decided. At this current the the lock file will be generated in the build/rust-project directory, which is not in a location which can be committed to source control.

Dependencies

Target-specific dependencies cannot be specified in os-arch-compiler triples. See #4.

Bootstrapping

These plugins are available from the plugin portal. Add the appropriate plugin identifiers to your build.gradle file depending on the type of functionality you require.

build.gradle
plugins {
  id 'org.ysb33r.rust.base'  version '0.2'  (1)
  id 'org.ysb33r.rust.exe'  version '0.2'  (2)
  id 'org.ysb33r.rust.lib'  version '0.2'  (3)
}
1 Apply the base plugin.
2 Apply the application development plugin.
3 Apply the library development plugin.
You need at least JDK8 & Gradle 3.3 to use this plugin.

The base plugin provides:

  • A project extension called rust for configuring the Rust version.

  • A handler extension to the dependencies block for specifying

  • A processRustSource task to process Rust source code before building. It is a Copy task and can be enhanced with any additional filtering and source locations.

The library development plugin provides:

  • A configuration for processRustSource to populate build/rust-project/(src|tests|benches) folders from src/(main|test|benches)/rust folders.

  • A compileLibRust task to compile Rust source.

  • A compileTestRust task to compile Rist test source code.

  • A cargoManifest task to generate a Cargo.toml file.

  • A testRust task to execute Rust tests.

  • Compilation tasks are part of the assemble lifecycle.

  • Test tasks are part of the check lifecycle.

The application development plugin provides similar functionality to the application development plugin except that it creates a compileExeRust instead of compileLibRust task. In addition, the application plugin also creates a runApp task to execute the executable created by the project.

Configuring and Accessing Tools

All configuration and bootstrapping happens within the rust extension block.

build.gradle
rust {
  executable version : '1.2.3' (1)
  executable searchPath() (2)

  abiToolsSearchPath = [ '/bin', '/usr/bin' ] (3)
  abiToolsSearchPath 'bin', '/usr/bin' (4)
}
1 Use a specified version of Rust.
2 Instead of bootstrapping Rust, discover the binaries in the system search path.
3 Set the path to search for system linker etc.
4 Add additional path to search
If the abiToolsSearchPath is not specified, Gradle will default to using the system search path.

The extension also offers two methods for build script authors to get access to the rustc and cargo executables.

build.gradle
task displayLocations {
  doLast {
    println "cargo: ${rust.resolveCargoPath()}"
    println "rustc: ${rust.resolveRustcPath()}"
  }
}

Invoking any of the two methods will cause Rust to be bootstrapped.

The Rust Dependency Handler

Instead of using Cargo.toml files, the Gradle dependencies.rust block is used to define all required upstream dependencies. Doing it this way, will allow future releases of the plugin to use Gradle’s excellent artifact resolving capabilities to enhance that of Cargo. It is expected that dependencies.rust supports all the methods for defining Carg-style dependencies.

Dependencies are defined sub-blocks (or configurations) called compile, test and build which in turn, corresponds to Cargo’s dependencies, dev-dependencies and build-dependencies.

Defining dependencies

build.gradle
dependencies {
  rust {  (1)
    compile 'winhttp:0.4.0' (2)

    compile { (3)
    }

    test 'winhttp:0.4.0' (4)

    test { (5)
    }

    build 'winhttp:0.4.0' (6)

    build { (7)
    }
  }
}
1 All Rust dependencies are declared inside the rust block.
2 Shortcut for adding a dependency by name and version to the Cargo dependencies group.
3 Add a dependency using a fully configurable closure.
4 Shortcut for adding a dependency by name and version to the Cargo dev-dependencies group.
5 Add a test dependency using a fully configurable closure.
6 Shortcut for adding a dependency by name and version to the Cargo build-dependencies group.
7 Add a build dependency using fully configurable closure.
Instead of a closure, Kotlin DSL users can use a Gradle Action to configure dependencies.

It is also possible to define more than one dependency at a time when using the short form.

dependencies {
  rust {
    compile 'winhttp:0.4.0', 'uuid:1.2.3'
  }
}

A number of options are available for configuring a dependency at creation time.

build.gradle
dependencies {
  rust {
    compile { (1)
      name 'winhttp'
      version '0.4.0'
    }
    compile { (2)
        name 'uuid_1'
        git 'https://github.com/foo/uuid_1'
    }
    compile { (3)
        name 'uuid_2'
        git 'https://github.com/foo/uuid_2', 'my-branch'
    }
    compile { (4)
        name 'uuid_3'
        path 'path/to/uuid_3'
    }
    compile { (5)
        name 'uuid_4'
        path 'path/to/uuid_5'
        version '0.2'
    }
    compile { (6)
        target 'cfg(windows)'
        name 'winhttp'
        version '1.2.3'
    }
    compile { (7)
        name 'uuid_5'
        version '1.2.3'
        withDefaultFeatures false
        features 'time', 'urandom'
    }
}
1 Add a dependency by name and version.
2 Add a dependency by name and Git repository
3 Add a dependency by name, Git repository and Git branch
4 Add a dependency by name and path
5 Add a dependency by name, path and version
6 Add a target-specific dependency. The string passed to target should correspond to something that Cargo understands.
7 Include / exclude optional feastures for a dependency.
git and path cannot be specified in the same dependency.

Creating patches

Patches can also be specified via dependencies.rust. Use the patch keyword instead of specifying a configuration name such as compile.

build.gradle
dependencies {
  rust {
    patch 'foo', '../path/to/foo' (1)
    patch 'http://foo/bar'.toURI(), 'bar', '../path/to/bar' (2)
    patch { (3)
        name 'foo2'
        path '../path/to/foo2'
    }
    patch ('http://foo/bar'.toURI()) { (4)
        name 'foo3'
        path '../path/to/foo3'
    }
    patch { (5)
        name 'foo4'
        git 'https://gitlab.com/foo/bar'
    }
  }
}
1 Specify a cargo.io patch by name and local path.
2 Specify a non-cargo patch by URI, name and local path. The first parameter has to be a URI.
3 Create and configure a cargo.io patch by name and local path
4 Create and configure a non-cargo patch by URI, name and local path. The first parameter has to be a URI.
5 Create and configure a cargo.io patch by name and Git repository.

Cargo Manifest

The Cargo.toml is created by the` cargoManifest` task. It is possible to configure a number of basic values for the manifest:

build.gradle
cargoManifest {
  manifest.packageInf {
    name 'foo' (1)
    version '1.2.3 (2)
    authors = [ 'Schalk' ] (3)
    authors 'Russel' (4)
  }
}
1 Name of the Rust package. Defaults to project.name.
2 Version of Rust package. Defaults to project.version.
3 Set the authors for the package.
4 Adds an author.
If no authors were specified, Gradle will use name of user currently running the build.

Existing Rust Project

Using an existing Cargo.toml

It is possible to combine an existing Cargo.toml file with a Gradle project. Simply set the location of the file in the rust extension.

rust {
  cargoToml './Cargo.toml'
}

Once this is configured, Gradle will enhance the dependencies.rust block with information supplied in the Cargo.toml file. It will also update the cargoManifest task accordingly. Anything set in this file will override the manifest oackage settings specified in cargoManifest.

Wrapping an existing project

If you want to use your existing Rust project with Cargo directory conventions, fear not. It is possible to wrap you existing project. Simply create a build.gradle file in the existing project’s directory.

plugins {
  id 'org.ysb33r.rust.exe'  version '0.2'  (1)
}

rust {
  cargoToml './Cargo.toml' (2)
  useCargoSourceLayout() (3)
}
1 Or use org.ysb33r.rust.lib depending whether you are an application or a library.
2 Remember to tell it about the location of the Cargo.toml file otherwise it will not load it.
3 Apply the Cargo source layout convention.

When building it will still place the output under build/rust-project, but you’ll have miniomal requirements to change source locations.

If you would rather have the whole Rust project as a subdirectory of the Gradle project, then pass the subdirectory to the rust extension:

rust {
  useCargoSourceLayout('my-rust-project') (1)
  cargoToml 'my-rust-project/Cargo.toml' (2)
}
1 This will look for your project in a subfolder called my-rust-project.
2 Remember to update the location of Cargo.toml as well.
Remember to exclude .gradle and build/ from source repository management.

Alternative solutions

This is not the only solution. You might also want to look at