Compilación y enlazamiento

Ahora que entendemos que es un procesador, que arquitecturas hay, que especializaciones existen, como funcionan (a grandes rasgos) y que es la memoria podemos ir hacia; la compilación

Cuando uno escribe un programa, sea el lenguaje que sea (Rust, C, C++, JavaScript, etc) estamos usando el lenguaje humano;

“Código del programa ps del sistema operativo BSD”

Sin embargo, el procesador no entiende este lenguaje humano, como todo componente de electrónica entiende y se basa en pulsos eléctricos codificados en binario (ceros 0 y unos 1) por lo que necesitamos de un programa que pueda entender ese lenguaje humano, entender su lógica y poder traducirlo (en el lenguaje ensamblador respectivo de la arquitectura del CPU) para que sea funcional, ese programa es lo que llamamos ‘compilador’.

A lo largo de la historia han existido una cantidad indefinida de compiladores. Existen dos tipos de compiladores que determinan también al lenguaje de programación que soportan;

  • Lenguajes de programación compilados

    Por el nombre puede dar a confusión, pero los compiladores a secas son los que primero compilan el código (de humano a máquina) y luego permiten que se pueda ejecutar. Son las primeras generaciones de los compiladores para los lenguajes de programación más veteranos y muchos de los más nuevos también.

    En esta categoría de lenguajes compilados entran lenguajes como; C, C++, Haskell, Rust, Go, Carbon (de Google), ensamblador, etc.

    En todas las arquitecturas el proceso es siempre el mismo para los programas en lenguajes compilados; se crea un código en el lenguaje de programación “X”, se ejecuta un compilador que lo reconoce y se compila para la arquitectura “Y” para finalmente ser enlazado.

    Ahora, ese binario ¿cómo maneja sus necesidades de interactividad con el sistema operativo y otras funcionalidades como sockets?;

    Para tratar ese problema tenemos las “dependencias” ó “bibliotecas”; como su nombre lo indica son dependencias que tiene el programa compilado para poder realizar una funcionalidad determinada de forma correcta.

    En los sistemas GNU/Linux se provee una herramienta llamada; “ldd” el cual al pasarle como argumento el path absoluto de un binario, devolverá al stdout la información sobre que bibliotecas externas que utiliza.

    Por ejemplo, estas son las dependencias externas para Nginx en un Arch Linux x86_64;

    “”

    Como se puede apreciar, el binario de Nginx depende de que en el sistema donde se ejecutan se encuentren tales bibliotecas. El “so” como extensión de archivo significa; “Shared Object” (“Objeto compartido). En este caso en concreto;

    BibliotecaLocaciónPaquete que lo provee
    linux-vdso.so.1Memoria RAMEl propio kernel Linux
    libcrypt.so.2/usr/lib/libcrypt.so.2libxcrypt
    libpcre2-8.so.0/usr/lib/libpcre2-9.so.0pcre2
    libssl.so.3/usr/lib/libssl.so.3openssl
    libcrypto.so.3/usr/lib/libcrypto.so.3openssl
    libz.so.1/usr/lib/libz.so.1zlib
    libGeoIP.so.1/usr/lib/libGeoIP.so.1geoip
    libc.so.6/usr/lib/libc.so.6glibc, aarch64-linux-gnu-glibc, riscv64-linux-gnu-glibc, etc

    Esto, en donde se suplen necesidades de un programa mediante dependencias externas alocadas en el sistema operativo mediante bibliotecas, se llama “compilación con bibliotecas compartidas”. En este proceso, el compilador llama luego al enlazador para que pueda referir en el binario a que bibliotecas (“shared object”) debe recurrir.

    Todo esto se vera en mayor profundidad en el siguiente capitulo.

    Como podrá apreciar, el binario de Nginx va a necesitar de que el sistema que lo ejecuta no solamente sea uno con un kernel Linux, Si no que además tenga todos esos paquetes instalados, si no fallará al funcionar.

    Si se desea indicar que esas bibliotecas sean embebidas dentro del binario, a fin de que sea tan autosuficiente como sea posible (necesitando solamente que sea un sistema Linux) y no se necesiten programas adicionales externos se debe indicar al compilador que utilice un proceso de compilación estático;

    CompiladorArgumento
    GCC-static -static-libgcc -static-libstdc++ -static-libasan -static-libtsan -static-liblsan -static-libubsan
    Clang–emit-static-lib -static-libgcc -static-libsan -static-libstdc++ -static-openmp -static
    Rustc-C target-feature=+crt-static
  • Lenguajes de programación interpretados

    Generalmente los intérpretes lo que hacen es tomar el código e ir compilándolo en tiempo real a medida que se ejecuta el programa. En esta categoría caen lenguajes de programación como; Python, Java, JavaScript (dependiendo del intérprete), Ruby, Perl, etc.

    Debido a que tienen que ir interpretando en tiempo de ejecución el código (véase, lo van ejecutando a medida que el programa se lee en tiempo real) tienen muchísima menos performance que los lenguajes compilados.

    Así mismo, debido a que estos intérpretes/compiladores tienen que ejecutar siempre el mismo código en diferentes plataformas también se los llama ‘Engines’ (motores).

Arquitectura de compilación

No se puede ejecutar un programa compilado para una arquitectura de CPU diferente a la del CPU en donde se intenta ejecutar (véase si compilas un programa para ARM, no vas a poder ejecutarlo en x86).

Pero sí se puede realizar un proceso de traducción el cual va a intentar ir traduciendo e interpretando en tiempo real para dar soporte al programa aunque sean dos arquitecturas diferentes, como hace el programa QEMU, pero generalmente suele haber muchísimos errores. Por lo tanto se debe tener en consideración eso.

Compilación nativa para ARM

Si se realiza una compilación nativa para un procesador ARM (o más comúnmente un SoC) se debe tener en conocimiento previo;

  • El ABI (“Application Binary Interface”; “Interfaz Binaria de la aplicación”)

    Esto determinará si, a nivel de la bibliotecas estándar de C utilizada en el sistema operativo, los tipos de variables; “int”, “long int” y los punteros en memorias son de 32 bits todos (ilp32) o si en cambio los “int” se mantienen en 32 bits pero los “long int” y los punteros pasan a ser de 64 bits (lp64).

    Se debe tener en cuenta que todo el programa debe ser compilado con la misma ABI.

https://gcc.gnu.org/onlinedocs/gcc/AArch64-Options.html

Listado de compiladores

Obviamente para poder traducir o interpretar el código de lenguaje humano a máquina se debe tener soporte de tal, por lo que no cualquier compilador sirve para cualquier lenguaje. Acá un listado de los más comunes;

  • GCC (Gnu Compiler Collection)

    Es una colección de compiladores del proyecto GNU, no es un intérprete y soporta lenguajes como; C, C++, Objective-C, Objective-C++, Fortran, Ada, D, y Go.

  • LLVM

    Es una colección de herramientas para compiladores, al igual que GCC no es un intérprete y soporta lenguajes como; ActionScript, Ada, C#, Common Lisp, PicoLisp, Crystal, CUDA, D, Delphi, Dylan, Forth, Fortran, Free Basic, Free Pascal, Graphical G, Halide, Haskell, Java bytecode, Julia, Kotlin, Lua, Objective-C, OpenCL, PostgreSQL’s SQL and PLpgSQL, Ruby, Rust, Scala, Swift, XC, Xojo y Zig.

    El compilador de Rust (rustc) está construido usando LLVM.

  • Java Virtual Machine (JVM)

    Es el compilador e intérprete para el lenguaje de programación Java.

  • CPython

    Es el compilador e intérprete por defecto para el lenguaje de programación Python.

  • Gecko / Servo

    Es el compilador e intérprete que usa Mozilla Firefox para el lenguaje de programación JavaScript.

  • V8

    Es el compilador e intérprete que usa Google Chrome, Chromium, Atom edit, Pulsar Edit, Microsoft Edge, Opera Browser y varios más para JavaScript.

Proceso de compilación

El proceso de compilación de un programa puede ser tan simple o tan complejo como sea el proyecto en si mismo.

Debido a la complejidad de los proyectos actuales existen diversas formas de realizar este proceso, pero en lineas generales todos siguen este mismo proceso;

  1. Configurar el código fuente y creación del script de compilación

    Mediante scripts o argumentos se seleccionan que características (features) se quieren o no en el programa final y el programa/script se encarga de verificar que existen las dependencias para cumplir con la compilación correctamente.

    Luego Se crea, en un mismo directorio u otro aparte, los archivos necesarios para saber que partes del programa deben ser compilados en el binario final según la configuración que hizo el paso anterior. Acá se incluyen diversas opciones/argumentos que se pasan al compilador final.

    Nos vamos a encontrar generalmente con varias opciones posibles (dependiendo del programa en que fueron programados);

    1. Autotools ( el script; “configure” )

      Este script se encarga de configurar las características finales de programas creados en C,C++ y otros programas soportados por “make” (ya lo veremos).

      Se tiene dos posibilidades, o lo ejecutas sin argumentos para que deje las opciones por defecto o;

      ./configure --help

      Ese comando te devolverá todas las opciones que se pueden aplicar al proceso de compilación, desde que características podes habilitar o deshabilitar hasta otras cosas mas extravagantes.

      Una vez sepamos que opciones y características queremos habilitar/deshabilitar, las pasamos como argumentos del script de “configure”;

      ./configure [argumentos]

      Luego el mismo empezara a verificar si tenes las librerías necesarias y te avisara si falta alguna.

      Programas que usan “configure”; coreutils, bash, grep, nginx, libreoffice, etc.

      Pueden haber casos (como en los ports de los sistemas BSD) en donde no se use principalmente “configure” si no “make configure” debido a que los sistemas BSD suelen incluir parches y otras cuestiones.

    2. CMake

      Como tal CMake se encarga de crear los archivos de configuración que distintos compiladores y entornos entienden para luego compilar de forma apropiada el programa.

      Se le dice a CMake como; “un meta sistema de construcción”. Ya que no construye nada, si no que crea los archivos de configuración.

      Puede crear archivos de configuración para Make (los Makefile, al igual que el script “configure”), para Meson - Ninja, y para multitud.

      Todos los proyectos que utilizan CMake tienen estos archivos en sus directorios;

      • CMakeLists.txt

        Este es el archivo principal de configuración que se utiliza para describir cómo debe ser construido el proyecto. Contiene las instrucciones para CMake, como qué archivos fuente deben ser compilados, qué bibliotecas deben ser vinculadas, y más.

      • CMakeCache.txt

        Este archivo es generado automáticamente por CMake. Contiene valores de configuración que CMake utiliza para configurar el proyecto. Por ejemplo, puede almacenar rutas a bibliotecas o herramientas específicas que el proyecto necesita.

      El proceso de CMake es;

      Primero vamos al directorio donde esta el proyecto con CMake, luego creamos un directorio llamado “build” y entramos en el;

      mkdir build && cd build

      Luego vemos que opciones podemos habilitar o deshabilitar del programa;

      cmake -L ../

      Elegimos que opciones habilitamos o deshabilitados;

      cmake -D[opción-1]=[ON/OFF] -D[opción-2]=[ON/OFF] -D[opción-N]=[ON/OFF] ../

    3. Meson - Ninja

      Meson (https://github.com/mesonbuild/meson ) es un frontend amigable para el backend Ninja (https://github.com/ninja-build/ninja) , en donde ambos estando programados en Python sirven como un sistema automatizado para compilar. Por ende, Python es un requisito para ambos.

      Meson se encarga de, al igual que CMake, construir el archivo de configuración. Normalmente se utiliza en conjunto con Ninja.

      Ninja luego lee sus archivos y empieza el proceso de compilación utilizando compiladores como; GCC, Clang, etc.

      Todos los proyectos que utilizan Meson tienen estos archivos en sus directorios;

      • meson.build

        Este es el archivo principal de configuración en Meson. Es equivalente al CMakeLists.txt en CMake y define cómo debe ser construido el proyecto.

      • meson-options.txt:

        Este archivo es opcional y se utiliza para definir las opciones de configuración del proyecto, como si un usuario desea habilitar o deshabilitar ciertas características.

      Todos los proyectos que utilizan Ninja tienen estos archivos en sus directorios;

      • build.ninja:

        Este es el archivo principal que Ninja utiliza para saber qué pasos ejecutar para construir el proyecto. Si se usa Ninja directamente, este archivo debe haber sido generado previamente por herramientas como CMake o Meson.

      • .ninja_log:

        Este archivo es creado por Ninja durante la construcción. Contiene información sobre las acciones ejecutadas, como compilación de archivos o ejecución de scripts.

      El proceso de Meson es;

      Primero vamos al directorio donde esta el proyecto con Meson, luego creamos un directorio llamado “build”;

      mkdir build

      Luego lo configuramos para setear las opciones que queremos pasando el directorio “build”;

      meson configure build/

      También podemos hacerlo de otra forma si ya conocemos las opciones o si necesitamos automatizar el proceso;

      meson setup build/ . -D[opción-1]=[ON/OFF] -D[opción-2]=[ON/OFF] -D[opción-N]=[ON/OFF]

  2. Compilación

    El compilador es llamado en cada paso y realiza las operaciones pertinentes.

    Acá voy a entrar en un pequeño detalle importante; soy muy partidario de optimizar el programa final mediante la deshabilitacion de características que no se quieren (véase, todo lo descrito en el punto anterior con “configure”, CMake o Meson) y de la optimización del binario final (mas adelante te muestro).

    Una vez que configuramos el proyecto procederemos a iniciar el proceso de compilación (el cual esta automatizado para que no lo hagamos a mano). Debemos prestar atención al directorio “build” que usamos para construir (no confundir con compilar) el proyecto.

    Si dentro encontramos un archivo “Makefile” entonces el programa que se encarga de llamar al compilador (GCC, Clang, etc) y realizar el proceso es “make”. Iniciamos el proceso de compilación con;

    make -j$(nproc)

    Una vez finalizado lo instalamos;

    sudo make install

    Si por el contrario, encontramos un archivo “build.ninja” significa que el programa que se encarga de llamar al compilador (GCC, Clang, etc) y realizar el proceso es “ninja”. Iniciamos el proceso de compilación con;

    ninja -c build

    Una vez finalizado lo instalamos;

    ninja -c build install

Esto es el proceso de configuración y compilación básica, pero podemos realizar optimizaciones para que el programa sea compilado específicamente para nuestra CPU, así como aumentar el nivel de seguridad del binario final.

Lo primero, lo de compilar el programa especifícamele para el CPU, es por lo siguiente; todos los compiladores tienen la capacidad de tener como objetivo (“target”) una arquitectura o modelo especifico de CPU. Por ende, podemos compilar de forma general un programa para toda la arquitectura x86_64, o podemos apuntar de forma especifica a un Intel Core i7-12700H. Si hacemos lo ultimo, el programa sera; mas chico, mas eficiente y mas rápido, ya que el compilador podrá aprovechar todas las características especificas de ese CPU que no necesariamente se comparten con el resto de procesadores x86_64 si lo compiláramos de forma general.

Eso es lo que permite que distribuciones Linux como; Gentoo, Funtoo, Slackware, etc tengan un rendimiento tan alto cuando se configura la compilación especifica de un programa para el CPU del usuario.

Por otro lado, el binario final puede incluir (o no) medidas de seguridad a muy bajo nivel como; escritura de ceros para los valores en memoria RAM antes del retorno y liberación de esa memoria, terminación automática y completa del programa si se detecta un fallo en concreto dentro del stack/heap de la memoria del programa (para evitar casos de toma de control de punteros), entre muchos otros.

Acá te dejo una tabla de los flags que recomiendo incluir siempre para C;

FlagPropósito
-march=nativeCompilar el programa de forma especifica para el CPU.
-O2Aplica el segundo nivel de optimización al binario.
-Wp,-D_FORTIFY_SOURCE=2Aplica fortificaciones de seguridad generales.
-fstack-clash-protectionAplica protecciones para evitar que un exploit tome control del programa ante un fallo en el stack.
-fcf-protectionHabilita una característica de protección del flujo de control en el binario, que se logra mediante una serie de mecanismos que aseguran que las direcciones de salto (como las llamadas a funciones o las instrucciones return) no puedan ser manipuladas por un atacante.
-fstack-protector-strongSe utiliza para habilitar una protección de la pila más fuerte, protegiendo el programa contra desbordamientos de buffer y otros ataques relacionados con la manipulación de la memoria.
-fuse-ld=moldUsar el enlazador “mold” que es muy rápido y eficiente.

Para C++ recomiendo el flag; “Wp,-D_GLIBCXX_ASSERTIONS”.

Para Rust (y su compilador rustc) recomiendo estos flags que suelen ir en la

FlagPropósito
-C target-cpu=nativeCompilar el programa de forma especifica para el CPU.
-C opt-level=2Aplica el segundo nivel de optimización al binario.
-C link-arg=-fuse-ld=moldUsar el enlazador “mold que es muy rápido y eficiente.
-C strip=debuginfo -C strip=symbols -C debug-assertions=falseEliminar todos los símbolos de depuración del binario final.

Para el enlazador (se explica todo en el siguiente capitulo);

FlagPropósito
-WlSe usa para pasar las próximas opciones directamente al enlazador
-O1Aplica optimizaciones básicas al proceso de enlazado, como la eliminación de símbolos no utilizados y la reorganización de secciones.
–sort-commonLe dice al enlazador que ordene las secciones common (las que contienen variables globales no inicializadas) de forma que las variables más grandes se encuentren primero. Mejorando el rendimiento
–as-neededIndica al enlazador que solo debe incluir las bibliotecas que realmente se necesiten. Si una biblioteca está incluida en la lista de bibliotecas del enlazado, pero no se usan símbolos de esa biblioteca en el código, la biblioteca no será incluida en el binario final.
-z,relroEs una opción de seguridad que hace que algunas secciones del binario sean de solo lectura después de la fase de enlace.
-z,nowEs una opción de seguridad que le indica al enlazador que todas las reubicaciones de bibliotecas compartidas deben resolverse al inicio del programa (es decir, en el momento de la carga), en lugar de hacerlo cuando se hace uso de las funciones o símbolos de esas bibliotecas.
-z,defsEs una opción de seguridad que se debe generar un error si se encuentra una referencia a un símbolo que no esté definido en el binario o en las bibliotecas que se están enlazando.
-fuse-ld=moldUsar el enlazador “mold que es muy rápido y eficiente.

Todas estas opciones se realizan indicando a “configure”, CMake o Meson los argumentos que le deben pasar Ninja y Make a GCC, Clang, etc cada vez que se encarguen de compilar un binario u objeto;

Nota
“C” es para el lenguaje C, y “CXX” es para el lenguaje C++
  • Autotools (el script; “configure”)

    Cuando ejecutamos el script debemos añadir antes las variables de entorno con los flags de compilación respectivos;

LDFLAGS=“[flags-ld]” RUSTFLAGS=“[flags]” CFLAGS=“[flags]” CXXFLAGS=“[flags]” ./configure [opciones/argumentos]

  • CMake

    Editaremos el archivo “CMakeLists.txt” del proyecto;

set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} [flags_de_compilacion]")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} [flags_de_compilacion]")

set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} [flags-ld] -lm")

set(RUST_FLAGS "[flags_de_compilacion]")

Si no queremos editar el archivo, lo podemos hacer al momento de configurar el proyecto cuando elegimos que opciones habilitamos o deshabilitados;

cmake -DCMAKE_C_FLAGS=“[flags_de_compilación]” -DCMAKE_CXX_FLAGS=“[flags_de_compilación]” -DCMAKE_EXE_LINKER_FLAGS=“[flags-ld]” ../

  • Meson

    Editaremos el archivo “meson.build” del proyecto;

add_project_arguments('[flag_1]', '[flag_2]', '[flag_3]', language: 'c')
add_project_arguments('[flag_1]', '[flag_2]', '[flag_3]', language: 'cpp')

add_project_link_arguments('[flags-ld]', '-lm', language: 'c')
add_project_link_arguments('[flags-ld]', '-lm', language: 'cpp')

rustc = import('rust').rustc
rustc.set_option('[flag_1]')
rustc.set_option('[flag_X]')

Si no queremos editar el archivo, lo podemos hacer al momento de configurar el proyecto cuando elegimos que opciones habilitamos o deshabilitados;

RUSTFLAGS=“[flags]” meson setup builddir –cflags “ [flags_de_compilación] “ –cxxflags “ [flags_de_compilación] “

Automatización

Personalmente recomiendo siempre que primero hagas todo a mano, y cuando hallas aprendido lo automatices.

Dependiendo de la distribución y mil cosas adicionales, pueden haber mil y un formas de automatizar la compilación de un programa. Yo, como usuario de Artix Linux (distribución basada en Arch pero con OpenRC en vez de SystemD), hago uso de los archivos “PKGBUILD”.

Los archivos PKGBUILD son documentos en texto plano, sin extensión, que contienen todas y cada una de las instrucciones que debe seguir el gestor de paquetes “pacman” para; descargar un programa, compilarlo paso a paso y luego crear el paquete instalable.

Podes revisar mi repositorio personal en donde tengo muchos programas modificados para hacerlos livianos y seguros; https://github.com/ShyanJMC/minimal_packages.

Estructura del archivo PKGBUILD de LibreWolf (navegador basado en Firefox);

# Maintainer: ShyanJMC <shyan@shyanjmc.com>

pkgname=librewolf
_pkgname=LibreWolf
pkgver=132.0
pkgrel=1
pkgdesc="Community-maintained fork of Firefox, focused on privacy, security and freedom."
url="https://librewolf.net/"
arch=(x86_64)
license=(
  GPL
  LGPL
  MPL
)
depends=(
  dbus-glib
  ffmpeg
  gtk3
  libpulse
  libxss
  libxt
  mime-types
  nss
  ttf-font
)
makedepends=(
  binutils cbindgen ccache clang diffutils dump_syms git imake inetutils lld
  llvm mesa mold nasm nodejs pciutils pipewire pipewire-alsa pipewire-pulse
  python rust sndio unzip 'wasi-compiler-rt>15' 'wasi-libc++>15' 'wasi-libc++abi>15'
  'wasi-libc>=1:0+314+a1c7c2c' wireplumber xorg-server-xvfb yasm  zip
)

optdepends=(
  'hunspell-en_US: Spell checking, American English'
  'libnotify: Notification integration'
  'networkmanager: Location detection via available WiFi networks'
  'pipewire: Audio and screensharing support'
  'speech-dispatcher: Text-to-Speech'
  'xdg-desktop-portal: Screensharing with Wayland'
)
backup=('usr/lib/librewolf/librewolf.cfg'
        'usr/lib/librewolf/distribution/policies.json')
        
options=(
  !debug
  !emptydirs
  !lto
  !makeflags
  strip
  
)
_arch_git=https://raw.githubusercontent.com/archlinux/svntogit-packages/packages/firefox/trunk
_arch_git_blob=https://raw.githubusercontent.com/archlinux/svntogit-packages

install='librewolf.install'
source=(
  https://gitlab.com/api/v4/projects/32320088/packages/generic/librewolf-source/${pkgver}-${pkgrel}/librewolf-${pkgver}-${pkgrel}.source.tar.gz # {,.sig} sig files are currently broken, it seems
  $pkgname.desktop
  "default192x192.png"
)

sha256sums=('f4ba8d96e73cc3f8449979e8ff47aaa4baa9d229c24f50793b6f0bed73eeed7c'
            '21054a5f41f38a017f3e1050ccc433d8e59304864021bef6b99f0d0642ccbe93'
            '959c94c68cab8d5a8cff185ddf4dca92e84c18dccc6dc7c8fe11c78549cdc2f1')

validpgpkeys=('034F7776EF5E0C613D2F7934D29FBD5F93C0CFC3') # maltej(?)

prepare() {
  export MOZ_APP_REMOTINGNAME=${_pkgname}
  mkdir -p mozbuild
  cd librewolf-$pkgver-$pkgrel

  mv mozconfig ../mozconfig

  cat >>../mozconfig <<END
  
ac_add_options --enable-application=browser
ac_add_options --enable-linker=mold
ac_add_options --prefix=/usr
ac_add_options --disable-debug-symbols
ac_add_options --disable-rust-debug
ac_add_options --disable-rust-tests
#ac_add_options --enable-address-sanitizer
ac_add_options --disable-fuzzing
#ac_add_options --enable-signed-overflow-sanitizer
#ac_add_options --disable-nodejs
ac_add_options --enable-strip
ac_add_options --disable-accessibility
ac_add_options --disable-parental-controls
ac_add_options --disable-synth-speechd
ac_add_options --disable-webspeech
ac_add_options --disable-webspeechtestbackend
ac_add_options --disable-wmf
ac_add_options --disable-debug-js-modules
ac_add_options --enable-sandbox
ac_add_options --enable-webrtc
ac_add_options --disable-bootstrap
ac_add_options --enable-release
ac_add_options --with-app-name=${pkgname}
ac_add_options --with-app-basename=${pkgname}
ac_add_options --enable-update-channel=release
ac_add_options --with-distribution-id=com.shyanjmc.librewolf
ac_add_options --disable-unverified-updates
ac_add_options --with-system-nspr
ac_add_options --with-system-nss
ac_add_options --enable-alsa
ac_add_options --disable-jack
ac_add_options --enable-hardening
ac_add_options --enable-optimize
ac_add_options --enable-rust-simd
ac_add_options --disable-crashreporter
ac_add_options --disable-updater
ac_add_options --disable-tests
ac_add_options --disable-debug
ac_add_options --disable-elf-hack
ac_add_options --enable-lto
ac_add_options --without-wasm-sandboxed-libraries
ac_add_options --enable-wasm-memory64
ac_add_options --enable-wasm-memory-control
ac_add_options --enable-wasm-multi-memory
ac_add_options --enable-wasm-relaxed-simd
ac_add_options --enable-wasm-avx
ac_add_options --enable-wasm-simd

ac_add_options --disable-valgrind
ac_add_options --disable-gtest-in-build

# This requires cargo nightly, do not enable
#ac_add_options --enable-thread-sanitizer

#ac_add_options --enable-undefined-sanitizer
#ac_add_options --enable-unsigned-overflow-sanitizer

ac_add_options --enable-frame-pointers
ac_add_options --enable-audio-backends=pulseaudio
ac_add_options --enable-pulseaudio
ac_add_options --enable-sndio
ac_add_options --enable-default-toolkit=cairo-gtk3-wayland-only
ac_add_options --disable-webdriver
ac_add_options --enable-proxy-bypass-protection
ac_add_options --disable-proxy-direct-failover
ac_add_options --disable-backgroundtasks
ac_add_options --disable-legacy-profile-creation

END

}


build() {
  cd librewolf-$pkgver-$pkgrel

  echo "Cleaning old files"
  make clean
  
  export MOZCONFIG="$srcdir/mozconfig"
  export MOZ_NOSPAM=1
  export MOZBUILD_STATE_PATH="$srcdir/mozbuild"
  export MACH_BUILD_PYTHON_NATIVE_PACKAGE_SOURCE=pip
  
  # LTO needs more open files
  ulimit -n 4096
  
  ./mach build
    
}

package() {
  cd librewolf-$pkgver-$pkgrel
  DESTDIR="$pkgdir" ./mach install

  # mv ${pkgdir}/usr/local/lib ${pkgdir}/usr/lib/
  # mv ${pkgdir}/usr/local/bin ${pkgdir}/usr/bin/
  # rm -r ${pkgdir}/usr/local

  local vendorjs="$pkgdir/usr/local/lib/$pkgname/browser/defaults/preferences/vendor.js"

  install -Dvm644 /dev/stdin "$vendorjs" <<END

// Use LANG environment variable to choose locale
pref("intl.locale.requested", "");

// Use system-provided dictionaries
pref("spellchecker.dictionary_path", "/usr/share/hunspell");

// Disable default browser checking.
pref("browser.shell.checkDefaultBrowser", false);

// Dont disable extensions in the application directory
pref("extensions.autoDisableScopes", 11);

// More time before save file session in disk, this is to avoid consume SSD cycles
pref("browser.sessionstore.interval", 1800000);

// Hardening configurations/options guide
// https://brainfucksec.github.io/firefox-hardening-guide

// Disable startup warning
pref("browser.aboutConfig.showWarning", false);

// Home as startup page
// 0 = blank
// 1 = home
// 2 = last visited page
// 3 = resume previous session
pref("browser.startup.page", 1);
pref("browser.startup.homepage", "about:home");

// Disable Activity Stream on new windows and tab pages
pref("browser.newtabpage.enabled", false);
pref("browser.newtab.preload", false);
pref("browser.newtabpage.activity-stream.feeds.telemetry", false);
pref("browser.newtabpage.activity-stream.telemetry", false);
pref("browser.newtabpage.activity-stream.feeds.snippets", false);
pref("browser.newtabpage.activity-stream.feeds.section.topstories", false);
pref("browser.newtabpage.activity-stream.section.highlights.includePocket", false);
pref("browser.newtabpage.activity-stream.feeds.discoverystreamfeed", false);
pref("browser.newtabpage.activity-stream.showSponsored", false);
pref("browser.newtabpage.activity-stream.default.sites", false);
pref("browser.newtabpage.activity-stream.default.sites", "");

// Geolocation
pref("geo.provider.network.url", "");

// Disable using the OS’s geolocation service
pref("geo.provider.use_gpsd", false );
pref("geo.provider.use_geoclue", false);

// Disable region updates:
pref("browser.region.network.url", "");
pref("browser.region.update.enabled", false);

// Auto-updates / Recommendations
pref("pp.update.auto", false);

// Disable addons recommendations (uses Google Analytics)
pref("extensions.getAddons.showPane", false);
pref("extensions.htmlaboutaddons.recommendations.enabled", false);
pref("browser.discovery.enabled", false);

// Disable telemetry
pref("datareporting.policy.dataSubmissionEnabled", false);
pref("datareporting.healthreport.uploadEnabled", false);
pref("toolkit.telemetry.enabled", false);
pref("toolkit.telemetry.unified", false);
pref("toolkit.telemetry.server","data:,");
pref("toolkit.telemetry.archive.enabled", false);
pref("toolkit.telemetry.newProfilePing.enabled", false);
pref("toolkit.telemetry.shutdownPingSender.enabled", false);
pref("toolkit.telemetry.updatePing.enabled", false);
pref("toolkit.telemetry.bhrPing.enabled", false);
pref("toolkit.telemetry.firstShutdownPing.enabled", false);
pref("toolkit.telemetry.coverage.opt-out", true);
pref("toolkit.coverage.opt-out", true);
pref("toolkit.coverage.endpoint.base", "");
pref("browser.ping-centre.telemetry", false);
pref("beacon.enabled", false);


// Disable studies:
pref("app.shield.optoutstudies.enabled", false);

// Disable Normandy/Shield:
pref("app.normandy.enabled", false);
pref("app.normandy.api_url", "");

// Disable crash reports
pref("breakpad.reportURL", "");
pref("browser.tabs.crashReporting.sendReport", false);

// Disable captive portal detection
pref("captivedetect.canonicalURL", "");
pref("network.captive-portal-service.enabled", false);

// Disable network connections checks
pref("network.connectivity-service.enabled", false);

// Disable safe browsing service
pref("browser.safebrowsing.malware.enabled", false);
pref("browser.safebrowsing.phishing.enabled", false);

// Disable list of blocked URI
// pref("browser.safebrowsing.blockedURIs.enabled", false);

// Disable fetch of updates
pref("browser.safebrowsing.provider.google4.gethashURL", "");
pref("browser.safebrowsing.provider.google4.updateURL","");
pref("browser.safebrowsing.provider.google.gethashURL","");
pref("browser.safebrowsing.provider.google.updateURL","");
pref("browser.safebrowsing.provider.google4.dataSharingURL","");

// Disable checks for downloads
pref("browser.safebrowsing.downloads.enabled", false);
pref("browser.safebrowsing.downloads.remote.enabled",false);
pref("browser.safebrowsing.downloads.remote.url", "");

// Disable checks for unwanted software
pref("browser.safebrowsing.downloads.remote.block_potentially_unwanted", false);
pref("browser.safebrowsing.downloads.remote.block_uncommon", false);

// Disable bypasses the block of safe browsing with a click for current session:
pref("browser.safebrowsing.allowOverride", false); 

// Disable link prefetching
pref("network.prefetch-next", false);

// Disable DNS prefetching
pref("network.dns.disablePrefetch", false);

// Disable predictor
pref("network.predictor.enabled", false);

// Disable link-mouseover opening connection to linked server
pref("network.http.speculative-parallel-limit", 0);

// Disable mousedown speculative connections on bookmarks and history
pref("browser.places.speculativeConnect.enabled", false);

// Disable IPv6
// pref("network.dns.disableIPv6", false);

// Disable GIO protocols as a potential proxy bypass vectors
pref("network.gio.supported-protocols", "");

// Disable using UNC (Uniform Naming Convention) paths (prevent proxy bypass)
pref("network.file.disable_unc_paths", true);

// Remove special permissions for certain mozilla domains
pref("permissions.manager.defaultsUrl", "");

// Use Punycode in Internationalized Domain Names to eliminate possible spoofing
pref("network.IDN_show_punycode",true);

// Disable search suggestions
pref("browser.search.suggest.enabled", false);
pref("browser.urlbar.suggest.searches", false);

// Disable location bar domain guessing:

pref("browser.fixup.alternate.enabled", false);

// Display all parts of the url in the bar:

pref("browser.urlbar.trimURLs", false);

// Disable location bar making speculative connections:

pref("browser.urlbar.speculativeConnect.enabled", false);

// Disable form autofill:

pref("browser.formfill.enable", false);
pref("extensions.formautofill.addresses.enabled", false);
pref("extensions.formautofill.available", "off");
pref("extensions.formautofill.creditCards.available", false);
pref("extensions.formautofill.creditCards.enabled", false);
pref("extensions.formautofill.heuristics.enabled", false);

// Disable location bar contextual suggestions:

pref("browser.urlbar.quicksuggest.scenario","history");
pref("browser.urlbar.quicksuggest.enabled", false);
pref("browser.urlbar.suggest.quicksuggest.nonsponsored", false);
pref("browser.urlbar.suggest.quicksuggest.sponsored", false);

// Disable saving passwords:

pref("signon.rememberSignons", false);

// Disable autofill login and passwords:

pref("signon.autofillForms", false);

// Disable formless login capture for Password Manager:

pref("signon.formlessCapture.enabled", false);

// Hardens against potential credentials phishing:
// 0 = don’t allow sub-resources to open HTTP authentication credentials dialogs
// 1 = don’t allow cross-origin sub-resources to open HTTP authentication credentials dialogs
// 2 = allow sub-resources to open HTTP authentication credentials dialogs (default)

pref("network.auth.subresource-http-auth-allow", 1);

// Disable disk cache:

pref("browser.cache.disk.enable", false);

// Disable storing extra session data:
// 0 = everywhere
// 1 = unencrypted sites
// 2 = nowhere

pref("browser.sessionstore.privacy_level", 2);

// Disable resuming session from crash:

pref("browser.sessionstore.resume_from_crash", false);

// Disable page thumbnail collection

pref("browser.pagethumbnails.capturing_disabled", true);

// Disable favicons in profile folder

pref("browser.shell.shortcutFavicons", false);

// Delete temporary files opened with external apps:

pref("browser.helperApps.deleteTempFileOnExit", true);

// Enable HTTPS-Only mode in all windows:

pref("dom.security.https_only_mode", true);

// Disable sending HTTP request for checking HTTPS support by the server:

pref("dom.security.https_only_mode_send_http_background_request", false);

// Display advanced information on Insecure Connection warning pages:

pref("browser.xul.error_pages.expert_bad_cert", true);

// Disable TLS1.3 0-RTT (round-trip time):

pref("security.tls.enable_0rtt_data", false);

// Set OCSP to terminate the connection when a CA isn’t validate:

pref("security.OCSP.require", true);

// Disable SHA-1 certificates:

pref("security.pki.sha1_enforcement_level", 1);

// Enable strict pinning (PKP (Public Key Pinning)):
// 0 = disabled
// 1 = allow user MiTM (i.e. your Antivirus)
// 2 = strict

pref("security.cert_pinning.enforcement_level", 2);

// Enable CRLite
// 0 = disabled
// 1 = consult CRLite but only collect telemetry (default)
// 2 = consult CRLite and enforce both “Revoked” and “Not Revoked” results
// 3 = consult CRLite and enforce “Not Revoked” results, but defer to OCSP for “Revoked”

pref("security.remote_settings.crlite_filters.enabled", true);
pref("security.pki.crlite_mode", 2);

// Control when to send a referer:
// 0 = always (default)
// 1 = only if base domains match
// 2 = only if hosts match

pref("network.http.referer.XOriginPolicy", 2);

// Control the amount of information to send:
// 0 = send full URI (default): https://example.com:8888/foo/bar.html?id=1234
// 1 = scheme+host+port+path: https://example.com:8888/foo/bar.html
// 2 = scheme+host+port: https://example.com:8888

pref("network.http.referer.XOriginTrimmingPolicy", 2);

// Disable WebRTC

// pref("media.peerconnection.enabled", false);

// Force WebRTC inside the proxy:

pref("media.peerconnection.ice.proxy_only_if_behind_proxy", true);

// Force a single network interface for ICE candidates generation:

pref("media.peerconnection.ice.default_address_only", true);

// Force exclusion of private IPs from ICE candidates:

pref("media.peerconnection.ice.no_host", true);

// Disable WebGL (Web Graphics Library):

// pref("webgl.disabled", true);

// Disable autoplay of HTML5 media:
// 0 = allow all
// 1 = block non-muted media (default)
// 5 = block all

pref("media.autoplay.default", 5);
    
// Disable DRM Content: 
// pref("media.eme.enabled", false);

// Always ask you where to save files:

pref("browser.download.useDownloadDir", false);

// Disable adding downloads to system’s “recent documents” list:

pref("browser.download.manager.addToRecentDocs", false);

// Enable ETP (Enhanced Tracking Protection), ETP strict mode enables Total Cookie Protection (TCP):

pref("browser.contentblocking.category", "strict");

// Enable state partitioning of service workers:

pref("privacy.partition.serviceWorkers", true);

// Enable APS (Always Partitioning Storage)

pref("privacy.partition.always_partition_third_party_non_cookie_storag", true);
pref("privacy.partition.always_partition_third_party_non_cookie_storage.exempt_sessionstorage", true);

// Block popup windows:

pref("dom.disable_open_during_load", true);

// Limit events that can cause a popup:

pref("dom.popup_allowed_events", "click dblclick mousedown pointerdown");

// Disable Pocket extension:

pref("extensions.pocket.enabled", false);

// Disable Screenshots extension:

//pref("extensions.Screenshots.disabled", true);

// Disable PDJFS scripting:

pref("pdfjs.enableScripting", false);

// Enable Containers and show the UI settings:

pref("privacy.userContext.enabled", true);
pref("privacy.userContext.enabled", true);

// Set extensions to work on restricted domains, and their scopeis to “profile+applications”:

pref("extensions.enabledScopes", 5);
pref("extensions.webextensions.restrictedDomains", "");

// Display always the installation prompt:

pref("extensions.postDownloadThirdPartyPrompt", false);

// Clear history, cookies and site data when Firefox closes:

pref("network.cookie.lifetimePolicy", 2);
pref("privacy.sanitize.sanitizeOnShutdown", true);
pref("privacy.clearOnShutdown.cache", true);
pref("privacy.clearOnShutdown.cookies", true);
pref("privacy.clearOnShutdown.downloads", true);
pref("privacy.clearOnShutdown.formdata", true);
pref("privacy.clearOnShutdown.history", true);
pref("privacy.clearOnShutdown.offlineApps", true);
pref("privacy.clearOnShutdown.sessions", true);
pref("privacy.clearOnShutdown.sitesettings", false);
pref("privacy.sanitize.timeSpan", 0);

// Enable RFP:

pref("privacy.resistFingerprinting", true);

// Set new window size rounding max values:

pref("privacy.window.maxInnerWidth", 1600);
pref("privacy.window.maxInnerHeight", 900);

// Disable mozAddonManager Web API:

pref("privacy.resistFingerprinting.block_mozAddonManager", true);

// Disable using system colors:

pref("browser.display.use_system_colors", false);

// Disable showing about:blank page when possible at startup

pref("browser.startup.blankWindow", false);

// Disable using system colors:

pref("browser.display.use_system_colors", false);

END

  local distini="$pkgdir/usr/lib/$pkgname/distribution/distribution.ini"
  install -Dvm644 /dev/stdin "$distini" <<END

[Global]
id=io.gitlab.${pkgname}-community
version=1.0
about=LibreWolf

[Preferences]
app.distributor="LibreWolf Community"
app.distributor.channel=$pkgname
app.partner.librewolf=$pkgname
END

  for i in 16 32 48 64 128; do
    install -Dvm644 browser/branding/${pkgname}/default$i.png \
      "$pkgdir/usr/share/icons/hicolor/${i}x${i}/apps/$pkgname.png"
  done
  # install -Dvm644 browser/branding/librewolf/content/about-logo.png \
    # "$pkgdir/usr/share/icons/hicolor/192x192/apps/$pkgname.png"
  install -Dvm644 ${srcdir}/default192x192.png \
    "$pkgdir/usr/share/icons/hicolor/192x192/apps/$pkgname.png"

  # arch upstream provides a separate svg for this. we don't have that, so let's re-use 16.png
  install -Dvm644 browser/branding/${pkgname}/default16.png \
    "$pkgdir/usr/share/icons/hicolor/symbolic/apps/$pkgname-symbolic.png"

  install -Dvm644 ../$pkgname.desktop \
    "$pkgdir/usr/share/applications/$pkgname.desktop"

  # Install a wrapper to avoid confusion about binary path
  install -Dvm755 /dev/stdin "$pkgdir/usr/bin/$pkgname" <<END
#!/bin/sh
exec /usr/local/lib/$pkgname/librewolf "\$@"
END

  # Replace duplicate binary with wrapper
  # https://bugzilla.mozilla.org/show_bug.cgi?id=658850
  ln -srfv "$pkgdir/usr/local/bin/$pkgname" "$pkgdir/usr/local/lib/$pkgname/librewolf-bin"
  # Use system certificates
  local nssckbi="$pkgdir/usr/local/lib/$pkgname/libnssckbi.so"
  if [[ -e $nssckbi ]]; then
    ln -srfv "$pkgdir/usr/local/lib/libnssckbi.so" "$nssckbi"
  fi
}

Finalmente simplemente ejecuto “makepkg” y el proceso inicia, creando finalmente el archivo instalable para pacman.

LibreWolf puede ser un ejemplo complicado (aunque muy completo), por lo que acá te dejo la documentación oficial de Arch Linux de como crear el documento paso a paso de forma clara y sencilla;

Alpine Linux tiene un archivo de una estructura muy parecida llamado APKBUILD;