cmake_minimum_required(VERSION 3.10)
project(libelf C)

# ──────────────────────────────────────────────────────────────────────────
# GNU-free CMake build for elfutils' libelf.
#
# This replaces the ancient (2009) Michael Riepe libelf 0.8.x with the modern,
# actively-maintained elfutils libelf — the implementation the Linux kernel
# (objtool) detects via `pkg-config libelf`.  elfutils ships only an
# autoconf/automake + GNU make build; this CMakeLists builds *just* the libelf
# component (plus the three lib/ helpers it links) against the musl/LLVM
# toolchain, with no GNU autotools or GNU make involved.
#
# Expected source layout: the elfutils source tree, i.e. this file is dropped
# at the top level next to libelf/ and lib/.
#
# Build with the musl/LLVM toolchain file, e.g.:
#   cmake -S . -B build \
#       -DCMAKE_TOOLCHAIN_FILE=/usr/local/musl/share/musl-llvm-toolchain.cmake \
#       -DCMAKE_INSTALL_PREFIX=/usr/local
#   cmake --build build
#   cmake --install build
# ──────────────────────────────────────────────────────────────────────────

set(CMAKE_C_STANDARD 99)
set(CMAKE_POSITION_INDEPENDENT_CODE ON)

# Version (elfutils). Override with -DLIBELF_VERSION=...
if(NOT DEFINED LIBELF_VERSION)
    set(LIBELF_VERSION "0.193")
endif()

# zlib is a hard dependency of elf_compress.c (compressed ELF sections). It is
# built GNU-free from source into the same prefix before libelf.
find_library(ZLIB_LIB z PATHS "${CMAKE_INSTALL_PREFIX}/lib" /usr/local/lib /usr/local/musl/lib)
find_path(ZLIB_INC zlib.h PATHS "${CMAKE_INSTALL_PREFIX}/include" /usr/local/include /usr/local/musl/include)
if(NOT ZLIB_LIB)
    message(FATAL_ERROR "libelf: zlib (libz) not found — build zlib first.")
endif()

# ── Generated config.h ────────────────────────────────────────────────────
# Feature macros for a Linux/x86_64 + musl/LLVM host. Mirrors what elfutils'
# ./configure would emit for this target. Functions musl lacks a *declaration*
# for are set to 0 so lib/system.h provides a fallback; rawmemchr (no fallback)
# is supplied by musl-compat.h.
file(WRITE "${CMAKE_BINARY_DIR}/config.h"
"#ifndef _LIBELF_CONFIG_H
#define _LIBELF_CONFIG_H 1
#define PACKAGE        \"elfutils\"
#define PACKAGE_NAME   \"elfutils\"
#define PACKAGE_VERSION \"${LIBELF_VERSION}\"
#define VERSION        \"${LIBELF_VERSION}\"

/* x86_64 permits unaligned accesses. */
#define ALLOW_UNALIGNED 1

/* GNU libc extensions: musl declares these under _GNU_SOURCE. */
#define HAVE_DECL_MEMPCPY      1
#define HAVE_DECL_MEMRCHR      1
#define HAVE_DECL_REALLOCARRAY 1
/* musl lacks these declarations -> use elfutils fallbacks / musl-compat.h. */
#define HAVE_DECL_POWEROF2     0
#define HAVE_DECL_RAWMEMCHR    0

/* musl has <err.h> (verr/vwarn) but not glibc's <error.h>; lib/error.c
   synthesises error() from err.h when HAVE_ERROR_H is undefined. */
#define HAVE_ERR_H 1

/* Toolchain (clang) feature support. */
#define HAVE_FALLTHROUGH 1
#define HAVE_GCC_STRUCT  1
#define HAVE_VISIBILITY  1

/* Linux mremap(2). */
#define HAVE_MREMAP 1

/* SYMBOL_VERSIONING and USE_LOCKS intentionally left undefined:
   libelf uses no symbol-version macros, and objtool/modpost use libelf
   single-threaded, so locks expand to no-ops. */

/* elfutils expects its configure-generated config.h to pull in eu-config.h
   (compiler attribute macros: attribute_hidden, internal_function, ...). */
#include <eu-config.h>
#endif
")

# ── Sources ───────────────────────────────────────────────────────────────
set(ELF_DIR "${CMAKE_SOURCE_DIR}/libelf")
set(LIB_DIR "${CMAKE_SOURCE_DIR}/lib")

if(NOT EXISTS "${ELF_DIR}/libelfP.h")
    message(FATAL_ERROR "libelf: expected elfutils source tree at ${CMAKE_SOURCE_DIR} (missing libelf/).")
endif()

# All libelf/*.c belong to the library (verified against libelf_a_SOURCES).
file(GLOB libelfSrcs "${ELF_DIR}/*.c")

# Helpers libelf links from lib/: tsearch wrappers, xmalloc, and the err.h
# based error() fallback.
list(APPEND libelfSrcs
    "${LIB_DIR}/eu-search.c"
    "${LIB_DIR}/xmalloc.c"
    "${LIB_DIR}/error.c"
)

set(libelfCFLAGS
    -D_GNU_SOURCE
    -DHAVE_CONFIG_H
    -std=gnu99
    -include "${CMAKE_SOURCE_DIR}/musl-compat.h"
    -Wno-macro-redefined
)

include_directories(
    "${CMAKE_BINARY_DIR}"   # config.h
    "${ELF_DIR}"
    "${LIB_DIR}"
    "${ZLIB_INC}"
)

# ── Shared + static library ───────────────────────────────────────────────
add_library(elf SHARED ${libelfSrcs})
target_compile_options(elf PRIVATE ${libelfCFLAGS} -DPIC -DSHARED)
target_link_libraries(elf PRIVATE ${ZLIB_LIB})
set_target_properties(elf PROPERTIES
    OUTPUT_NAME elf
    VERSION "1.${LIBELF_VERSION}"
    SOVERSION "1"
)

add_library(elf-static STATIC ${libelfSrcs})
target_compile_options(elf-static PRIVATE ${libelfCFLAGS})
set_target_properties(elf-static PROPERTIES OUTPUT_NAME elf)

# ── Install ───────────────────────────────────────────────────────────────
install(TARGETS elf elf-static
    LIBRARY DESTINATION lib
    ARCHIVE DESTINATION lib
    RUNTIME DESTINATION bin
)

# Public headers (elfutils ships its own richer <elf.h> superset that objtool
# expects, plus libelf.h/gelf.h/nlist.h).
install(FILES
    "${ELF_DIR}/libelf.h"
    "${ELF_DIR}/gelf.h"
    "${ELF_DIR}/nlist.h"
    "${ELF_DIR}/elf.h"
    DESTINATION include
)

# pkg-config file so the kernel's `HOSTPKG_CONFIG libelf` resolves.
file(WRITE "${CMAKE_BINARY_DIR}/libelf.pc"
"prefix=${CMAKE_INSTALL_PREFIX}
exec_prefix=\${prefix}
libdir=\${prefix}/lib
includedir=\${prefix}/include

Name: libelf
Description: elfutils libelf library to read and write ELF files
Version: ${LIBELF_VERSION}
URL: http://elfutils.org/

Libs: -L\${libdir} -lelf
Libs.private: -lz
Cflags: -I\${includedir}
")
install(FILES "${CMAKE_BINARY_DIR}/libelf.pc" DESTINATION lib/pkgconfig)
