I hope this may be helpful to others. Please accept my apologies its lengthiness.
This post is for anyone interested in building an up-to-date gcc cross-compiler for aarch64 without having to wait for their linux distro. It been verified to produce running executables in 4 languages on a Raspberry Pi running Linux.
Below are a collection of small bash scripts to be run sequentially.
These build a gcc cross-compiler for aarch64 for the languages (ada, algol68, fortran, c, c++).
The approach aims for minimal dependencies, building everything from source. This also will compile glibc, and thus requires a couple of extra (but painless steps).
The first file is not a bash script. It needs to be “sourced” (since we need to propagate the environment variables we define herein. There are 3 env. variables you need to set to match your file placement desires: $PREFIX, $WORK, and $FETCHED_FILES_DIR. And three more to set to define versions to build (GCC, GlibC, and Binutils).
step_0_env_variables_and_install_directory
# This must be run using "source".
echo "step_0_env_variables_and_install_directory"
# Set PREFIX - the directory in which compiled software shall reside
export PREFIX="${HOME}/my_cross_compiler"
# WORK in a temporary location for the working files used during compilation
export WORK="${HOME}/Build_Tmp_Files/my_gcc_build"
# FETCHED_FILES_DIR is a place to store fetched files, can copy files from
# here to avoid multiple downloads
export FETCHED_FILES_DIR="${HOME}/My_Fetched_Files"
# Set versions of GCC, GLIBC, and Binutils desired
export GCC_VERSION_TO_BUILD="16.1.0"
export GLIBC_VERSION_TO_BUILD="2.43"
export BINUTILS_VERSION="2.46.1"
# Create installation directory for the compiled compiler,
if [ -d "$PREFIX" ]; then
echo "Installation directory: "${PREFIX}" already exists, terminating."
echo "Either use a different PREFIX, or remove this directory and try again."
return
fi
echo "Creating installation directory: "${PREFIX}" ..."
mkdir -p $PREFIX
export PATH=${PREFIX}"/bin:$PATH"
echo ${PREFIX}" has been added to PATH"
echo "done."
# Create clean working directory for use during compilation
if [ -d "$WORK" ]; then
echo "The temporary work directory: "${WORK}" already exists."
read -p "Shall this be nuked? (yes/no) ? " choice
case "$choice" in
yes ) echo "nuking ${WORK} in 3 seconds and then will be remade"; \
sleep 3; rm -Rf ${WORK} ;;
* ) echo "nothing deleted.";;
esac
fi
echo "Creating temporary work directory: "${WORK}" ..."
mkdir -p $WORK
echo "done."
echo "Creating fetched files directory: "${FETCHED_FILES_DIR}" ..."
mkdir -p $FETCHED_FILES_DIR
echo "done."
# Define temporary working directory for downloads and compilation.
export BINUTILS_FILE="binutils-"${BINUTILS_VERSION}".tar.bz2"
export GCC_FILE="gcc-"${GCC_VERSION_TO_BUILD}".tar.gz"
export GLIBC_FILE="glibc-"${GLIBC_VERSION_TO_BUILD}".tar.bz2"
echo "Defined version and filenames, created directories."
Next is to download and extract the source files needed
step_1a_download_and_extract_files.sh
#!/bin/bash
echo "step_1a_download_and_extract_files.sh"
# exit script on any error
set -e
if [ ! -d "$WORK" ]; then
echo "Work directory: "${WORK}" does not exist, exiting."
exit 1
fi
cd $WORK
if [ -f ${WORK}/${BINUTILS_FILE} ]; then
echo ${BINUTILS_FILE} " already exists, not fetching."
else
echo ". . . . . . . . . . . . . . . . . . . . . ."
echo " attempting fetch of: " ${BINUTILS_FILE}
wget https://ftpmirror.gnu.org/binutils/${BINUTILS_FILE}
echo " fetched."
tar xf ${BINUTILS_FILE}
echo " extracted."
mv $BINUTILS_FILE $FETCHED_FILES_DIR
echo " moved to: ${FETCHED_FILES_DIR}"
echo ". . . . . . . . . . . . . . . . . . . . . ."
echo
fi
if [ -f ${WORK}/${GCC_FILE} ]; then
echo ${GCC_FILE} " already exists, not fetching."
else
echo ". . . . . . . . . . . . . . . . . . . . . ."
echo " attempting fetch of: " ${GCC_FILE}
wget https://ftpmirror.gnu.org/gcc/gcc-${GCC_VERSION_TO_BUILD}/${GCC_FILE}
echo " fetched."
tar xf ${GCC_FILE}
echo " extracted."
mv $GCC_FILE $FETCHED_FILES_DIR
echo " moved to: ${FETCHED_FILES_DIR}"
echo ". . . . . . . . . . . . . . . . . . . . . ."
echo
fi
if [ -f ${WORK}/${GLIBC_FILE} ]; then
echo ${GLIBC_FILE} " already exists, not fetching."
else
echo ". . . . . . . . . . . . . . . . . . . . . ."
echo " attempting fetch of: " ${GLIBC_FILE}
wget https://ftpmirror.gnu.org/glibc/${GLIBC_FILE}
echo " fetched."
tar xf ${GLIBC_FILE}
echo " extracted."
mv $GLIBC_FILE $FETCHED_FILES_DIR
echo " moved to: ${FETCHED_FILES_DIR}"
echo ". . . . . . . . . . . . . . . . . . . . . ."
fi
git clone --depth=1 https://github.com/raspberrypi/linux
echo ". . . . . . . . . . . . . . . . . . . . . ."
echo " raspberry pi linux cloned"
cp -R linux $FETCHED_FILES_DIR
echo " copied linux to: ${FETCHED_FILES_DIR}"
echo ". . . . . . . . . . . . . . . . . . . . . ."
Once the files have been downloaded, you could use this next step as alternative to re-downloading if you ever need to re-run any build steps. (For example, if you decide to “start-over” from the beginning.)
step_1b_copy_local_files_and_extract_files.sh
#!/bin/bash
echo "step_1b_copy_local_files_and_extract_files.sh"
# exit script on any error
set -e
if [ ! -d "$WORK" ]; then
echo "Work directory: "${WORK}" does not exist, exiting."
exit 1
fi
cd $WORK
echo "extracting fetched files."
tar xf $FETCHED_FILES_DIR/$BINUTILS_FILE
tar xf $FETCHED_FILES_DIR/$GCC_FILE
tar xf $FETCHED_FILES_DIR/$GLIBC_FILE
echo "copying linux from: $FETCHED_FILES_DIR"
cp -r $FETCHED_FILES_DIR/linux .
echo "done."
step_2_build_binutils.sh
#!/bin/bash
echo "step_2_build_binutils.sh"
# exit script on any error
set -e
if [ ! -d "$WORK" ]; then
echo "Work directory: "${WORK}" does not exist, exiting."
exit 1
fi
cd $WORK
mkdir build-binutils
cd build-binutils
echo ${BINUITLS_VERSION}
../"binutils-"${BINUTILS_VERSION}/configure \
--prefix=$PREFIX \
--target=aarch64-linux-gnu \
--disable-multilib
make -j 4
make install
step_3_gcc_prerequisites.sh
#!/bin/bash
echo "step_3_gcc_prerequisites.sh"
# exit script on any error
set -e
if [ ! -d "$WORK" ]; then
echo "Work directory: "${WORK}" does not exist, exiting."
exit 1
fi
cd $WORK
echo "Downloading GCC prerequisties ... "
cd "gcc-"${GCC_VERSION_TO_BUILD}
contrib/download_prerequisites
rm *.tar.*
cd $WORK
echo " ... done. "
step_4_kernel_headers.sh
#!/bin/bash
echo "step_4_kernel_headers.sh"
# exit script on any error
set -e
if [ ! -d "$WORK" ]; then
echo "Work directory: "${WORK}" does not exist, exiting."
exit 1
fi
cd $WORK
cd linux
KERNEL=kernel7
make ARCH=arm64 INSTALL_HDR_PATH=$PREFIX/aarch64-linux-gnu headers_install
step_5_partial_gcc-16.1_build_SEE_NOTE.sh
#!/bin/bash
echo "step_5_partial_gcc-16.1_build_SEE_NOTE.sh"
# exit script on any error
set -e
if [ ! -d "$WORK" ]; then
echo "Work directory: "${WORK}" does not exist, exiting."
exit 1
fi
cd $WORK
echo "Configuring GCC ... "
mkdir build-gcc
cd build-gcc
../"gcc-"${GCC_VERSION_TO_BUILD}/configure \
--prefix=$PREFIX \
--target=aarch64-linux-gnu \
--enable-languages=c,c++,ada,algol68,fortran \
--enable-libada \
--with-arch=armv8-a \
--disable-multilib
make -j4 all-gcc
make install-gcc
echo
echo ". . . . . . . . . . . . . . . . . . . . . . . . ."
echo " NOTE: step_9_install_libada.sh (and others) is needed, because trying to"
echo " do 'make all-target-libada' at this stage would cause a compilation"
echo " complaint about pthread."
echo ". . . . . . . . . . . . . . . . . . . . . . . . ."
echo
step_6_partial_Glibc_build.sh
#!/bin/bash
echo "step_6_partial_Glibc_build.sh"
# exit script on any error
set -e
if [ ! -d "$WORK" ]; then
echo Work directory: $WORK does not exist, exiting.
exit 1
fi
cd $WORK
echo "Configuring GLIBC ... "
mkdir build-glibc
cd build-glibc
../glibc-2.43/configure \
--prefix=$PREFIX/aarch64-linux-gnu \
--build=$MACHTYPE \
--host=aarch64-linux-gnu \
--target=aarch64-linux-gnu \
--with-arch=armv8-a \
--with-headers=$PREFIX/aarch64-linux-gnu/include \
--disable-multilib libc_cv_forced_unwind=yes
make install-bootstrap-headers=yes install-headers
make -j4 csu/subdir_lib
install csu/crt1.o csu/crti.o csu/crtn.o $PREFIX/aarch64-linux-gnu/lib
aarch64-linux-gnu-gcc \
-nostdlib -nostartfiles -shared -x c /dev/null \
-o $PREFIX/aarch64-linux-gnu/lib/libc.so
touch $PREFIX/aarch64-linux-gnu/include/gnu/stubs.h
step_7_back_to_gcc.sh
#!/bin/bash
echo "step_7_back_to_gcc.sh"
# exit script on any error
set -e
if [ ! -d "$WORK" ]; then
echo "Work directory: "${WORK}" does not exist, exiting."
exit 1
fi
cd $WORK
cd build-gcc
make -j4 all-target-libgcc
make -j4 -C gcc cross-gnattools ada.all.cross
make -j4 install-strip-gcc install-target-libgcc
step_8_finish_building_Glibc_with_hack_fix.sh
#!/bin/bash
echo "step_8_finish_building_Glibc_with_hack_fix.sh"
# exit script on any error
set -e
if [ ! -d "$WORK" ]; then
echo "Work directory: "${WORK}" does not exist, exiting."
exit 1
fi
cd $WORK
# Step 8 would fail with error about "cannot find libatomic_asneeded".
# A quick hack solution avoids this by touching the expected file:
#
touch $PREFIX/aarch64-linux-gnu/lib/libatomic_asneeded.a
cd build-glibc
make -j1
make install
echo
echo ". . . . . . . . . . . . . . . . . . . . . . . . ."
echo "To avoid a compilation error, a fix hack has been performed here, namely:"
echo "touch $PREFIX/aarch64-linux-gnu/lib/libatomic_asneeded.a"
echo
echo "Everything is probably just fine."
echo ". . . . . . . . . . . . . . . . . . . . . . . . ."
step_9_install_libada_libga68_libgfortran.sh
#!/bin/bash
echo "step_9_install_libada_libga68_libgfortran.sh"
# exit script on any error
set -e
if [ ! -d "$WORK" ]; then
echo "Work directory: "${WORK}" does not exist, exiting."
exit 1
fi
cd $WORK
echo "Installing libada, libga68, libgfortran ... "
cd build-gcc
make all-target-libada
make install-target-libada
make all-target-libga68
make install-target-libga68
make all-target-libgfortran
make install-target-libgfortran
echo "installed: libada, libga68, libgfortran."
If all went well, we can do a quick verification now.
step_10_verify_it_works.sh
#!/bin/bash
echo "step_10_verify_it_works.sh"
echo "The executables produced should work on a Raspberry Pi running linux."
echo "Compiling aarch64 c program"
$PREFIX/bin/aarch64-linux-gnu-gcc -o hello_from_c hello_from_c.c
echo "Compiling aarch64 ada program"
$PREFIX/bin/aarch64-linux-gnu-gnatmake hello_from_ada.adb
echo "Compiling aarch64 algol68 program"
$PREFIX/bin/aarch64-linux-gnu-ga68 -o hello_from_algol68 hello_from_algol68.a68
echo "Compiling aarch64 fortran program"
$PREFIX/bin/aarch64-linux-gnu-gfortran -static -o hello_from_fortran hello_from_fortran.f90
Which uses source files:
hello_from_c.c
#include <stdio.h>
int main() {
printf("Hello, from C!\n");
return 0;
}
hello_from_ada.adb
with Ada.Text_IO; use Ada.Text_IO;
procedure hello_from_ada is
begin
Put_Line("Hello from Ada!");
end hello_from_ada;
hello_from_algol68.a68
begin
puts("Hello from Algol68!'n")
end
hello_from_fortran.f90
program hello_from_fortran
print *, 'Hello from Fortran!'
end program hello_from_fortran