Cross-compilation adalah proses membangun executable untuk arsitektur target yang berbeda dari arsitektur host tempat compiler berjalan. Skenario umum: kita menulis kode di laptop x86-64 Linux, tetapi binary yang dihasilkan akan berjalan di Raspberry Pi (ARM), router OpenWRT (MIPS), atau perangkat Android (ARM64). Cross-compilation memungkinkan kita membangun binary untuk perangkat target tanpa perlu men-compile di perangkat target itu sendiri (yang seringkali lambat atau memiliki keterbatasan memori). Pada chapter ini kita akan memahami konsep target triple, struktur cross-toolchain, dan dua mekanisme utama: Autoconf ./configure --host= dan CMake toolchain file.

Konsep Target Triple #

Setiap toolchain GCC/Clang diidentifikasi oleh target triple — string berformat <arch>-<vendor>-<os>-<abi>. Contoh:

  • x86_64-linux-gnu: arsitektur x86-64, OS Linux, ABI GNU glibc (toolchain native di Debian/Ubuntu 64-bit).
  • aarch64-linux-gnu: arsitektur ARM64 (AArch64), OS Linux, ABI GNU (toolchain cross untuk Raspberry Pi 4 64-bit, atau server ARM).
  • arm-linux-gnueabihf: arsitektur ARM 32-bit, OS Linux, ABI GNU hard-float (toolchain cross untuk Raspberry Pi 2/3 32-bit).
  • arm-none-eabi: arsitektur ARM bare-metal (tanpa OS), ABI EABI (toolchain untuk microcontroller ARM Cortex-M).
  • x86_64-w64-mingw32: arsitektur x86-64, OS Windows, ABI mingw (toolchain cross dari Linux ke Windows).

Perintah gcc -dumpmachine menampilkan target triple toolchain yang sedang aktif:

bash
gcc -dumpmachine

Output yang Dihasilkan:

text
x86_64-linux-gnu

Output ini menandakan GCC yang terinstal adalah toolchain native x86-64 untuk Linux glibc. Bila kita memasang cross-toolchain (misalnya apt install gcc-arm-linux-gnueabihf), perintah arm-linux-gnueabihf-gcc -dumpmachine akan menghasilkan arm-linux-gnueabihf.

Struktur Cross-Toolchain #

Cross-toolchain biasanya terdiri dari sekumpulan tool yang diberi prefix target triple. Misalnya untuk target arm-linux-gnueabihf, tool yang terinstal adalah:

text
arm-linux-gnueabihf-gcc      # compiler driver
arm-linux-gnueabihf-g++      # C++ compiler driver
arm-linux-gnueabihf-ar       # archiver
arm-linux-gnueabihf-as       # assembler
arm-linux-gnueabihf-ld       # linker
arm-linux-gnueabihf-nm       # symbol table reader
arm-linux-gnueabihf-objcopy  # binary utility
arm-linux-gnueabihf-objdump  # disassembler
arm-linux-gnueabihf-ranlib   # archive index generator
arm-linux-gnueabihf-strip    # symbol stripper

Selain tool, cross-toolchain juga menyertakan sysroot — direktori berisi library dan header target. Sysroot biasanya berada di /usr/<triple>/ (misal /usr/arm-linux-gnueabihf/) dan berisi struktur seperti filesystem target (lib/, usr/lib/, usr/include/). Compiler mencari header dan library di sysroot ini, bukan di /usr/include dan /usr/lib host.

Cross-Compilation dengan Autoconf #

Proyek yang memakai GNU Autotools (Autoconf/Automake) menerima flag --host= untuk cross-compilation. Saat flag ini diberikan, ./configure mengganti variabel CC, AR, RANLIB dengan versi yang diawali target triple:

bash
./configure --host=arm-linux-gnueabihf
make

Berikut simulasi output ./configure (skrip demo yang menunjukkan pola ini):

bash
./configure-demo.sh
./configure-demo.sh arm-linux-gnueabihf

Output yang Dihasilkan:

text
# Native:
Configuring for host: native
CC = gcc
CFLAGS = -O2 -g

# Cross:
Configuring for host: arm-linux-gnueabihf
CC = arm-linux-gnueabihf-gcc
CFLAGS = -O2 -g
AR = arm-linux-gnueabihf-ar
RANLIB = arm-linux-gnueabihf-ranlib

Mari kita telaah output di atas:

  1. Saat native: CC = gcc — compiler host dipakai langsung. Variabel AR dan RANLIB tidak disebut karena default (ar, ranlib) sudah benar.
  2. Saat cross: CC = arm-linux-gnueabihf-gcc — ./configure menerapkan prefix arm-linux-gnueabihf- ke nama tool bawaan. Variabel AR dan RANLIB juga di-prefix karena kita perlu archive tool target, bukan host (format archive sebenarnya sama, tetapi ar dari cross-toolchain tahu di mana mencari library).
  3. CFLAGS tetap sama — flag compiler seperti -O2 -g tidak bergantung target. Bila target membutuhkan flag khusus (misalnya -mcpu=cortex-a53), kita tambahkan ke CFLAGS saat konfigurasi.

Cross-Compilation dengan CMake #

CMake memakai mekanisme toolchain file — file .cmake berisi definisi variabel yang menentukan compiler, target, dan sysroot. File ini diberikan saat konfigurasi lewat -DCMAKE_TOOLCHAIN_FILE=.

Berikut toolchain-arm-linux-gnueabihf.cmake (template konseptual untuk target ARM hard-float):

cmake
# Toolchain file untuk target arm-linux-gnueabihf
set(CMAKE_SYSTEM_NAME      Linux)
set(CMAKE_SYSTEM_PROCESSOR arm)

set(CMAKE_C_COMPILER   arm-linux-gnueabihf-gcc)
set(CMAKE_CXX_COMPILER arm-linux-gnueabihf-g++)

# Sysroot tempat library dan header target berada
set(CMAKE_SYSROOT /usr/arm-linux-gnueabihf)

# Aturan pencarian find_package, find_library, find_path
set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY)

Mari kita telaah baris-baris toolchain file di atas:

  1. CMAKE_SYSTEM_NAME Linux: menandakan target adalah sistem Linux. CMake memakai nilai ini untuk memilih platform module yang benar (mis. Linux.cmake, Windows.cmake, Darwin.cmake). Tanpa variabel ini, CMake menganggap target sama dengan host.
  2. CMAKE_SYSTEM_PROCESSOR arm: menandakan arsitektur target. Berguna untuk logika kondisional di CMakeLists.txt (mis. if(CMAKE_SYSTEM_PROCESSOR STREQUAL "arm")).
  3. CMAKE_C_COMPILER arm-linux-gnueabihf-gcc: nama compiler target. CMake mencari binary ini di PATH. Bila tidak ada, konfigurasi gagal dengan error cannot find compiler.
  4. CMAKE_CXX_COMPILER arm-linux-gnueabihf-g++: nama compiler C++ target (bila kita membangun kode C++).
  5. CMAKE_SYSROOT /usr/arm-linux-gnueabihf: path absolut ke sysroot target. Compiler akan mencari header di <sysroot>/usr/include dan library di <sysroot>/usr/lib.
  6. CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER: saat find_program mencari executable (mis. find_program(PYTHON python3)), CMake hanya mencari di path host — bukan sysroot target. Logikanya: program seperti python3 dijalankan saat build (mis. untuk code generation), jadi harus bisa dieksekusi di host.
  7. CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY: saat find_library mencari library target, CMake hanya mencari di sysroot target — bukan di host. Mencari library host akan menghasilkan binary yang salah (mis. libfoo.so versi x86-64 alih-alih ARM).
  8. CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY: saat find_path mencari header, hanya di sysroot target. Header host mungkin ada, tetapi tidak kompatibel ABI-nya.
  9. CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY: saat find_package mencari CMake config file target, hanya di sysroot target.

Demo dengan Toolchain Same-Arch #

Pada sandbox tutorial ini hanya toolchain native x86-64 yang terinstal, tetapi binary x86_64-linux-gnu-gcc (dengan prefix) tersedia sebagai alias ke gcc native. Kita bisa memverifikasi pola toolchain file dengan toolchain same-arch:

cmake
# toolchain-x86_64.cmake
set(CMAKE_SYSTEM_NAME      Linux)
set(CMAKE_SYSTEM_PROCESSOR x86_64)

set(CMAKE_C_COMPILER   x86_64-linux-gnu-gcc)
set(CMAKE_CXX_COMPILER x86_64-linux-gnu-g++)

set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)
set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)
set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)

CMakeLists.txt yang menampilkan variabel target:

cmake
cmake_minimum_required(VERSION 3.10)
project(cross-demo C)

add_executable(app main.c)

message(STATUS "CMAKE_SYSTEM_NAME = ${CMAKE_SYSTEM_NAME}")
message(STATUS "CMAKE_SYSTEM_PROCESSOR = ${CMAKE_SYSTEM_PROCESSOR}")
message(STATUS "CMAKE_C_COMPILER = ${CMAKE_C_COMPILER}")
bash
cmake -B build_cross -DCMAKE_TOOLCHAIN_FILE=toolchain-x86_64.cmake
cmake --build build_cross
./build_cross/app

Output yang Dihasilkan:

text
-- Check for working C compiler: /usr/bin/x86_64-linux-gnu-gcc - skipped
-- Detecting C compile features
-- Detecting C compile features - done
-- CMAKE_SYSTEM_NAME = Linux
-- CMAKE_SYSTEM_PROCESSOR = x86_64
-- CMAKE_C_COMPILER = /usr/bin/x86_64-linux-gnu-gcc
-- Configuring done (0.1s)
-- Generating done (0.0s)
-- Build files have been written to: /tmp/test_cross/build_cross
[ 50%] Building C object CMakeFiles/app.dir/main.c.o
[100%] Linking C executable app
[100%] Built target app
size of pointer = 8 bytes
size of int     = 4 bytes
size of long    = 8 bytes

Perhatikan baris CMAKE_C_COMPILER = /usr/bin/x86_64-linux-gnu-gcc — CMake memakai compiler yang kita sebut di toolchain file, bukan /usr/bin/cc default. Program menghasilkan size of pointer = 8 karena target sama dengan host (x86-64), tetapi bila kita pakai toolchain ARM yang sebenarnya, output akan menampilkan size of pointer = 4 (ARM 32-bit) atau size of pointer = 8 (AArch64).

Code yang Membedakan Target #

Saat menulis kode yang akan di-cross-compile, kita sering perlu membedakan arsitektur target saat preprocessing. Standar C mendefinisikan macro standar (__SIZEOF_POINTER__), sementara compiler mendefinisikan macro spesifik arsitektur (__x86_64__, __arm__, __aarch64__):

c
#include <stdio.h>

int main(void) {
    printf("size of pointer = %zu bytes\n", sizeof(void *));
    printf("size of int     = %zu bytes\n", sizeof(int));
    printf("size of long    = %zu bytes\n", sizeof(long));
    return 0;
}

Karena sizeof(void *) dievaluasi saat kompilasi, output mencerminkan arsitektur target. Saat di-cross-compile ke ARM 32-bit, output menjadi size of pointer = 4 bytes. Saat ke AArch64, tetap 8 bytes seperti x86-64.