Skip to content

Contributing

Emil Gardström edited this page Sep 6, 2023 · 3 revisions

Thank you for looking to contribute to cross. Have a new feature you'd like to add? Know how to fix an open bug? Want to add an image for a new target? Here's a guide for how to get up-to-speed with how cross's internal work and how to contribute new features, bug fixes, and additional targets for cross.

Please read our code of conduct so our community stays positive and welcoming. If you have any additional questions, please feel free to ask in either our discussions or our Matrix room.

Table of Contents

Getting Started

First, clone the repository and build it:

$ git clone https://github.com/cross-rs/cross
$ cd cross
$ cargo build

Next, install our Git hooks: this ensures automated checks are run whenever you commit or push code, so you can detect bugs prior to submitting a pull request:

cargo xtask install-git-hooks

Now, checkout your own branch, make your changes, and get hacking. Remember that if you want to implement a new feature, we recommend first opening a feature request and offering to work on it yourself so we can determine if we want to include it in cross prior to you putting in the implementation work.

Made a substantial change? Add a changelog entry. We use JSON files documenting the changes to auto-generate our changelog, which is documented here.

Still unsure of how to get started after reading this? Please ask us on our discussions or in our Matrix room.

Cheatsheet

  • src: where the code for the cross CLI command is located. this is packaged as a library, with the binaries for cross and cross-util provided in the src/bin directory.
  • src/docker: code specific to interfacing with the container engine is located. this includes starting the container, copying data to and from data volumes with remote cross, and building custom images.
  • xtask: extra tasks useful for maintaining cross, including:
    • Extract and print info for targets
    • Build docker images
    • Lint and check our code
    • Run our unittests
    • Configure our crosstool-based images
  • docker: Dockerfiles and scripts to build images containing the cross-compiler toolchains

How Cross Works

Cross effectively has two layers:

  1. The CLI utility which processes configuration settings, command-line arguments, and data to pass to forward to a cross-compiler toolchain.
  2. Pre-built container images with the necessary toolchain and runners required to cross-compile and run foreign binaries.

CLI Utility

To do this, cross parses the command-line arguments and configuration settings in order to mount the the host Rust toolchain, the package data, and any additional configuration options to the container. This includes:

  • Determining the container image to use
  • Detecting the correct Rust toolchain to use
  • Find the Rust manifest, target, target directory, and additional flags
  • Forwarding cargo-specific environment variables to the container

Advanced use also encapsulates:

  • Efficiently copying data to and from data-volumes to cross-compile on a remote machine
  • Building custom images
  • Detecting container engine features and supporting cross-platform image builds.

Images

Meanwhile, the container images require:

  • Linker for the target
  • C and (optionally) C++ standard library for the target
  • C and (optionally) C++ cross-compiler
  • Binary utilities (binutils) for the target

The correct environment variables must also be provided so cargo, as well as other crates like cc and bindgen detect the correct toolchai (especially, linker, binutils, and cross-compilers). For example, for aarch64-linux-android, we can do this by specifying the following environment variables:

# the linker, required for pure-rust packages
export CARGO_TARGET_AARCH64_LINUX_ANDROID_LINKER=aarch64-linux-android-gcc
# C and C++ compilers. required for packages with external dependencies.
export CC_aarch64_linux_android=aarch64-linux-android-gcc
export CXX_aarch64_linux_android=aarch64-linux-android-g++
# the archive utility. required for cc-rs to build static libs.
export AR_aarch64_linux_android=aarch64-linux-android-ar
# additional arguments so bindgen can find the include headers
export BINDGEN_EXTRA_CLANG_ARGS_aarch64_linux_android="--sysroot=/android-ndk/sysroot"

The more information provided, the less setup a user will have to do when using cross. Additional, useful information includes PKG_CONFIG_PATH (where pkg-config can find installed libraries) and the QEMU_LD_PREFIX (where the Qemu runners can find system libraries for the cross-compiled targets).

Adding New Targets

New targets should ideally be officially supported by Rust itself. Tier 1 and tier 2 targets are fit for submission in cross itself, tier 3 targets should be submitted to cross-toolchains.

Building and Testing

We describe how to build your toolchain and create your dockerfile in Creating a Toolchain and Dockerfile. Once your Dockerfile is created, run the following to build and test your new target:

# this should be the name after `Dockerfile.`, which may not be the target name
# for example, for `cross-toolchains`, it's `$target-cross`.
cargo build-docker-image $target

# then, get our test script up. you may need to remove or add features 
# here. options are:
# - DYLIB: supports dynamic libraries
# - STD: target has a rust standard library
# - CPP: toolchain supports C++
# - RUN: target has a binary runner
# - BUILD_STD: target requires the rust standard library to be built
test() {
  declare CROSS_TARGET_$name_IMAGE="ghcr.io/cross-rs/$target:local"
  DYLIB=1 STD=1 RUN=1 TARGET=$target ci/test.sh
}

test

Creating a Toolchain and Dockerfile

Ideally, the new target should be based on a Ubuntu docker image, and use either official system packages, our musl, or crosstool-ng scripts to build and install the toolchain.

Official Repositories

For official repositories, this is typically as simple as:

RUN apt-get update && apt-get install --assume-yes --no-install-recommends \
    g++-aarch64-linux-gnu \
    libc6-dev-arm64-cross

Musl

When using our musl toolchains, you must provide the desired target as well as any additional config variables required to musl.sh, which builds the toolchain. Afterwards, use musl-symlink.sh to symlink the generated libraries so Rust and Qemu know how to interact with the GCC and musl toolchain. For example, with mips64-unknown-linux-muslabi64:

COPY musl.sh /
RUN /musl.sh \
    TARGET=mips64-linux-musl \
    "COMMON_CONFIG += -with-arch=mips64r2"

ENV CROSS_MUSL_SYSROOT=/usr/local/mips64-linux-musl
COPY musl-symlink.sh /
RUN /musl-symlink.sh $CROSS_MUSL_SYSROOT mips64

Crosstool

The last type are the crosstool-based images, which require a config file and a build script. We use template config files, which are then configured via cargo xtask configure-crosstool $target, so we can allow flexible GCC and libc versions. In order to get your first config file, check out ct-ng menuconfig or copy from one of our existing config files.

Once the config file has been generated, add the following to your Dockerfile:

ARG VERBOSE
COPY crosstool-ng.sh /
COPY crosstool-config/$target.config /
RUN /crosstool-ng.sh $target.config 5

Additional examples can be found in cross-toolchains.

Other Targets

If your target cannot be built using any of the above tools, some examples for how to get started include the macOS, MSVC, iOS, wasm, Dragonfly BSD, and FreeBSD images.

Adding to Cross Toolchains

When adding an image to cross-toolchains, you should initiate the repository with your own fork, rather than the default submodule:

# clone cross as usual
git clone https://github.com/cross-rs/cross
cd cross

# clone your own fork into the `cross-toolchains` directory, rather than the default
git clone https://github.com/cross-rs/cross-toolchains docker/cross-toolchains

Now, you can add Dockerfiles to docker/cross-toolchains/docker and build docker images as described above.