#!/usr/bin/env bash # shellcheck disable=SC2034 # Script Version: 1.1.5 # Updated: 12.18.25 # GitHub: https://github.com/slyfox1186/imagemagick-build-script # Purpose: Build ImageMagick 7 from the source code obtained from ImageMagick's official GitHub repository # Supported OS: Debian (11|12) | Ubuntu (20|22|24).04 set -o pipefail # Check if sudo is available for commands that need root if ! command -v sudo &>/dev/null && [[ "$EUID" -ne 0 ]]; then echo "Warning: sudo is not available and you are not root. Some operations may fail." fi # SET GLOBAL VARIABLES script_ver="1.1.4" cwd="$PWD/magick-build-script" packages="$cwd/packages" workspace="$cwd/workspace" regex_string='(Rc|rc|rC|RC|alpha|beta|master|pre)+[0-9]*$' debug="${debug:-OFF}" # Allow external toggle: DEBUG=true|false (also 1/0, on/off). if [[ -n "${DEBUG:-}" ]]; then case "${DEBUG,,}" in 1|true|yes|on) debug=ON ;; 0|false|no|off) debug=OFF ;; esac fi # Preferred GNU mirror (avoids regional/outage issues with ftp.gnu.org). GNU_MIRROR="${GNU_MIRROR:-https://mirrors.ibiblio.org/gnu}" GNU_MIRROR="${GNU_MIRROR%/}" # ImageMagick allocator selection. One of: jemalloc (default), tcmalloc, dmalloc, system IMAGEMAGICK_ALLOCATOR="${IMAGEMAGICK_ALLOCATOR:-jemalloc}" export IMAGEMAGICK_ALLOCATOR # Prefer sudo when not root. SUDO="" if [[ "$EUID" -ne 0 ]] && command -v sudo &>/dev/null; then SUDO="sudo" fi # Pre-defined color variables RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[0;33m' NC='\033[0m' # No Color # ANNOUNCE THE BUILD HAS BEGUN box_out_banner_header() { input_char=$(echo "$@" | wc -c) line=$(for i in $(seq 0 $input_char); do printf "-"; done) tput bold line="$(tput setaf 3)$line" space="${line//-/ }" echo " $line" printf "|" ; echo -n "$space" ; printf "%s\n" "|"; printf "| " ;tput setaf 4; echo -n "$@"; tput setaf 3 ; printf "%s\n" " |"; printf "|" ; echo -n "$space" ; printf "%s\n" "|"; echo " $line" tput sgr 0 } box_out_banner_header "ImageMagick Build Script v$script_ver" # CREATE OUTPUT DIRECTORIES mkdir -p "$packages" "$workspace" init_toolchain_flags() { # Respect user overrides, otherwise use distro hardening defaults when available. local sys_cflags sys_cxxflags sys_cppflags sys_ldflags sys_cflags="" sys_cxxflags="" sys_cppflags="" sys_ldflags="" if command -v dpkg-buildflags &>/dev/null; then sys_cflags="$(dpkg-buildflags --get CFLAGS)" sys_cxxflags="$(dpkg-buildflags --get CXXFLAGS)" sys_cppflags="$(dpkg-buildflags --get CPPFLAGS)" sys_ldflags="$(dpkg-buildflags --get LDFLAGS)" else sys_cflags="-O2 -pipe -fstack-protector-strong" sys_cxxflags="$sys_cflags" sys_cppflags="-D_FORTIFY_SOURCE=2" sys_ldflags="-Wl,-O1 -Wl,--as-needed -Wl,-z,relro" fi # Optional: host-tuned builds (not portable). Enable with IMAGEMAGICK_BUILD_MARCH_NATIVE=1 if [[ "${IMAGEMAGICK_BUILD_MARCH_NATIVE:-0}" == "1" ]]; then sys_cflags+=" -march=native" sys_cxxflags+=" -march=native" fi CC="${CC:-gcc}" CXX="${CXX:-g++}" if [[ -n "${CFLAGS:-}" ]]; then CFLAGS="$CFLAGS -fPIC" else CFLAGS="$sys_cflags -fPIC" fi if [[ -n "${CXXFLAGS:-}" ]]; then CXXFLAGS="$CXXFLAGS -fPIC" else CXXFLAGS="$sys_cxxflags -fPIC" fi if [[ -n "${CPPFLAGS:-}" ]]; then CPPFLAGS="$CPPFLAGS -I$workspace/include -I/usr/local/include -I/usr/include" else CPPFLAGS="$sys_cppflags -I$workspace/include -I/usr/local/include -I/usr/include" fi if [[ -n "${LDFLAGS:-}" ]]; then LDFLAGS="$LDFLAGS -Wl,--as-needed -Wl,-rpath,/usr/local/lib64:/usr/local/lib" else LDFLAGS="$sys_ldflags -Wl,--as-needed -Wl,-rpath,/usr/local/lib64:/usr/local/lib" fi export CC CXX CFLAGS CXXFLAGS CPPFLAGS LDFLAGS } # SET THE COMPILERS TO USE AND THE COMPILER OPTIMIZATION FLAGS init_toolchain_flags # SET THE AVAILABLE CPU COUNT FOR PARALLEL PROCESSING (SPEEDS UP THE BUILD PROCESS) if [[ -f /proc/cpuinfo ]]; then cpu_threads=$(grep -c ^processor /proc/cpuinfo) else cpu_threads=$(nproc --all) fi # Set the path variable PATH="/usr/lib/ccache:$workspace/bin:$PATH" export PATH # Set the pkg_config_path variable PKG_CONFIG_PATH="\ $workspace/lib64/pkgconfig:\ $workspace/lib/x86_64-linux-gnu/pkgconfig:\ $workspace/lib/pkgconfig:\ $workspace/share/pkgconfig:\ /usr/local/lib64/pkgconfig:\ /usr/local/lib/x86_64-linux-gnu/pkgconfig:\ /usr/local/lib/pkgconfig:\ /usr/lib/x86_64-linux-gnu/pkgconfig\ " export PKG_CONFIG_PATH # Ensure aclocal can find pkg-config and libtool m4 macros when bootstrapping # GitHub/GitLab snapshots via autogen/autoconf. ACLOCAL_PATH="$workspace/share/aclocal:/usr/local/share/aclocal:/usr/share/aclocal" export ACLOCAL_PATH exit_fn() { echo echo -e "${GREEN}[INFO]${NC} Make sure to ${YELLOW}star${NC} this repository to show your support!" echo -e "${GREEN}[INFO]${NC} https://github.com/slyfox1186/script-repo" echo exit 0 } fail() { echo echo -e "${RED}[ERROR]${NC} $1\n" echo -e "${GREEN}[INFO]${NC} For help or to report a bug, create an issue at: https://github.com/slyfox1186/script-repo/issues" echo exit 1 } log() { echo -e "${GREEN}[INFO]${NC} $1" } warn() { echo -e "${YELLOW}[WARNING]${NC} $1" } cleanup() { local choice echo echo "========================================================" echo " Would you like to clean up the build files? " echo "========================================================" echo echo "[1] Yes" echo "[2] No" echo read -r -p "Your choices are (1 or 2): " choice case "$choice" in 1|y|Y) rm -fr "$cwd" ;; 2|n|N) ;; *) unset choice cleanup ;; esac } execute() { local lineno="${BASH_LINENO[0]}" echo "$ $*" if [[ "$debug" == "ON" ]]; then if ! "$@"; then notify-send -t 5000 "Failed to execute: $*" 2>/dev/null || true fail "Failed to execute: $* (line $lineno)" fi else if ! "$@" >/dev/null 2>&1; then notify-send -t 5000 "Failed to execute: $*" 2>/dev/null || true fail "Failed to execute: $* (line $lineno)" fi fi } build() { local name="$1" local ver="$2" if [[ -f "$packages/$name.done" ]] && grep -Fx "$ver" "$packages/$name.done" >/dev/null; then log "$name version $ver already built. Remove $packages/$name.done lockfile to rebuild it." return 1 fi echo echo -e "${GREEN}Building ${YELLOW}$name${NC} - ${GREEN}version ${YELLOW}$ver${NC}" echo "==========================================" return 0 } build_done() { echo "$2" > "$packages/$1.done" } download() { local url="$1" local archive="${2:-${url##*/}}" local output_dir="$3" local target_file="$packages/$archive" local target_dir="$packages/${output_dir:-${archive%.tar*}}" # If a prior run left a truncated/corrupt file behind, remove it so we can re-download cleanly. if [[ -f "$target_file" ]] && [[ "$archive" == *.tar* ]]; then if ! tar -tf "$target_file" >/dev/null 2>&1; then warn "Cached download \"$archive\" is corrupt; deleting and re-downloading." rm -f "$target_file" else log "The file \"$archive\" is already downloaded." fi fi if [[ ! -f "$target_file" ]]; then log "Downloading \"$url\" saving as \"$archive\"" # Robust download: show only a progress bar, retry transient errors, then fall back to wget. if ! curl -fL --silent --show-error --progress-bar \ --retry 6 \ --retry-all-errors \ --retry-delay 2 \ --connect-timeout 15 \ --max-time 600 \ -o "$target_file" \ "$url"; then warn "curl download failed for \"$archive\", trying wget..." if ! wget --quiet --show-progress -O "$target_file" \ --timeout=20 --tries=6 "$url"; then rm -f "$target_file" fail "Failed to download \"$archive\" from \"$url\" after retries. Line: $LINENO" fi fi fi # Extract, retrying once if the archive is corrupt/truncated. for attempt in 1 2; do rm -rf "$target_dir" mkdir -p "$target_dir" if [[ -n "$output_dir" ]]; then if tar -xf "$target_file" -C "$target_dir"; then break fi else if tar -xf "$target_file" -C "$target_dir" --strip-components=1; then break fi fi if [[ $attempt -eq 1 ]]; then warn "Failed to extract \"$archive\"; deleting and retrying download once." rm -f "$target_file" log "Re-downloading \"$url\" saving as \"$archive\"" if ! curl -fL --silent --show-error --progress-bar \ --retry 6 \ --retry-all-errors \ --retry-delay 2 \ --connect-timeout 15 \ --max-time 600 \ -o "$target_file" \ "$url"; then warn "curl re-download failed for \"$archive\", trying wget..." if ! wget --quiet --show-progress -O "$target_file" \ --timeout=20 --tries=6 "$url"; then rm -f "$target_file" fail "Failed to re-download \"$archive\" from \"$url\" after retries. Line: $LINENO" fi fi else rm -f "$target_file" fail "Failed to extract \"$archive\" after re-download. Line: $LINENO" fi done log "File extracted: $archive" cd "$target_dir" || fail "Unable to change the working directory to \"$target_dir\" Line: $LINENO" } git_caller() { git_url="$1" repo_name="$2" recurse_flag="${3:-}" git_clone "$git_url" "$repo_name" "$recurse_flag" } git_clone() { local repo_url="$1" local repo_name="${2:-"${repo_url##*/}"}" repo_name="${repo_name//\./-}" local recurse_flag="${3:-}" local target_directory="$packages/$repo_name" local computed_version local tag_ref="" local store_prior_version="" local -a recurse_args=() # Try to get the latest tag tag_ref=$( git ls-remote --tags "$repo_url" | awk -F'/' '/\/v?[0-9]+\.[0-9]+(\.[0-9]+)?(-[0-9]+)?(\^\{\})?$/ { tag = $3; sub(/\^\{\}$/, "", tag); norm = tag; sub(/^v/, "", norm); print norm "\t" tag }' | sort -rV -k1,1 | head -n1 | awk '{print $2}' ) computed_version="${tag_ref#v}" # If no tags are found, use the latest commit hash as the version if [[ -z "$computed_version" ]]; then computed_version=$(git ls-remote "$repo_url" | grep "HEAD" | awk '{print substr($1,1,7)}' ) [[ -z "$computed_version" ]] && computed_version="unknown" fi [[ -f "$packages/$repo_name.done" ]] && store_prior_version=$(<"$packages/$repo_name.done") if [[ ! "$computed_version" == "$store_prior_version" ]]; then [[ "$recurse_flag" == "recurse" ]] && recurse_args=(--recursive) [[ -d "$target_directory" ]] && rm -fr "$target_directory" # Clone shallow without checkout, then fetch+checkout the selected tag. # This avoids noisy warnings like: # "warning: refs/tags/vX.Y.Z is not a commit!" # which can occur when cloning annotated tags with --branch in a shallow clone. if ! git -c advice.detachedHead=false clone --depth 1 --no-checkout "${recurse_args[@]}" -q "$repo_url" "$target_directory"; then echo echo -e "${RED}[ERROR]${NC} Failed to clone \"$target_directory\". Second attempt in 10 seconds..." echo sleep 10 if ! git -c advice.detachedHead=false clone --depth 1 --no-checkout "${recurse_args[@]}" -q "$repo_url" "$target_directory"; then fail "Failed to clone \"$target_directory\". Exiting script. Line: $LINENO" fi fi fi cd "$target_directory" || fail "Failed to cd into \"$target_directory\". Line: $LINENO" if [[ ! "$computed_version" == "$store_prior_version" ]]; then if [[ -n "$tag_ref" ]]; then git fetch -q --depth 1 origin "tag" "$tag_ref" || fail "Failed to fetch tag \"$tag_ref\" for \"$repo_name\". Line: $LINENO" git -c advice.detachedHead=false checkout -q "$tag_ref" || fail "Failed to checkout tag \"$tag_ref\" for \"$repo_name\". Line: $LINENO" else git checkout -q || fail "Failed to checkout default branch for \"$repo_name\". Line: $LINENO" fi if [[ "$recurse_flag" == "recurse" ]]; then git submodule update -q --init --recursive --depth 1 || fail "Failed to update submodules for \"$repo_name\". Line: $LINENO" fi fi version="$computed_version" return 0 } show_version() { echo log "ImageMagick's new version is:" echo magick -version 2>/dev/null || fail "Failure to execute the command: magick -version. Line: $LINENO" } # Parse each git repository to find the latest release version number for each program gnu_repo() { local url="$1" version=$(curl -fsS "$url" | grep -oP '[a-z]+-\K(([0-9\.]*[0-9]+)){2,}' | sort -rV | head -n1) } github_repo() { local count=1 local git_repo="$1" local git_url="$2" version="" # Fetch GitHub tags page while [[ $count -le 10 ]]; do # Apply case-insensitive matching for RC versions to exclude them version=$(curl -fsSL "https://github.com/$git_repo/$git_url" | grep -oP 'href="[^"]*/tags/[^"]*\.tar\.gz"' | grep -oP '\/tags\/\K(v?[\w.-]+?)(?=\.tar\.gz)' | grep -iPv '(rc)[0-9]*' | head -n1 | sed 's/^v//') # Check if a non-RC version was found if [[ -n "$version" ]]; then break else ((count++)) fi done # Handle cases where only release candidate versions are found after the script reaches the maximum attempts [[ -z "$version" ]] && fail "No matching version found without RC/rc suffix. Line: $LINENO" } gitlab_freedesktop_repo() { local count repo repo="$1" count=0 version="" while true; do if curl_results=$(curl -fsSL "https://gitlab.freedesktop.org/api/v4/projects/$repo/repository/tags"); then version=$(echo "$curl_results" | jq -r ".[$count].name") version="${version#v}" # Check if the version contains "RC" and skip it if [[ $version =~ $regex_string ]]; then ((count++)) else break # Exit the loop when a non-RC version is found fi else fail "Failed to fetch data from GitLab API. Line: $LINENO" fi done } gitlab_gnome_repo() { local count repo url repo="$1" url="$2" count=0 version="" [[ -z "$repo" ]] && fail "Repository name is required. Line: $LINENO" if curl_results=$(curl -fsSL "https://gitlab.gnome.org/api/v4/projects/$repo/repository/$url"); then version=$(echo "$curl_results" | jq -r '.[0].name') version="${version#v}" fi # Deny installing a release candidate while [[ $version =~ $regex_string ]]; do if curl_results=$(curl -fsSL "https://gitlab.gnome.org/api/v4/projects/$repo/repository/$url"); then version=$(echo "$curl_results" | jq -r ".[$count].name" | sed 's/^v//') fi ((count++)) done } find_git_repo() { local url="$1" local git_repo_type="$2" local url_action="$3" case "$git_repo_type" in 1) set_repo="github_repo" ;; 2) set_repo="gitlab_freedesktop_repo" ;; 3) set_repo="gitlab_gnome_repo" ;; *) fail "Error: Could not detect the variable \"\$git_repo_type\" in the function \"find_git_repo\". Line: $LINENO" esac case "$url_action" in T) set_action="tags" ;; *) set_action="$3" ;; esac "$set_repo" "$url" "$set_action" 2>/dev/null } download_fonts() { local -a font_urls=( "https://github.com/dejavu-fonts/dejavu-fonts.git" "https://github.com/adobe-fonts/source-code-pro.git" "https://github.com/adobe-fonts/source-sans-pro.git" "https://github.com/adobe-fonts/source-serif-pro.git" "https://github.com/googlefonts/roboto.git" "https://github.com/mozilla/Fira.git" ) local font_url repo_name for font_url in "${font_urls[@]}"; do repo_name="${font_url##*/}" repo_name="${repo_name%.git}" git_caller "$font_url" "$repo_name" if build "$repo_name" "$version"; then execute ${SUDO:-} cp -fr . "/usr/share/fonts/truetype/" build_done "$repo_name" "$version" fi done } find_ghostscript_version() { version="$1" formatted_version=$( echo "$version" | sed -E 's/gs([0-9]{2})([0-9]{2})([0-9])/\1.\2.\3/' ) gscript_url="https://github.com/ArtifexSoftware/ghostpdl-downloads/releases/download/${version}/ghostscript-${formatted_version}.tar.xz" } apt_pkgs() { local pkg missing_packages local -a pkgs=() pkgs=( "$@" alien autoconf autoconf-archive binutils bison build-essential cmake curl dbus-x11 flex fontforge git gperf intltool jq libcamd3 libcpu-features-dev libdmalloc-dev libdmalloc5 libfont-ttf-perl libgc-dev libgc1 libgegl-0.4-0 libgegl-common libgl2ps-dev libglib2.0-dev libgs-dev libheif-dev libhwy-dev libjxl-dev libnotify-bin librsvg2-dev libgraphviz-dev graphviz librust-jpeg-decoder-dev librust-malloc-buf-dev libsharp-dev libticonv-dev libtool libtool-bin libjpeg-dev libyuv-dev libyuv-utils libyuv0 lsb-release lzip m4 meson nasm ninja-build pkg-config python3-dev yasm zlib1g-dev ) # Initialize arrays for missing, available, and unavailable packages missing_packages=() available_packages=() unavailable_packages=() log "Checking package installation status..." # Loop through the array to find missing packages for pkg in "${pkgs[@]}"; do if ! dpkg-query -W -f='${Status}' "$pkg" 2>/dev/null | grep -q "ok installed"; then missing_packages+=("$pkg") fi done # Check the availability of missing packages and categorize them for pkg in "${missing_packages[@]}"; do if apt-cache show "$pkg" >/dev/null 2>&1; then available_packages+=("$pkg") else unavailable_packages+=("$pkg") fi done # Print unavailable packages if [[ "${#unavailable_packages[@]}" -gt 0 ]]; then echo warn "Unavailable packages:" printf " %s\n" "${unavailable_packages[@]}" fi # Install available missing packages if [[ "${#available_packages[@]}" -gt 0 ]]; then echo log "Installing available missing packages:" printf " %s\n" "${available_packages[@]}" echo ${SUDO:-} apt update # Avoid pulling large recommended sets and do not autoremove packages # (autoremoval can uninstall dev packages the user still wants). ${SUDO:-} apt install --no-install-recommends "${available_packages[@]}" echo else log "No missing packages to install or all missing packages are unavailable." fi } set_autotrace() { # Autotrace support removed per user request. autotrace_switch="--without-autotrace" } # Install APT packages echo echo "Installing required APT packages" echo "==========================================" debian_version() { case "$VER" in 11) apt_pkgs libvmmalloc1 libvmmalloc-dev ;; 12) apt_pkgs ;; *) fail "Could not detect the Debian version. Line: $LINENO" ;; esac } get_os_version() { if command -v lsb_release &>/dev/null; then OS=$(lsb_release -si) VER=$(lsb_release -sr) elif [[ -f /etc/os-release ]]; then source /etc/os-release OS=$NAME VER=$VERSION_ID else fail "Failed to define the \$OS and/or \$VER variables. Line: $LINENO" fi } # GET THE OS NAME get_os_version # DISCOVER WHAT VERSION OF LINUX WE ARE RUNNING (DEBIAN OR UBUNTU) case "$OS" in Arch) ;; Debian) debian_version ;; Ubuntu) apt_pkgs ;; *) fail "Could not detect the OS architecture. Line: $LINENO" ;; esac # INSTALL OFFICIAL IMAGEMAGICK LIBS (optional - skip if version not available) find_git_repo "imagemagick/imagemagick" "1" "T" if build "magick-libs" "$version"; then if [[ ! -d "$packages/deb-files" ]]; then mkdir -p "$packages/deb-files" fi cd "$packages/deb-files" || exit 1 if curl -LsSo "magick-libs-$version.rpm" "https://imagemagick.org/archive/linux/CentOS/x86_64/ImageMagick-libs-$version.x86_64.rpm" 2>/dev/null; then execute ${SUDO:-} alien -d ./*.rpm || warn "alien conversion failed, continuing..." execute ${SUDO:-} dpkg -i ./*.deb || warn "dpkg install failed, continuing..." build_done "magick-libs" "$version" else warn "magick-libs $version not available for download, skipping (will build from source)" fi fi case "$VER" in 20.04|22.04|23.04|23.10) version="2.4.6" ;; 11|12|24.04) version="2.4.7" ;; esac if build "libtool" "$version"; then download "$GNU_MIRROR/libtool/libtool-$version.tar.xz" execute ./configure --prefix="$workspace" --with-pic M4="$workspace/bin/m4" execute make "-j$cpu_threads" execute make install build_done "libtool" "$version" fi gnu_repo "https://pkgconfig.freedesktop.org/releases/" if build "pkg-config" "$version"; then download "https://pkgconfig.freedesktop.org/releases/pkg-config-$version.tar.gz" execute autoconf execute ./configure --prefix="$workspace" \ --with-internal-glib \ --with-pc-path="$PKG_CONFIG_PATH" \ CFLAGS="$CFLAGS -I$workspace/include" \ LDFLAGS="$LDFLAGS -L$workspace/lib64 -L$workspace/lib" execute make "-j$cpu_threads" execute make install build_done "pkg-config" "$version" fi find_git_repo "libsdl-org/libtiff" "1" "T" if build "libtiff" "$version"; then download "https://codeload.github.com/libsdl-org/libtiff/tar.gz/refs/tags/v$version" "libtiff-$version.tar.gz" # libsdl-org/libtiff tag archives are GitHub snapshots, not "make dist" # tarballs. They may miss autotools aux files and pre-generated outputs. # Bootstrap offline with explicit autotools steps (no autogen.sh, no autoreconf). mkdir -p config [[ -f config/config.guess ]] || cp -f /usr/share/misc/config.guess config/config.guess 2>/dev/null || true [[ -f config/config.sub ]] || cp -f /usr/share/misc/config.sub config/config.sub 2>/dev/null || true if [[ ! -f config/install-sh ]]; then isrc=$(find /usr/share -maxdepth 2 -path '/usr/share/automake-*/install-sh' -print -quit 2>/dev/null || true) [[ -n "$isrc" ]] && cp -f "$isrc" config/install-sh fi chmod +x config/config.guess config/config.sub config/install-sh 2>/dev/null || true if [[ ! -f ./configure || ! -f ./Makefile.in || ! -f ./config/config.h.in ]] || \ ( [[ -f ./configure ]] && grep -q 'LT_INIT(' ./configure ); then execute libtoolize --force --copy || warn "libtoolize failed, continuing..." execute aclocal -I m4 execute autoheader execute autoconf execute automake --add-missing --force-missing --copy fi execute ./configure --prefix="$workspace" --enable-cxx --disable-docs execute make "-j$cpu_threads" execute make install # libtiff's libtool archives from GitHub snapshots can embed absolute # system libjpeg.la paths, which break downstream libtool links. # Strip those .la references so ImageMagick links cleanly. for f in "$workspace"/lib*/libtiff*.la "$workspace"/lib*/x86_64-linux-gnu/libtiff*.la; do [[ -f "$f" ]] || continue sed -i 's@ /usr/lib[^ ]*/libjpeg\.la@@g; s@ /usr/lib[^ ]*/libjpeg-turbo[^ ]*\.la@@g' "$f" done build_done "libtiff" "$version" fi if [[ "$IMAGEMAGICK_ALLOCATOR" == "tcmalloc" ]]; then find_git_repo "gperftools/gperftools" "1" "T" version="${version#gperftools-}" if build "gperftools" "$version"; then download "https://github.com/gperftools/gperftools/releases/download/gperftools-$version/gperftools-$version.tar.gz" "gperftools-$version.tar.gz" ( execute autoreconf -fi mkdir -p build cd build || exit 1 execute ../configure --prefix="$workspace" --with-pic --with-tcmalloc-pagesize=256 \ CFLAGS="$CFLAGS -DNOLIBTOOL" execute make "-j$cpu_threads" execute make install ) build_done "gperftools" "$version" fi fi git_caller "https://github.com/libjpeg-turbo/libjpeg-turbo.git" "jpeg-turbo-git" if build "$repo_name" "${version//\$ /}"; then execute cmake -S . -B build \ -DCMAKE_INSTALL_PREFIX="$workspace" \ -DCMAKE_BUILD_TYPE=Release \ -DCMAKE_C_FLAGS="$CFLAGS" \ -DCMAKE_CXX_FLAGS="$CXXFLAGS" \ -DCMAKE_EXE_LINKER_FLAGS="$LDFLAGS" \ -DCMAKE_SHARED_LINKER_FLAGS="$LDFLAGS" \ -DENABLE_{SHARED,STATIC}=ON \ -G Ninja -Wno-dev execute ninja "-j$cpu_threads" -C build execute ninja -C build install build_done "$repo_name" "$version" fi git_caller "https://github.com/imageMagick/libfpx.git" "libfpx-git" if build "$repo_name" "$version"; then execute autoreconf -fi execute ./configure --prefix="$workspace" --with-pic execute make "-j$cpu_threads" execute make install build_done "$repo_name" "$version" fi find_git_repo "ArtifexSoftware/ghostpdl-downloads" "1" "T" find_ghostscript_version "$version" if build "ghostscript" "$version"; then download "$gscript_url" "ghostscript-$version.tar.xz" execute ./autogen.sh execute ./configure --prefix="$workspace" --with-libiconv=native execute make "-j$cpu_threads" execute make install build_done "ghostscript" "$version" fi find_git_repo "pnggroup/libpng" "1" "T" # User-pinned stable version (do not auto-detect for libpng) version="1.6.53" if build "libpng" "$version"; then download "https://github.com/pnggroup/libpng/archive/refs/tags/v1.6.53.tar.gz" "libpng-1.6.53.tar.gz" # GitHub tag archives aren't "make dist" tarballs; run an offline autotools bootstrap # to ensure libtool/automake macros are available. execute libtoolize --force --copy || warn "libtoolize failed, continuing..." # Older snapshots sometimes omit libtool m4 macros; seed them from system. mkdir -p m4 if [[ ! -f m4/libtool.m4 ]]; then cp -f /usr/share/aclocal/libtool.m4 /usr/share/aclocal/lt*.m4 m4/ 2>/dev/null || true fi execute aclocal -I m4 execute autoheader execute autoconf execute automake --add-missing --force-missing --copy execute ./configure --prefix="$workspace" --enable-hardware-optimizations=yes --with-pic execute make "-j$cpu_threads" execute make install build_done "libpng" "$version" fi git_caller "https://chromium.googlesource.com/webm/libwebp" "libwebp-git" if build "$repo_name" "${version//\$ /}"; then # libwebp is built via CMake below; autoreconf is unnecessary for git checkouts # and can fail due to missing libtool macros. Skip autotools bootstrap here. execute cmake -B build \ -DCMAKE_INSTALL_PREFIX="$workspace" \ -DCMAKE_BUILD_TYPE=Release \ -DCMAKE_C_FLAGS="$CFLAGS" \ -DCMAKE_CXX_FLAGS="$CXXFLAGS" \ -DCMAKE_EXE_LINKER_FLAGS="$LDFLAGS" \ -DCMAKE_SHARED_LINKER_FLAGS="$LDFLAGS" \ -DBUILD_SHARED_LIBS=ON \ -DZLIB_INCLUDE_DIR="$workspace/include" \ -DWEBP_BUILD_{CWEBP,DWEBP}=ON \ -DWEBP_BUILD_{ANIM_UTILS,EXTRAS,VWEBP}=OFF \ -DWEBP_ENABLE_SWAP_16BIT_CSP=OFF \ -DWEBP_LINK_STATIC=ON \ -G Ninja -Wno-dev execute ninja "-j$cpu_threads" -C build execute ninja -C build install build_done "$repo_name" "$version" fi find_git_repo "7950" "2" version="${version#VER-}" version1="${version//-/.}" if build "freetype" "$version1"; then download "https://gitlab.freedesktop.org/freetype/freetype/-/archive/VER-$version/freetype-VER-$version.tar.bz2" "freetype-$version1.tar.bz2" extracmds=("-D"{harfbuzz,png,bzip2,brotli,zlib,tests}"=disabled") execute meson setup build --prefix="$workspace" --buildtype=release --default-library=static --strip "${extracmds[@]}" execute ninja "-j$cpu_threads" -C build execute ninja -C build install build_done "freetype" "$version1" fi find_git_repo "1665" "3" "T" if build "libxml2" "$version"; then download "https://gitlab.gnome.org/GNOME/libxml2/-/archive/v$version/libxml2-v$version.tar.bz2" "libxml2-$version.tar.bz2" if command -v python3.11-config &>/dev/null; then PYTHON_CFLAGS=$(python3.11-config --cflags) PYTHON_LIBS=$(python3.11-config --ldflags) else PYTHON_CFLAGS=$(python3.12-config --cflags) PYTHON_LIBS=$(python3.12-config --ldflags) fi export PYTHON_CFLAGS PYTHON_LIBS execute cmake -B build \ -DCMAKE_INSTALL_PREFIX="$workspace" \ -DCMAKE_BUILD_TYPE=Release \ -DCMAKE_C_FLAGS="$CFLAGS" \ -DCMAKE_CXX_FLAGS="$CXXFLAGS" \ -DCMAKE_EXE_LINKER_FLAGS="$LDFLAGS" \ -DCMAKE_SHARED_LINKER_FLAGS="$LDFLAGS" \ -DBUILD_SHARED_LIBS=OFF \ -G Ninja -Wno-dev execute ninja "-j$cpu_threads" -C build execute ninja -C build install build_done "libxml2" "$version" fi find_git_repo "890" "2" fc_dir="$packages/fontconfig-$version" if build "fontconfig" "$version"; then download "https://gitlab.freedesktop.org/fontconfig/fontconfig/-/archive/$version/fontconfig-$version.tar.bz2" execute ./autogen.sh --noconf ( # fontconfig's static + libxml2 builds may need explicit libxml2 static deps (zlib, lzma, etc.). # Prefer pkg-config to avoid hardcoding system paths. xml_cflags="$("$workspace/bin/pkg-config" --cflags libxml-2.0 2>/dev/null || true)" xml_libs="$("$workspace/bin/pkg-config" --libs --static libxml-2.0 2>/dev/null || true)" execute ./configure --prefix="$workspace" \ --disable-docbook \ --disable-docs \ --disable-shared \ --disable-nls \ --enable-iconv \ --enable-libxml2 \ --enable-static \ --with-arch="$(uname -m)" \ --with-libiconv-prefix=/usr \ --with-pic \ CFLAGS="$CFLAGS $xml_cflags" \ LDFLAGS="$LDFLAGS $xml_libs" execute make "-j$cpu_threads" execute make install ) build_done "fontconfig" "$version" fi git_caller "https://github.com/fribidi/c2man.git" "c2man-git" if build "$repo_name" "${version//\$ /}"; then execute ./Configure -desO \ -D bin="$workspace/bin" \ -D cc="/usr/bin/cc" \ -D d_gnu="/usr/lib/x86_64-linux-gnu" \ -D gcc="/usr/bin/gcc" \ -D installmansrc="$workspace/share/man" \ -D ldflags="$LDFLAGS" \ -D libpth="/usr/lib64 /usr/lib /lib64 /lib" \ -D locincpth="$workspace/include /usr/local/include" \ -D loclibpth="$workspace/lib /usr/local/lib64 /usr/local/lib" \ -D osname="$OS" \ -D prefix="$workspace" \ -D privlib="$workspace/lib/c2man" \ -D privlibexp="$workspace/lib/c2man" execute make depend execute make "-j$cpu_threads" execute ${SUDO:-} make install build_done "$repo_name" "$version" fi find_git_repo "fribidi/fribidi" "1" "T" if build "fribidi" "$version"; then download "https://github.com/fribidi/fribidi/archive/refs/tags/v$version.tar.gz" "fribidi-$version.tar.gz" extracmds=("-D"{docs,tests}"=false") execute meson setup build --prefix="$workspace" \ --buildtype=release \ --default-library=static \ --strip \ "${extracmds[@]}" execute ninja "-j$cpu_threads" -C build execute ninja -C build install build_done "fribidi" "$version" fi find_git_repo "harfbuzz/harfbuzz" "1" "T" if build "harfbuzz" "$version"; then download "https://github.com/harfbuzz/harfbuzz/archive/refs/tags/$version.tar.gz" "harfbuzz-$version.tar.gz" extracmds=("-D"{benchmark,cairo,docs,glib,gobject,icu,introspection,tests}"=disabled" "-Dfreetype=enabled") execute meson setup build --prefix="$workspace" \ --buildtype=release \ --default-library=both \ --strip \ "${extracmds[@]}" execute ninja "-j$cpu_threads" -C build execute ninja -C build install build_done "harfbuzz" "$version" fi find_git_repo "host-oman/libraqm" "1" "T" if build "raqm" "$version"; then download "https://codeload.github.com/host-oman/libraqm/tar.gz/refs/tags/v$version" "raqm-$version.tar.gz" execute meson setup build --prefix="$workspace" \ --includedir="$workspace/include" \ --buildtype=release \ --default-library=static \ --strip \ -Ddocs="false" execute ninja "-j$cpu_threads" -C build execute ninja -C build install build_done "raqm" "$version" fi if [[ "$IMAGEMAGICK_ALLOCATOR" == "jemalloc" ]]; then find_git_repo "jemalloc/jemalloc" "1" "T" if build "jemalloc" "$version"; then download "https://github.com/jemalloc/jemalloc/archive/refs/tags/$version.tar.gz" "jemalloc-$version.tar.gz" execute ./autogen.sh execute ./configure --prefix="$workspace" \ --disable-debug \ --disable-doc \ --disable-fill \ --disable-log \ --disable-prof \ --disable-stats \ --enable-autogen \ --enable-static \ --enable-xmalloc \ CFLAGS="$CFLAGS" execute make "-j$cpu_threads" execute make install build_done "jemalloc" "$version" fi fi git_caller "https://github.com/KhronosGroup/OpenCL-SDK.git" "opencl-sdk-git" "recurse" if build "$repo_name" "${version//\$ /}"; then execute cmake \ -S . \ -B build \ -DCMAKE_INSTALL_PREFIX="$workspace" \ -DCMAKE_BUILD_TYPE=Release \ -DCMAKE_POSITION_INDEPENDENT_CODE="true" \ -DBUILD_SHARED_LIBS=ON \ -DBUILD_TESTING=OFF \ -DBUILD_DOCS=OFF \ -DBUILD_EXAMPLES=OFF \ -DOPENCL_SDK_BUILD_SAMPLES=OFF \ -DOPENCL_SDK_TEST_SAMPLES=OFF \ -DCMAKE_C_FLAGS="$CFLAGS" \ -DCMAKE_CXX_FLAGS="$CXXFLAGS" \ -DCMAKE_EXE_LINKER_FLAGS="$LDFLAGS" \ -DCMAKE_SHARED_LINKER_FLAGS="$LDFLAGS" \ -DOPENCL_HEADERS_BUILD_CXX_TESTS=OFF \ -DOPENCL_ICD_LOADER_BUILD_SHARED_LIBS=ON \ -DOPENCL_SDK_BUILD_OPENGL_SAMPLES=OFF \ -DOPENCL_SDK_BUILD_SAMPLES=OFF \ -DOPENCL_SDK_TEST_SAMPLES=OFF \ -DTHREADS_PREFER_PTHREAD_FLAG=ON \ -G Ninja -Wno-dev execute ninja "-j$cpu_threads" -C build execute ninja -C build install build_done "$repo_name" "$version" fi find_git_repo "uclouvain/openjpeg" "1" "T" if build "openjpeg" "$version"; then download "https://codeload.github.com/uclouvain/openjpeg/tar.gz/refs/tags/v$version" "openjpeg-$version.tar.gz" execute cmake -B build \ -DCMAKE_INSTALL_PREFIX="$workspace" \ -DCMAKE_BUILD_TYPE=Release \ -DCMAKE_C_FLAGS="$CFLAGS" \ -DCMAKE_CXX_FLAGS="$CXXFLAGS" \ -DCMAKE_EXE_LINKER_FLAGS="$LDFLAGS" \ -DCMAKE_SHARED_LINKER_FLAGS="$LDFLAGS" \ -DCMAKE_POSITION_INDEPENDENT_CODE="true" \ -DBUILD_{SHARED_LIBS,THIRDPARTY}=ON \ -DBUILD_TESTING=OFF \ -G Ninja -Wno-dev execute ninja "-j$cpu_threads" -C build execute ninja -C build install build_done "openjpeg" "$version" fi find_git_repo "mm2/Little-CMS" "1" "T" version="${version//lcms/}" if build "lcms2" "$version"; then download "https://github.com/mm2/Little-CMS/archive/refs/tags/lcms$version.tar.gz" "lcms2-$version.tar.gz" execute ./autogen.sh execute ./configure --prefix="$workspace" --with-pic --with-threaded execute make "-j$cpu_threads" execute make install build_done "lcms2" "$version" fi # Download and install fonts download_fonts # Determine whether of not to install autotrace set_autotrace echo box_out_banner_magick() { input_char=$(echo "$@" | wc -c) line=$(for i in $(seq 0 $input_char); do printf "-"; done) tput bold line="$(tput setaf 3)$line" space="${line//-/ }" echo " $line" printf "|" ; echo -n "$space" ; printf "%s\n" "|"; printf "| " ;tput setaf 4; echo -n "$@"; tput setaf 3 ; printf "%s\n" " |"; printf "|" ; echo -n "$space" ; printf "%s\n" "|"; echo " $line" tput sgr 0 } box_out_banner_magick "Build ImageMagick" find_git_repo "ImageMagick/ImageMagick" "1" "T" if build "imagemagick" "$version"; then download "https://imagemagick.org/archive/releases/ImageMagick-$version.tar.lz" "imagemagick-$version.tar.lz" # Ensure no stale libtiff .la files reference missing system libjpeg.la for f in "$workspace"/lib*/libtiff*.la "$workspace"/lib*/x86_64-linux-gnu/libtiff*.la; do [[ -f "$f" ]] || continue sed -i 's@ /usr/lib[^ ]*/libjpeg\.la@@g; s@ /usr/lib[^ ]*/libjpeg-turbo[^ ]*\.la@@g' "$f" done execute autoreconf -fi mkdir build; cd build || exit 1 # Choose a single allocator to avoid conflicting malloc implementations. malloc_opts=() case "${IMAGEMAGICK_ALLOCATOR:-jemalloc}" in jemalloc) malloc_opts+=(--with-jemalloc) ;; tcmalloc) malloc_opts+=(--with-tcmalloc) ;; dmalloc) malloc_opts+=(--with-dmalloc --enable-ccmalloc) ;; system) ;; *) warn "Unknown IMAGEMAGICK_ALLOCATOR=${IMAGEMAGICK_ALLOCATOR}; using jemalloc"; malloc_opts+=(--with-jemalloc) ;; esac execute ../configure --prefix=/usr/local \ --enable-delegate-build \ --enable-hdri \ --enable-hugepages \ --enable-legacy-support \ --enable-opencl \ --with-fontpath=/usr/share/fonts/truetype \ --with-dejavu-font-dir=/usr/share/fonts/truetype/dejavu \ --with-gs-font-dir=/usr/share/fonts/ghostscript \ --with-urw-base35-font-dir=/usr/share/fonts/type1/urw-base35 \ --with-fpx \ --with-gslib \ --with-gvc \ --with-heic \ --with-modules \ --with-pic \ --with-pkgconfigdir="/usr/local/lib/pkgconfig" \ --with-png \ --with-quantum-depth=16 \ --with-rsvg \ --with-utilities \ $autotrace_switch \ "${malloc_opts[@]}" \ CFLAGS="$CFLAGS -DCL_TARGET_OPENCL_VERSION=300" \ CXXFLAGS="$CXXFLAGS -DCL_TARGET_OPENCL_VERSION=300" \ CPPFLAGS="$CPPFLAGS -I$workspace/include/CL -I/usr/include" \ PKG_CONFIG="$workspace/bin/pkg-config" execute make "-j$cpu_threads" execute ${SUDO:-} make install fi # LDCONFIG MUST BE RUN NEXT TO UPDATE FILE CHANGES OR THE MAGICK COMMAND WILL NOT WORK ${SUDO:-} ldconfig # SHOW THE NEWLY INSTALLED MAGICK VERSION show_version # PROMPT THE USER TO CLEAN UP THE BUILD FILES cleanup # SHOW EXIT MESSAGE exit_fn