Rust on Chrome OS

This provides information on creating Rust projects for installation within Chrome OS and Chrome OS SDK. All commands and paths given are from within the SDK's chroot.

Usage

cros-rust.eclass is an eclass that supports first-party and third-party crate dependencies. Rust project's ebuild should inherit the eclass to support Rust building in Chrome OS build system.

WARNING: Some legacy projects are still using cargo.eclass. These two eclasses can't be used together in a single project. Rust projects with dependencies should all use cros-rust.eclass.

cros-rust.eclass

All the Rust projects using cros-rust.eclass will get dependent crates from /build/${BOARD}/usr/lib/cros_rust_registry instead of crates.io. You'll learn how to publish first-party and third-party crates to cros_rust_registry in the following sections.

Third-party ebuild crates

To import a third-party crate, we need to create an ebuild for it in ~/chromiumos/src/third_party/chromiumos-overlay/dev-rust/<crate_name>/<crate_name>-<crate_version>.ebuild.

There is a script to automate this process that can be run inside the chroot:

`~/chromiumos/src/platform/dev/contrib/cargo2ebuild.py`

The recommended way to use this script is to:

  1. Have the crate(s) you want to import defined in a Cargo.toml file.
  2. If the Cargo.toml file is for a placeholder crate, exclude it using -k <placeholder crate name>
  3. Do a dry run first and resolve any issues -d
  4. When the set of crates looks good, run it again without the dry run flag.
  5. Test the generated ebuilds locally.
  6. Upload the generated ebuilds as a CL.

This process can be done without the script. For an example crate with the following SemVer dependencies:

[dependencies]
libc = "0.2"
getopts = "1.2"
rayon = "1.4.1"
http = "^0.1.8"
indexmap = "^1.0"
string = "~1.2"
bit-set = "~2.7.1"
walkdir = "~2"

We would create the following ebuild:

# Copyright <copyright_year> The Chromium OS Authors. All rights reserved.
# Distributed under the terms of the GNU General Public License v2

EAPI="7"

CROS_RUST_REMOVE_DEV_DEPS=1

inherit cros-rust

DESCRIPTION="An example library"
HOMEPAGE="url/to/the/homepage"
SRC_URI="https://crates.io/api/v1/crates/${PN}/${PV}/download -> ${P}.crate"

LICENSE="|| ( <fill in license> )"
SLOT="${PV}/${PR}"
KEYWORDS="*"

DEPEND="
	=dev-rust/libc-0.2*:=
	=dev-rust/getopts-1.2*:=
	>=dev-rust/rayon-1.4.1 <dev-rust/rayon-2.0:=
	>=dev-rust/http-0.1.8 <dev-rust/http-0.2:=
	=dev-rust/indexmap-1*:=
	=dev-rust/string-1.2*:=
	>=dev-rust/bit-set-2.7.1 <dev-rust/bit-set-2.8:=
	=dev-rust/walkdir-2*:=
"
# (crbug.com/1182669): build-time only deps need to be in RDEPEND so they are pulled in when
# installing binpkgs since the full source tree is required to use the crate.
RDEPEND="${DEPEND}"
  • DESCRIPTION, HOMEPAGE, SRC_URI and LICENSE should be found from Cargo.toml or crates.io.
  • All the dependencies should have a cros-rust ebuild and be listed in DEPEND section.
  • Dependencies in Cargo.toml which do not have any operator specified in their version number (for example, '^', '<', '~') are handled the same as crates with '^' specified.
  • Download the crate from crates.io and upload it to localmirror. (Check details in "Depending on Crates" section).
  • Run command ebuild example.ebuild digest to generate Manifest.

After creating the ebuild file, running emerge-${BOARD} <crate_name> will install the crate's package to cros-rust-registry.

Many third party crates have dev dependencies that are not actually needed for Chrome OS. To keep dev dependencies from being enforced cros-rust.eclass provides, CROS_RUST_REMOVE_DEV_DEPS which can be set to remove the dev dependencies during src_prepare. This is especially useful when there would otherwise be circular dependencies.

Empty crates

If some third-party crates are dependent on some unused crates (e.g., dependencies uses by unused features or they're in dev-dependencies) and we want to mock those unused crates out, we could create empty-crate ebuilds for them. Here is an empty ebuild for example crate:

# Copyright <copyright_year> The Chromium OS Authors. All rights reserved.
# Distributed under the terms of the GNU General Public License v2

EAPI="7"

CROS_RUST_EMPTY_CRATE=1

inherit cros-rust

DESCRIPTION="Empty example crate"
HOMEPAGE=""

LICENSE="BSD-Google"
SLOT="${PV}/${PR}"
KEYWORDS="*"

First-party crates

You can create your Rust project in anywhere in the Chrome OS system and it's ebuild in a suitable place in chromiumos-overlay with name <category>/<crate_name>-9999.ebuild. Here is an ebuild for an example first-party crate:

# Copyright <copyright_year> The Chromium OS Authors. All rights reserved.
# Distributed under the terms of the GNU General Public License v2

EAPI="7"

CROS_WORKON_INCREMENTAL_BUILD=1
CROS_WORKON_LOCALNAME="example"
CROS_WORKON_PROJECT="path/to/project/repository"
# We don't use CROS_WORKON_OUTOFTREE_BUILD here since project's Cargo.toml is
# using "provided by ebuild" macro which supported by cros-rust.
CROS_WORKON_SUBTREE="path/to/project/subtree"

inherit cros-workon cros-rust

DESCRIPTION="An example first party project"
HOMEPAGE="home_page_url"

LICENSE="BSD-Google"
SLOT="0/${PVR}"
KEYWORDS="~*"
IUSE="test"

DEPEND="
	dev-rust/third_party_crate:=
	example2/first_party_crate:=
"
# (crbug.com/1182669): build-time only deps need to be in RDEPEND so they are pulled in when
# installing binpkgs since the full source tree is required to use the crate.
RDEPEND="${DEPEND}"

# Only include this if you need to install binaries or multiple crates.
src_install() {

	# 1. Publish this library for other first-party crates.
	#    This is needed if other crates depend on this crate.
	cros-rust_src_install
	# 2. Install the binary to image. This isn't needed for libraries.
	dobin "$(cros-rust_get_build_dir)/example_bin"
}

cros-rust.eclass publishes this crate by using the cros-rust_publish command to allow it to be used by other first-party crates.

WARNING: Please make sure your project could be built by both steps for engineering productivity:

  1. From chroot with emerge-${BOARD} CRATE-EBUILD-NAME

    Tips: You can set USE=-lto to speed up build times when using emerge. This turns off link time optimization, which is useful for release builds but significantly increases build times and isn't really needed during development.

  2. From project root directory with cargo build

We add two macros to resolve conflicts between these two build system. Check details from the following section.

Ebuild versioning

Ebuild versions should match the version in Cargo.toml. To keep the versions in sync, add a files/chromeos-version.sh script like this:

#!/bin/sh
#
# Copyright <copyright year> The Chromium OS Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.

# Assumes the first 'version =' line in the Cargo.toml is the version for the
# crate.
awk '/^version = / { print $3; exit }' "$1/<path/to/Cargo.toml>" | tr -d '"'

Replace <path/to/Cargo.toml> with the path of Cargo.toml relative to the root of the repository it's in.

Cargo.toml macros

The cros-rust eclass supports building crates in the Chrome OS build system, but it breaks cargo build in some situations. We add two macros which are recognized by the eclass to keep both build systems working. The macros take the form of special comments in Cargo.toml.

  • # provided by ebuild

    This macro introduces a replacement of:

    [dependencies]
    data_model = { path = "../data_model" }  # provided by ebuild
    

    with:

    [dependencies]
    data_model = { version = "*" }
    

    Example usage: Add dependency to first-party crate

    1. Add the dependent crate to DEPEND section in ebuild.
    2. Use relative path for imported crates in Cargo.toml but with # provided by ebuild macro:
    [dependencies]
    data_model = { path = "../data_model" }  # provided by ebuild
    
  • # ignored by ebuild

    We will use this to discard parts of [patch.crates-io] which should be applied to local developer builds but not to ebuilds.

    This macro introduces a replacement of:

    audio_streams = { path = "../../third_party/adhd/audio_streams" } # ignored by ebuild
    

    with empty line while building with emerge.

    Example usage: Add dependency to first-party crate for sub-crates from root crate.

    1. Add the dependent crate to DEPEND section in root crate's ebuild.
    2. Use relative path for imported crates in [patch.crates-io] section in root crate's Cargo.toml but with # ignored by ebuild macro:
    [patch.crates-io]
    audio_streams = { path = "../../third_party/adhd/audio_streams" } # ignored by ebuild
    

Depending on Crates

Because the sources for all ebuilds in Chrome OS must be available at localmirror, you will have to upload all third-party crate dependencies for the project to localmirror. This is taken care of automatically if you use the cargo2ebuild.py script.

The following will download a crate, upload it to localmirror, and make it accessible for download:

WARNING: localmirror is shared by all Chrome OS developers. If you break it, everybody will have a bad day.

export CRATE_NAME="<crate_name>"
export CRATE_VERSION="<crate_version>"
mkdir -p /tmp/crates
curl -L "https://crates.io/api/v1/crates/${CRATE_NAME}/${CRATE_VERSION}/download" >"/tmp/crates/${CRATE_NAME}-${CRATE_VERSION}.crate"
gsutil cp -a public-read "/tmp/crates/${CRATE_NAME}-${CRATE_VERSION}.crate" gs://chromeos-localmirror/distfiles/

Workflows

There are a few different ways for building and testing first party Rust code.

cros_workon_make

Using cros_workon_make is the same for Rust as with typical platform2 packages on Chrome OS. The downsides for using it with Rust on CrOS are it modifies the Cargo.toml file and can be slower than running cargo directly. You will want to locally commit any Cargo.toml changes before running cros_workon_make. If you try to run cargo directly with the cros_workon_make-modified Cargo.toml, things will likely be broken, so be sure to checkout the unmodified copy before calling cargo.

cargo

The primary downside of using cargo directly is it will fetch dependencies from crates.io though the internet. These often do not match with the versions used by Chrome OS leading to undesired consequences. One workaround is to have an up-to-date Cargo.lock file checked in for your project. Another is to use the same cargo config as cros_workon_make. There is a helper script that set this up for you:

BOARD=<board> ~/chromiumos/src/platform/dev/contrib/setup_cros_cargo_home

This can be applied outside the Chrome OS chroot by copying the ~/.cargo/config from the chroot outside and updating the paths to be correct for outside the chroot.

***note Note the resulting ~/.cargo/config depends on the cros_rust_registry for the specified ${BOARD}. You may need to run ./build_packages for the board or emerge-${BOARD} dev-rust/<dependency> to install or update dependencies.


Cross-compiling

This is taken care of in the ~/.cargo/config if you preform the setup above.

The toolchain that is installed by default is targetable to the following triples:

Target TripleDescription
x86_64-pc-linux-gnu(default) Used exclusively for packages installed in the chroot
armv7a-cros-linux-gnueabihfUsed by 32-bit usermode ARM devices
aarch64-cros-linux-gnuUsed by 64-bit usermode ARM devices (none of these exist as of November 30th, 2018)
x86_64-cros-linux-gnuUsed by x86_64 devices

When building Rust projects for development, a non-default target can be selected as follows:

cargo build --target=<target_triple>

If a specific board is being targeted, that board's sysroot can be used for compiling and linking purposes by setting the SYSROOT environment variable as follows:

export SYSROOT="/build/<board>"

If C files are getting compiled with a build script that uses the cc or gcc crates, you may also need to set the TARGET_CC environment variable to point at the appropriate C compiler.

export TARGET_CC="<target_triple>-clang"

If a C/C++ package is being pulled in via pkg-config, the PKG_CONFIG_ALLOW_CROSS environment variable should be exposed. Without this, you might see CrossCompilation as part of an error message during build script execution.

export PKG_CONFIG_ALLOW_CROSS=1