# GNUMake snippet for java compilation and execution
#
# SPDX-License-Identifier: GPL-3.0-or-later
# SPDX-FileCopyrightText: 2024-2025 Jonas Smedegaard <dr@jones.dk>
#
# Setup:
# In main Makefile...
#  * set variable JAVA_PROJECTMODULES, or PROJECT for one simply named
#  * set variable JAVA_MAINCLASSES with at least one main()-method class,
#    where the first one listed will be used by default
#  * set variables JAVA_CLASSPATHS JAVA_MODULEPATHS JAVA_MODULESOURCEPATH
#    JAVA_ROOT JAVA_EXTRACLASSES JAVA_MODULES JAR_FILE
#    if needed,
#  * include this make snippet
#
# Variables may add suffix _$(PROJECTMODULE), in full or "host-only",
# e.g. JAVA_MODULES_org.example.mydemo or JAVA_MODULES_mydemo.

# resolve Java version
JAVA_MAJOR_VERSION = $(shell $(JAVA) -version 2>&1 \
 | grep -Pom1 '"\K\d+' )

# defaults overridable at make invocation
JAVA_ROOT ?= src/main/java
JAVAC ?= javac
JAVA ?= java
JAR ?= jar

# check javadoc only with JDK 23+ supporting Markdown-flavored javadoc
ifeq ($(shell expr $(JAVA_MAJOR_VERSION) \>= 23), 1)
JAVACFLAGS ?= -Xlint -Xdoclint -implicit:none
else
JAVACFLAGS ?= -Xlint -implicit:none
endif

# account for one annoyingly popular class of non-POSIX system
_java_path_separator := $(if $(filter Windows%,$($(shell uname))),;,:)
_java_module_separator = ,

# expansions optionally stem- or last-word-of-stem-based
# first try fully suffixed, then "host"-suffixed, then bare variable
_java_projvar = $(strip $(foreach v,$1,$(or \
 $(if $(or $2,$*),$(or \
  $(value $v_$(or $2,$*)),\
  $(value $v_$(lastword $(subst ., $(),$(or $2,$*)))))),\
 $(value $v))))
_java_root = $(call _java_projvar,JAVA_ROOT)
_java_flag = $(eval x = $(call _java_projvar,$2))$(strip $(if $4$x$5,\
 --$1 $(subst $() ,$(value _java_$3_separator),$(strip $4 $x $5))))
_java_common_flags = $(strip \
 $(call _java_flag,class-path,JAVA_CLASSPATHS,path) \
 $(call _java_flag,module-path,JAVA_MODULEPATHS,path) \
 $(call _java_flag,add-modules,JAVA_MODULES,module))

# projectmodules with default main function
_java_mainclassmodules = $(strip \
 $(foreach p,$(JAVA_PROJECTMODULES),\
  $(if $(call _java_projvar,JAVA_MAINCLASSES,$p),$p)))

# projectmodules with non-default main function expanded, dot-appended
_java_extramainclassmodules = $(strip \
 $(foreach p,$(JAVA_PROJECTMODULES),\
  $(eval classes = $(call _java_projvar,JAVA_MAINCLASSES,$p))\
  $(foreach c,$(filter-out $(firstword $(classes)),$(classes)),\
   $p.$(subst /,.,$c))))

# TODO: formally define a builddir, and drop that on clean
#clean::
#	rm -f $(CLASSES)

$(JAVA_PROJECTMODULES:%=build-%): build-%:
	rm -rf mods/$*
	$(JAVAC) $(strip \
	$(call _java_flag,module-source-path,JAVA_MODULESOURCEPATH) \
	$(_java_common_flags) $(JAVACFLAGS) \
	$(call _java_projvar,JAVACFLAGS) \
	-d mods/$* \
	$(wildcard $(_java_root)/module-info.java) \
	$(foreach c,\
	$(call _java_projvar,JAVA_MAINCLASSES JAVA_EXTRACLASSES),\
	 $(patsubst %,$(_java_root)/%.java,\
	  $(subst .,/,\
	   $(if $(wildcard $(_java_root)/module-info.java),$*/))$c)))

$(JAVA_PROJECTMODULES:%=check-%): check-%:
	checkstyle -c _checkstyle/checks.xml $(strip \
	$(foreach c,\
	$(call _java_projvar,JAVA_MAINCLASSES JAVA_EXTRACLASSES),\
	 $(_java_root)/$(subst .,/,$*)/$c.java))

$(JAVA_PROJECTMODULES:%=jar-%): jar-%: build-%
	$(JAR) --create $(strip \
	--file $(or $(call _java_projvar,JAR_FILE),$*.jar) \
	$(addprefix --main-class $*.,\
	 $(firstword $(call _java_projvar,JAVA_MAINCLASSES))) \
	$(call _java_projvar,JARFLAGS) \
	-C mods/$* .)

$(addprefix runsrc-,$(_java_mainclassmodules)): runsrc-%:
	$(strip \
	$(call _java_projvar,JAVA_RUNTIME_ENV) \
	$(JAVA) \
	$(_java_common_flags) \
	$(call _java_projvar,JAVAFLAGS) \
	$(patsubst %,$(_java_root)/%.java,\
	 $(subst .,/,$(or \
	  $(_java_main_class),\
	  $(strip $(if $(wildcard $(_java_root)/module-info.java),$*/)\
	  )$(firstword $(call _java_projvar,JAVA_MAINCLASSES))))) \
	$(call _java_projvar,JAVA_RUNTIME_ARGS))

$(addprefix runclass-,$(_java_mainclassmodules)): runclass-%: build-%
	$(strip \
	$(eval JAVA_MODULEPATHS_$* = $(strip \
	 mods/$* $(call _java_projvar,JAVA_MODULEPATHS)))\
	$(call _java_projvar,JAVA_RUNTIME_ENV) \
	$(JAVA) \
	$(_java_common_flags) \
	$(call _java_projvar,JAVAFLAGS) \
	-m $*/$(or $(_java_main_class),\
	 $*.$(firstword $(call _java_projvar,JAVA_MAINCLASSES))) \
	$(call _java_projvar,JAVA_RUNTIME_ARGS))

$(addprefix runjar-,$(_java_mainclassmodules)): runjar-%: jar-%
	$(strip \
	$(call _java_projvar,JAVA_RUNTIME_ENV) \
	$(JAVA) \
	$(call _java_flag,class-path,JAVA_CLASSPATHS,path) \
	$(call _java_flag,module-path,JAVA_MODULEPATHS,path,\
	 $(or $(call _java_projvar,JAR_FILE),$*.jar)) \
	$(call _java_projvar,JAVAFLAGS) \
	--module $*$(_java_main_class:%=/%) \
	$(call _java_projvar,JAVA_RUNTIME_ARGS))

$(addprefix runsrc-,$(_java_extramainclassmodules)): runsrc-%:
	$(strip _java_main_class=$* \
	$(MAKE) runsrc-$(strip $(foreach p,$(JAVA_PROJECTMODULES),\
	 $(if $(filter $p%,$*),$p))))

$(addprefix runclass-,$(_java_extramainclassmodules)): runclass-%:
	$(strip _java_main_class=$* \
	$(MAKE) runclass-$(strip $(foreach p,$(JAVA_PROJECTMODULES),\
	 $(if $(filter $p%,$*),$p))))

$(addprefix runjar-,$(_java_extramainclassmodules)): runjar-%:
	$(strip _java_main_class=$* \
	$(MAKE) runjar-$(strip $(foreach p,$(JAVA_PROJECTMODULES),\
	 $(if $(filter $p%,$*),$p))))

.PHONY: \
 $(foreach p,$(JAVA_PROJECTMODULES),build-$p check-$p) \
 $(foreach p,$(_java_mainclassmodules) $(_java_extramainclassmodules),\
  runclass-$p runsrc-$p runjar-$p)