#!/bin/sh

# ccache-update-symlinks - keep ccache symlinks in sync with installed
# versions of compatible compilers
# Copyright (C) 2021 by Jim Klimov

# This script can be used standalone, or as an SMF method script
[ -s /lib/svc/share/smf_include.sh ] && . /lib/svc/share/smf_include.sh

SMF_FMRI_BASE=""
if [ -n "$SMF_FMRI" ]; then
    SMF_FMRI_BASE="`echo "$SMF_FMRI" | sed 's|:[^:]*$||'`"
fi

getproparg() {
    [ -n "$SMF_FMRI" ] || return
    val="`svcprop -p "$1" "$SMF_FMRI" 2>/dev/null`" || val="`svcprop -p "$1" "$SMF_FMRI_BASE" 2>/dev/null`"
    [ -n "$val" ] && echo "$val"
}

# Caller may `export DEBUG=yes` to trace the decisions in the script
# Caller may `export DRYRUN=echo` to avoid actual link manipulations

# This is where symlinks named identically to compiler binaries are present.
# These symlinks point to the ccache binary however, and it knows how to
# wrap the real thing if called (gcc, clang, maybe icc... and compatibles).
# Users who want build stuff with a speedup add this to front of their PATH,
# and their PATH must later contain the location of the real tool's binary.
# While it is not generally a problem for common /usr/bin it may be for
# not-exposed compilers.
# Also note that even if the caller has this location in their PATH, they
# can turn off effects of ccache without changing runtime PATHs just by
# `export CCACHE_DISABLE=1` before a build.
LINKDIR="/usr/lib/ccache"
SYMLINK="../../bin/ccache"

# These are the filenames we manage symlinks for, either exact or suffixed
# by a dash and number, and optionally add values from SMF instance:
TOOLS="
    gcc g++ gcpp
    clang clang++ clang-cpp
    c++ cc cpp
    i386-pc-solaris2.11-c++ i386-pc-solaris2.11-cpp i386-pc-solaris2.11-g++ i386-pc-solaris2.11-gcc
    `getproparg ccache-update-symlinks/TOOLS_ADD`
"
# Rearrange for easier parsing below
TOOLS="`echo $TOOLS | tr ' ' '\n' | sort -n | uniq`"

PATH_ADD="`getproparg ccache-update-symlinks/PATH_ADD`"
if [ -n "$PATH_ADD" ]; then
    PATH="$PATH:$PATH_ADD"
fi

cd "$LINKDIR" || exit

# TODO: Option in the SMF service to enable additions to PATH for other special
# compilers? It would help everyone if that particular system's admin rather
# symlinked the custom compiler to common /usr/bin/ instead.
# Set value from SMF instance if present there
[ -n "${ALLOW_DELETE-}" ] || ALLOW_DELETE="`getproparg ccache-update-symlinks/ALLOW_DELETE`"
[ "${ALLOW_DELETE-}" = true -o "${ALLOW_DELETE-}" = false ] || { echo "Defaulting ALLOW_DELETE=false" >&2; ALLOW_DELETE=false; }

# Begin work
echo "Trawling PATH='$PATH' for TOOLS:" $TOOLS >&2

NAMES=""
for T in $TOOLS ; do
    for P in `echo "\"$PATH\"" | sed 's,:," ",g'` ; do
        P="`echo "$P" | sed 's|^"\(.*\)"$|\1|'`"
        [ "$P" = "$LINKDIR" ] && continue
        for F in `ls -1d "$P/$T"* 2>/dev/null` ; do
            [ -f "$F" ] && [ -x "$F" ] || continue
            # F represents an existing filename in PATH component P
            # that starts with tool name T
            B="`basename "$F"`"
            case "$B" in
                ccache*) ;; # Quiet skip
                "$T"|"$T"-[0123456789]*)
                    # We have either the default tool name, or one suffixed
                    # by the version number (may be dotted)
                    # Assumes no whitespace in compiler filenames
                    if [ -n "$NAMES" ]; then
                        NAMES="$NAMES $B"
                    else
                        NAMES="$B"
                    fi
                    [ "$DEBUG" = yes ] && echo "ADDED:   $B ($F) ($T)" >&2
                    ;;
                *)  # Skip other tools with the same prefix
                    # Note that while looking at T=clang we skip hits of clang++*
                    [ "$DEBUG" = yes ] && echo "SKIPPED: $B ($F) ($T)" >&2
                    ;;
            esac
        done
    done
done

# Dedup
NAMES="`echo "$NAMES" | tr ' ' '\n' | sort -n | uniq`"
[ "$DEBUG" = yes ] && echo "=== PATH:" && echo "$NAMES"

LINKS="`ls -1 "$LINKDIR" | sort -n | uniq`"
[ "$DEBUG" = yes ] && echo "=== LINKDIR:" && echo "$LINKS"

if [ "$NAMES" = "$LINKS" ]; then
    echo "CCACHE symlinks are already up to date for current PATH='$PATH'" >&2
    exit 0
fi

# Link missing compilers to get wrapped by ccache for its consumers
# Note: originally used `fgrep` but it misfired for substring matches
# finding e.g. "gcc" or "gcc-4" in "gcc-4.9"; not a full-string match
for N in $NAMES ; do
    for L in $LINKS ; do
        if [ "$N" = "$L" ]; then
            continue 2
        fi
    done
    echo "ADDING LINK: $N" >&2
    $DRYRUN ln -sf "$SYMLINK" "$LINKDIR/$N"
done

# (optionally) Remove links to compilers that are not in PATH
$ALLOW_DELETE || DRYRUN="echo Would exec:"
for L in $LINKS ; do
    for N in $NAMES ; do
        if [ "$N" = "$L" ]; then
            continue 2
        fi
    done
    echo "REMOVE LINK: $L" >&2
    $DRYRUN rm -f "$LINKDIR/$L"
done

echo "CCACHE symlinks checked/rearranged to be up to date for current PATH='$PATH'" >&2
exit 0
