#!/bin/sh
#
#  Copyright (c) 2008-2026 Jonathan Schleifer <js@nil.im>
#
#  All rights reserved.
#
#  This program is free software: you can redistribute it and/or modify it
#  under the terms of the GNU Lesser General Public License version 3.0 only,
#  as published by the Free Software Foundation.
#
#  This program is distributed in the hope that it will be useful, but WITHOUT
#  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
#  FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
#  version 3.0 for more details.
#
#  You should have received a copy of the GNU Lesser General Public License
#  version 3.0 along with this program. If not, see
#  <https://www.gnu.org/licenses/>.
#

if test x"$(basename "$0")" != x"objfw-compile"; then
	OBJFW_CONFIG="$(basename "$0" | sed 's/-objfw-compile$//')-objfw-config"
else
	OBJFW_CONFIG="objfw-config"
fi

if ! which $OBJFW_CONFIG >/dev/null 2>&1; then
	echo "You need to have ObjFW and $OBJFW_CONFIG installed!"
	exit 1
fi

parse_packages() {
	packages=""

	while test x"$1" != "x"; do
		case "$1" in
		--package)
			shift
			packages="$packages --package $1"
			;;
		esac
		shift
	done
}
parse_packages "$@"

parse_static_libs() {
	static_libs="no"

	while test x"$1" != "x"; do
		case "$1" in
		--static-libs)
			static_libs="yes"
			break
			;;
		esac
		shift
	done
}
parse_static_libs "$@"

show_help() {
	cat >&2 <<__EOF__
Syntax: objfw-compile -o output [flags] source1.m source2.mm ...

    -o name         Specify the output name (not file name!)
    --arc           Use automatic reference counting
    --lib version   Compile a library (with the specified version) instead of
                    an application
    --plugin        Compile a plugin instead of an application
    --package name  Use the specified package
    --builddir dir  Place built objects into the specified directory
    --static-libs   Link ObjFW and packages statically
    -D*  -D *       Pass the specified define to the compiler
    -framework *    Pass the specified -framework argument to the linker
                    (macOS / iOS only)
    -f*             Pass the specified -f flag to the compiler and linker
    -F* -F *        Pass the specified -F flag to the linker (macOS / iOS only)
    -g*             Pass the specified -g flag to the compiler
    -I*  -I *       Pass the specified -I flag to the compiler
    -l*  -l *       Pass the specified -l flag to the linker
    -L*  -L *       Pass the specified -L flag to the linker
    -m*             Pass the specified -m flag to the compiler and linker
    -O*             Pass the specified -O flag to the compiler
    -pthread        Pass -pthread to the compiler and linker
    -std=*          Pass the specified -std= flag to the compiler
    -Wl,*           Pass the specified -Wl, flag to the linker
    -W*             Pass the specified -W flag to the compiler
    --help          Show this help
__EOF__
}

CPPFLAGS="$CPPFLAGS $($OBJFW_CONFIG $packages --cppflags)"
OBJC="$($OBJFW_CONFIG --objc)"
OBJCFLAGS="$OBJCFLAGS $($OBJFW_CONFIG $packages --objcflags) -Wall -g"
if test x"$static_libs" = x"yes"; then
	LIBS="$LIBS $($OBJFW_CONFIG $packages --static-libs)"
else
	LIBS="$LIBS $($OBJFW_CONFIG $packages --libs)"
fi
LDFLAGS="$LDFLAGS $($OBJFW_CONFIG $packages --ldflags --rpath)"

if test x"$1" = "x"; then
	show_help
	exit 1
fi

status_compiling() {
	printf "\033[K\033[0;33mCompiling \033[1;33m%s\033[0;33m...\033[0m\r" \
		"$1"
}
status_compiled() {
	printf "\033[K\033[0;32mSuccessfully compiled \033[1;32m%s\033[0;32m." \
		"$1"
	printf "\033[0m\n"
}
status_compile_failed() {
	printf "\033[K\033[0;31mFailed to compile \033[1;31m%s\033[0;31m!" "$1"
	printf "\033[0m\n"
	exit $2
}
status_linking() {
	printf "\033[K\033[0;33mLinking \033[1;33m%s\033[0;33m...\033[0m\r" "$1"
}
status_linked() {
	printf "\033[K\033[0;32mSuccessfully linked \033[1;32m%s\033[0;32m." \
		"$1"
	printf "\033[0m\n"
}
status_link_failed() {
	printf "\033[K\033[0;31mFailed to link \033[1;31m%s\033[0;31m!" "$1"
	printf "\033[0m\n"
	exit $2
}

srcs=""
out=""
objs=""
builddir=""
link="no"
link_stdcpp="no"
lib="no"
plugin="no"
static="no"
out_prefix=""
out_suffix=""

while test x"$1" != "x"; do
	case "$1" in
	-o|--out)
		shift
		out="$1"
		;;
	--lib)
		if test x"$plugin" = x"yes"; then
			echo "You can't use --lib and --plugin!"
			exit 1
		fi

		shift

		if ! echo "$1" | grep "^[0-9]\+\.[0-9]\+$" >/dev/null; then
			echo "$1 is not a valid library version!"
			exit 1
		fi

		export LIB_MAJOR="${1%.*}"
		export LIB_MINOR="${1#*.}"

		lib="yes"
		OBJCFLAGS="$OBJCFLAGS $($OBJFW_CONFIG --lib-cflags)"
		out_prefix="$($OBJFW_CONFIG --lib-prefix)"
		out_suffix="$($OBJFW_CONFIG --lib-suffix)"
		;;
	--package)
		# Already included into the flags.
		shift
		;;
	--plugin)
		if test x"$lib" = x"yes"; then
			echo "You can't use --lib and --plugin!"
			exit 1
		fi

		plugin="yes"
		OBJCFLAGS="$OBJCFLAGS $($OBJFW_CONFIG --plugin-cflags)"
		LDFLAGS="$LDFLAGS $($OBJFW_CONFIG --plugin-ldflags)"
		out_suffix="$($OBJFW_CONFIG --plugin-suffix)"
		;;
	--arc)
		OBJCFLAGS="$OBJCFLAGS $($OBJFW_CONFIG --arc)"
		;;
	--builddir)
		shift
		builddir="$1"
		;;
	--static-libs)
		# Already handled separately.
		;;
	-D)
		shift
		CPPFLAGS="$CPPFLAGS -D$1"
		;;
	-D*)
		CPPFLAGS="$CPPFLAGS $1"
		;;
	-framework)
		shift
		LIBS="$LIBS -framework $1"
		;;
	-f*)
		OBJCFLAGS="$OBJCFLAGS $1"
		LDFLAGS="$LDFLAGS $1"
		;;
	-F)
		shift
		LIBS="$LIBS -F$1"
		;;
	-F*)
		LIBS="$LIBS $1"
		;;
	-g*)
		OBJCFLAGS="$OBJCFLAGS $1"
		;;
	-I)
		shift
		CPPFLAGS="$CPPFLAGS -I$1"
		;;
	-I*)
		CPPFLAGS="$CPPFLAGS $1"
		;;
	-l)
		shift
		LIBS="$LIBS -l$1"
		;;
	-l*)
		LIBS="$LIBS $1"
		;;
	-L)
		shift
		LIBS="$LIBS -L$1"
		;;
	-L*)
		LIBS="$LIBS $1"
		;;
	-m*)
		OBJCFLAGS="$OBJCFLAGS $1"
		LDFLAGS="$LDFLAGS $1"
		;;
	-O*)
		OBJCFLAGS="$OBJCFLAGS $1"
		;;
	-pthread)
		OBJCFLAGS="$OBJCFLAGS $1"
		LDFLAGS="$LDFLAGS $1"
		;;
	-std=*)
		OBJCFLAGS="$OBJCFLAGS $1"
		;;
	-Wl,*)
		LDFLAGS="$LDFLAGS $1"
		;;
	-W*)
		OBJCFLAGS="$OBJCFLAGS $1"
		;;
	--help)
		show_help
		exit 0
		;;
	-*)
		echo "Unknown option: $1"
		exit 1
		;;
	*.m)
		srcs="$srcs $1"
		;;
	*.mm)
		srcs="$srcs $1"
		link_stdcpp="yes"
		;;
	*)
		echo "Only .m and .mm files can be compiled!" 1>&2
		exit 1
		;;
	esac

	shift
done

if test x"$out" = x""; then
	echo "No output name specified! Use -o or --out!"
	exit 1
fi

case "$builddir" in
"")
	;;
*/)
	;;
*)
	builddir="$builddir/"
	;;
esac

for i in $srcs; do
	case $i in
	*.m)
		if test x"$lib" = x"yes"; then
			obj="$builddir${i%.m}.lib.o"
		elif test x"$plugin" = x"yes"; then
			obj="$builddir${i%.m}.plugin.o"
		else
			obj="$builddir${i%.m}.o"
		fi
		;;
	*.mm)
		if test x"$lib" = x"yes"; then
			obj="$builddir${i%.mm}.lib.o"
		elif test x"$plugin" = x"yes"; then
			obj="$builddir${i%.mm}.plugin.o"
		else
			obj="$builddir${i%.mm}.o"
		fi
		;;
	esac
	objs="$objs $obj"
	build="no"

	if test ! -f "$obj" -o "$i" -nt "$obj"; then
		build="yes"
	else
		deps=$($OBJC -E -M $CPPFLAGS $OBJCFLAGS $i |
			sed -e 's/.*: //' -e 's/\\//g')
		for dep in $deps; do
			test "$dep" -nt $obj && build="yes"
		done
	fi

	if test x"$build" = x"yes"; then
		link="yes"
		status_compiling $i
		mkdir -p "$(dirname $obj)" || status_compile_failed $i $?
		$OBJC $CPPFLAGS $OBJCFLAGS -c -o $obj $i || \
			status_compile_failed $i $?
		status_compiled $i
	fi
done

test x"$lib" = x"no" -a x"$plugin" = x"no" && \
	out_suffix="$($OBJFW_CONFIG --prog-suffix)"

test x"$link_stdcpp" = x"yes" && LIBS="$LIBS -lstdc++"

if test x"$lib" = x"yes"; then
	export SHARED_LIB="$out_prefix$out$out_suffix"
	LDFLAGS="$LDFLAGS $($OBJFW_CONFIG --lib-ldflags)"
fi

if test ! -f "$out_prefix$out$out_suffix" -o x"$link" = x"yes"; then
	status_linking $out_prefix$out$out_suffix
	$OBJC -o $out_prefix$out$out_suffix $objs $LIBS $LDFLAGS || \
		status_link_failed $out $?
	status_linked $out_prefix$out$out_suffix
fi
