From bc187fb983c77db83b158197b5710796cb939db7 Mon Sep 17 00:00:00 2001 From: Michael Ernst Date: Sat, 10 Oct 2026 13:04:17 -0700 Subject: [PATCH 1/7] Find JAVA_HOME robustly in Makefiles * On macOS, use $(shell /usr/libexec/java_home); `$(/usr/libexec/java_home)` is a reference to an undefined make variable, so JAVA_HOME was empty. * Compute JAVA_HOME once, with ":=", and locate javac with a single shell command. * tests/Makefile.common sets JAVA_HOME before including Makefile.user, so the junit and junit5 Makefile.user files no longer duplicate the detection. A missing JDK is reported only when JAVA or JAVAC is expanded, so targets such as "clean" work without a JDK. * java/Makefile fails clearly if no JDK is found. --- java/Makefile | 20 +++++++------- tests/Makefile.common | 35 ++++++++++++++----------- tests/daikon-tests/junit/Makefile.user | 14 +--------- tests/daikon-tests/junit5/Makefile.user | 14 +--------- 4 files changed, 31 insertions(+), 52 deletions(-) diff --git a/java/Makefile b/java/Makefile index ce436e5d69..65dab35496 100644 --- a/java/Makefile +++ b/java/Makefile @@ -25,16 +25,16 @@ JAR ?= jar BuildJDKTool = BuildJDK -ifeq ($(shell uname), Darwin) - JAVA_HOME ?= $(shell /usr/libexec/java_home) -else -ifeq ($(shell which javac > /dev/null 2>&1 && echo found || echo nonexistent), found) - JAVA_HOME ?= $(shell readlink -f $(shell which javac) | sed "s:/bin/javac::") -else - $(info Failure: Here is the output of: which javac) - $(info $(shell which javac)) - $(error 'which javac' had exit status 1) -endif +# Use ":=" so that the shell commands run only once. +ifeq ($(origin JAVA_HOME), undefined) + ifeq ($(shell uname), Darwin) + JAVA_HOME := $(shell /usr/libexec/java_home) + else + JAVA_HOME := $(shell javac=$$(command -v javac) && readlink -f "$$javac" | sed 's:/bin/javac$$::') + endif + ifeq (${JAVA_HOME},) + $(error No JDK found; set JAVA_HOME or put javac on the PATH) + endif endif # The "JAVANN" variables mean "at least version NN", except JAVA8. diff --git a/tests/Makefile.common b/tests/Makefile.common index 38dde8e8cb..7013f200a2 100644 --- a/tests/Makefile.common +++ b/tests/Makefile.common @@ -149,6 +149,20 @@ ifndef COMMON_HERE $(error Test directory ${DAIKONDIR}/tests does not exist) endif +# JAVA_HOME is set before Makefile.user is included, so that Makefile.user can use or override it. +# Use ":=" so that the shell commands run only once. +ifeq ($(origin JAVA_HOME), undefined) + ifeq ($(shell uname), Darwin) + JAVA_HOME := $(shell /usr/libexec/java_home) + else + JAVA_HOME := $(shell javac=$$(command -v javac) && readlink -f "$$javac" | sed 's:/bin/javac$$::') + endif +endif +# CHECKED_JAVA_HOME is JAVA_HOME, but expanding it fails if no JDK was found. +# The check happens only when a variable that uses it is expanded, so targets +# that do not run Java (such as "clean") work without a JDK. +CHECKED_JAVA_HOME = $(or ${JAVA_HOME},$(error No JDK found; set JAVA_HOME)) + # Put user-specific changes in your own Makefile.user # (such as changes to PLUME_DIR or JAVAC or PATH). # Make will silently continue if the file does not exist. @@ -186,19 +200,8 @@ ECLAT_SCRATCH = $(SCRATCH_FOR_DAIKON_BASE)/$(PROJECT)_eclat # CP_PARENTS := cp -pf --parents CP_PARENTS := rsync -aR -ifeq ($(shell uname), Darwin) - JAVA_HOME ?= $(/usr/libexec/java_home) -else -ifeq ($(shell which javac > /dev/null 2>&1 && echo found || echo nonexistent), found) - JAVA_HOME ?= $(shell readlink -f $(shell which javac) | sed "s:/bin/javac::") -else - $(info Failure: Here is the output of: which javac) - $(info $(shell which javac)) - $(error 'which javac' had exit status 1) -endif -endif -JAVA := ${JAVA_HOME}/bin/java -JAVAC ?= ${JAVA_HOME}/bin/javac +JAVA = ${CHECKED_JAVA_HOME}/bin/java +JAVAC ?= ${CHECKED_JAVA_HOME}/bin/javac JUNIT_CLASSPATH ?= @@ -209,7 +212,7 @@ DAIKON_CLASSPATH := ${DAIKONDIR}/java:${DAIKONDIR}/java/lib/*:${DAIKONDIR}/daiko export DAIKON_TESTS_CLASSPATH := .:$(JAVALIB_CLASSPATH):$(SCRATCH_FOR_DAIKON):$(JUNIT_CLASSPATH):$(DAIKON_CLASSPATH) # Dynamic instrumentation based java front end command. -CHICORY := $(JAVA) -ea -cp "$(DAIKON_TESTS_CLASSPATH)" daikon.Chicory +CHICORY = $(JAVA) -ea -cp "$(DAIKON_TESTS_CLASSPATH)" daikon.Chicory CHICORY_EXEC := ${DAIKONDIR}/java/daikon/Chicory.class # As of JDK 1.3 (and 1.4, I think), don't use "-server" here. @@ -302,12 +305,12 @@ endif # If specified, setup for the old declaration format ifeq (old,${DECL_FORMAT}) - CHICORY := $(CHICORY) --new_decl_format=false + CHICORY += --new_decl_format=false endif # "make PROFILE_JAVA=1 " to get profile information ifdef PROFILE_JAVA -RUN_JAVA:=$(RUN_JAVA) -Xrunhprof:cpu=samples,file=${PROJECT_FILE}.profile.txt,depth=3 +RUN_JAVA += -Xrunhprof:cpu=samples,file=${PROJECT_FILE}.profile.txt,depth=3 endif # To turn off Simplify, put this in the project's own Makefile, after diff --git a/tests/daikon-tests/junit/Makefile.user b/tests/daikon-tests/junit/Makefile.user index 9c2889b925..169d40bfae 100644 --- a/tests/daikon-tests/junit/Makefile.user +++ b/tests/daikon-tests/junit/Makefile.user @@ -1,17 +1,5 @@ ########################################################################### -ifeq ($(shell uname), Darwin) - JAVA_HOME ?= $(/usr/libexec/java_home) -else -ifeq ($(shell which javac > /dev/null 2>&1 && echo found || echo nonexistent), found) - JAVA_HOME ?= $(shell readlink -f $(shell which javac) | sed "s:/bin/javac::") -else - $(info Failure: Here is the output of: which javac) - $(info $(shell which javac)) - $(error 'which javac' had exit status 1) -endif -endif - # need junit.jar on classpath to compile all junit references JUNIT_CLASSPATH := ${DAIKONDIR}/java/lib/* -JAVAC := $(JAVA_HOME)/bin/javac -cp .:$(JUNIT_CLASSPATH) +JAVAC = ${CHECKED_JAVA_HOME}/bin/javac -cp .:$(JUNIT_CLASSPATH) diff --git a/tests/daikon-tests/junit5/Makefile.user b/tests/daikon-tests/junit5/Makefile.user index d10d1f57d7..90c4b5cdad 100644 --- a/tests/daikon-tests/junit5/Makefile.user +++ b/tests/daikon-tests/junit5/Makefile.user @@ -1,18 +1,6 @@ ########################################################################### -ifeq ($(shell uname), Darwin) - JAVA_HOME ?= $(/usr/libexec/java_home) -else -ifeq ($(shell which javac > /dev/null 2>&1 && echo found || echo nonexistent), found) - JAVA_HOME ?= $(shell readlink -f $(shell which javac) | sed "s:/bin/javac::") -else - $(info Failure: Here is the output of: which javac) - $(info $(shell which javac)) - $(error 'which javac' had exit status 1) -endif -endif - # need junit.jar on classpath to compile all junit references # explicit order is necessary to get versions built with JDK 8 not 5 JUNIT_CLASSPATH := ${DAIKONDIR}/java/lib/junit-4.13.2-Daikon.jar:${DAIKONDIR}/java/lib/hamcrest-core-1.3-Daikon.jar:${DAIKONDIR}/java/lib/* -JAVAC := $(JAVA_HOME)/bin/javac -cp .:$(JUNIT_CLASSPATH) +JAVAC = ${CHECKED_JAVA_HOME}/bin/javac -cp .:$(JUNIT_CLASSPATH) From 3647e2a6d60eeb00424fac8980e505d00c192cab Mon Sep 17 00:00:00 2001 From: Michael Ernst Date: Sat, 10 Oct 2026 18:22:09 -0700 Subject: [PATCH 2/7] Share JAVA_HOME detection between Makefiles and validate it Detect JAVA_HOME by asking the JVM that runs javac, which handles wrapper scripts and Homebrew JDKs; fall back to /usr/libexec/java_home on macOS. Check that JAVA_HOME contains bin/javac. In the tests, fix the JDK after Makefile.user is included, use it for the version check, and omit nonexistent jars from JAVALIB_CLASSPATH. --- Makefile | 5 ++-- java/Makefile | 13 ++------- java/java-home.mk | 34 ++++++++++++++++++++++++ tests/Makefile.common | 61 +++++++++++++++++++++++-------------------- 4 files changed, 71 insertions(+), 42 deletions(-) create mode 100644 java/java-home.mk diff --git a/Makefile b/Makefile index 3783aa641d..8f1b4b830c 100644 --- a/Makefile +++ b/Makefile @@ -454,7 +454,7 @@ staging: @echo "]2;Building Javadoc" mkdir ${STAGING_DIR}/download/api # Javadoc is skipped on Java 8. Hard-coded JAVA_HOME path is a hack. - (export PATH=/usr/lib/jvm/java-11/bin:${PATH}; cd java; ${MAKE} SKIP_JAVADOC=0 'JAVADOC_DEST=${STAGING_DIR}/download/api' javadoc) + (if [ -d /usr/lib/jvm/java-11 ]; then export JAVA_HOME=/usr/lib/jvm/java-11 PATH=/usr/lib/jvm/java-11/bin:$${PATH}; fi; cd java; ${MAKE} SKIP_JAVADOC=0 'JAVADOC_DEST=${STAGING_DIR}/download/api' javadoc) # Copy the documentation @echo "]2;Copying documentation" mkdir ${STAGING_DIR}/download/doc @@ -624,7 +624,7 @@ daikon.jar: ${DAIKON_JAVA_FILES} $(patsubst %,java/%,${DAIKON_RESOURCE_FILES}) # careful about not including extraneous files in the distribution, and one # could make a distribution even if there were diffs in the current # checkout. -daikon.tar daikon.zip: kvasir ${README_PATHS} ${DAIKON_JAVA_FILES} java/Makefile +daikon.tar daikon.zip: kvasir ${README_PATHS} ${DAIKON_JAVA_FILES} java/Makefile java/java-home.mk ${MAKE} doc-all # `make doc-all` just did the work, but check that the files exist. ${MAKE} ${DOC_PATHS} @@ -682,6 +682,7 @@ daikon.tar daikon.zip: kvasir ${README_PATHS} ${DAIKON_JAVA_FILES} java/Makefile (cd java; tar chf ${TMPDIR}/daikon-java.tar --exclude daikon-java --exclude daikon-output --exclude Makefile.user --exclude PrototypeChecker.java --exclude VIndexChecker.java --exclude VIndexAnnotatedTypeFactory.java daikon jtb lib typequals) (mkdir ${TMPDIR}/daikon/java; cd ${TMPDIR}/daikon/java; tar xf ${TMPDIR}/daikon-java.tar; rm ${TMPDIR}/daikon-java.tar) cp -p java/Makefile ${TMPDIR}/daikon/java/Makefile + cp -p java/java-home.mk ${TMPDIR}/daikon/java/java-home.mk # Maybe I should do ${MAKE} javadoc # Don't do ${MAKE} clean which deletes .class files (cd ${TMPDIR}/daikon/java; ${RM_TEMP_FILES}) diff --git a/java/Makefile b/java/Makefile index 65dab35496..7e5db1a4fa 100644 --- a/java/Makefile +++ b/java/Makefile @@ -25,17 +25,8 @@ JAR ?= jar BuildJDKTool = BuildJDK -# Use ":=" so that the shell commands run only once. -ifeq ($(origin JAVA_HOME), undefined) - ifeq ($(shell uname), Darwin) - JAVA_HOME := $(shell /usr/libexec/java_home) - else - JAVA_HOME := $(shell javac=$$(command -v javac) && readlink -f "$$javac" | sed 's:/bin/javac$$::') - endif - ifeq (${JAVA_HOME},) - $(error No JDK found; set JAVA_HOME or put javac on the PATH) - endif -endif +include ${DAIKONDIR}/java/java-home.mk +CHECKED_JAVA_HOME := $(call checked_java_home,${JAVA_HOME}) # The "JAVANN" variables mean "at least version NN", except JAVA8. JAVA_VERSION_STRING_WITH_EA := $(shell javac -version 2>&1 | head -1 | cut "-d " -f2) diff --git a/java/java-home.mk b/java/java-home.mk new file mode 100644 index 0000000000..77d46f287a --- /dev/null +++ b/java/java-home.mk @@ -0,0 +1,34 @@ +# Makefile fragment that sets JAVA_HOME, if it is not already set to a non-empty value. +# It is included by java/Makefile and tests/Makefile.common. +# The same logic appears in scripts/daikon.bashrc. + +JAVA_HOME_WAS_SET := $(strip ${JAVA_HOME}) + +# Ask the JVM that runs javac for its home directory. This works even if javac +# on the PATH is a wrapper script (as used by jenv, asdf, or mise) rather than a +# symbolic link. For JDK 8, java.home is the "jre" subdirectory of the JDK. +# Use ":=" so that the shell commands run only once. +ifeq ($(strip ${JAVA_HOME}),) + JAVA_HOME := $(shell command -v javac > /dev/null 2>&1 && javac -J-XshowSettings:properties -version 2>&1 | sed -n 's:^ *java\.home = ::p' | sed 's:/jre$$::') +endif +ifeq ($(strip ${JAVA_HOME}),) + ifeq ($(shell uname), Darwin) + JAVA_HOME := $(shell /usr/libexec/java_home 2> /dev/null) + endif +endif + +# If the user set JAVA_HOME, warn if the javac on the PATH is in a different +# JDK, because some commands use the javac on the PATH. A javac that is not +# within a JDK (such as a wrapper script, or the macOS /usr/bin/javac stub) is +# not checked. A JDK is recognized by its "release" file. +ifneq (,${JAVA_HOME_WAS_SET}) + JAVAC_ON_PATH_REALPATH := $(realpath $(shell command -v javac 2> /dev/null)) + ifneq (,$(and $(filter %/bin/javac,${JAVAC_ON_PATH_REALPATH}),$(wildcard $(patsubst %/bin/javac,%,${JAVAC_ON_PATH_REALPATH})/release))) + ifneq (${JAVAC_ON_PATH_REALPATH},$(realpath ${JAVA_HOME}/bin/javac)) + $(warning JAVA_HOME is ${JAVA_HOME}, but the javac on the PATH is ${JAVAC_ON_PATH_REALPATH}; make them agree) + endif + endif +endif + +# $(call checked_java_home,DIR) yields DIR, or fails if DIR does not contain a JDK. +checked_java_home = $(if $(and $(1),$(wildcard $(1)/bin/javac)),$(1),$(error No JDK found at JAVA_HOME="$(1)"; set JAVA_HOME or put javac on the PATH)) diff --git a/tests/Makefile.common b/tests/Makefile.common index 7013f200a2..1c6391b27f 100644 --- a/tests/Makefile.common +++ b/tests/Makefile.common @@ -150,18 +150,11 @@ ifndef COMMON_HERE endif # JAVA_HOME is set before Makefile.user is included, so that Makefile.user can use or override it. -# Use ":=" so that the shell commands run only once. -ifeq ($(origin JAVA_HOME), undefined) - ifeq ($(shell uname), Darwin) - JAVA_HOME := $(shell /usr/libexec/java_home) - else - JAVA_HOME := $(shell javac=$$(command -v javac) && readlink -f "$$javac" | sed 's:/bin/javac$$::') - endif -endif -# CHECKED_JAVA_HOME is JAVA_HOME, but expanding it fails if no JDK was found. -# The check happens only when a variable that uses it is expanded, so targets -# that do not run Java (such as "clean") work without a JDK. -CHECKED_JAVA_HOME = $(or ${JAVA_HOME},$(error No JDK found; set JAVA_HOME)) +include ${DAIKONDIR}/java/java-home.mk +# CHECKED_JAVA_HOME is TESTS_JAVA_HOME (defined below), but expanding it fails if +# no JDK was found. The check happens only when a variable that uses it is +# expanded, so targets that do not run Java (such as "clean") work without a JDK. +CHECKED_JAVA_HOME = $(call checked_java_home,${TESTS_JAVA_HOME}) # Put user-specific changes in your own Makefile.user # (such as changes to PLUME_DIR or JAVAC or PATH). @@ -200,12 +193,18 @@ ECLAT_SCRATCH = $(SCRATCH_FOR_DAIKON_BASE)/$(PROJECT)_eclat # CP_PARENTS := cp -pf --parents CP_PARENTS := rsync -aR +# TESTS_JAVA_HOME is the JDK that the tests use. It is fixed here, so that a +# project Makefile that later reassigns JAVA_HOME does not change JAVA or JAVAC. +TESTS_JAVA_HOME := ${JAVA_HOME} JAVA = ${CHECKED_JAVA_HOME}/bin/java JAVAC ?= ${CHECKED_JAVA_HOME}/bin/javac JUNIT_CLASSPATH ?= -JAVALIB_CLASSPATH := ${JAVA_HOME}/jre/lib/rt.jar:${JAVA_HOME}/lib/tools.jar +# rt.jar exists only in JDK 8, and tools.jar only in JDK 8 and earlier. +empty := +space := ${empty} ${empty} +JAVALIB_CLASSPATH := $(subst ${space},:,$(if ${TESTS_JAVA_HOME},$(wildcard ${TESTS_JAVA_HOME}/jre/lib/rt.jar ${TESTS_JAVA_HOME}/lib/tools.jar))) DAIKON_CLASSPATH := ${DAIKONDIR}/java:${DAIKONDIR}/java/lib/*:${DAIKONDIR}/daikon.jar @@ -238,25 +237,29 @@ default: help # Note that version 11 (or 21 or ...) requires the 'start' anchor below # to make the old vs. new test work properly. -JAVA_VERSION_STRING := $(shell javac -version 2>&1 | head -1 | cut "-d " -f2 | sed 's/-ea//') -startJVS := $(shell echo start${JAVA_VERSION_STRING}) -ifneq (,$(findstring start1.8,$(startJVS))) - JAVA_RELEASE_NUMBER := 8 -else - # Old Java releases (e.g. 1.7) will set JAVA_RELEASE_NUMBER to 1. - # This will cause the JAVA8_OR_NEWER test to fail as desired. - JAVA_RELEASE_NUMBER := $(shell echo ${JAVA_VERSION_STRING} | sed 's/\([0-9]\+\)\(\..*\)\?\(-.*\)\?/\1/') -endif +# Without a JDK, the version variables are empty, so that targets that do not +# run Java (such as "clean") work. +ifneq (,$(and ${TESTS_JAVA_HOME},$(wildcard ${TESTS_JAVA_HOME}/bin/javac))) + JAVA_VERSION_STRING := $(shell ${TESTS_JAVA_HOME}/bin/javac -version 2>&1 | head -1 | cut "-d " -f2 | sed 's/-ea//') + startJVS := $(shell echo start${JAVA_VERSION_STRING}) + ifneq (,$(findstring start1.8,$(startJVS))) + JAVA_RELEASE_NUMBER := 8 + else + # Old Java releases (e.g. 1.7) will set JAVA_RELEASE_NUMBER to 1. + # This will cause the JAVA8_OR_NEWER test to fail as desired. + JAVA_RELEASE_NUMBER := $(shell echo ${JAVA_VERSION_STRING} | sed 's/\([0-9]\+\)\(\..*\)\?\(-.*\)\?/\1/') + endif -JAVA23_OR_NEWER := $(shell expr ${JAVA_RELEASE_NUMBER} \>= 23) -JAVA21_OR_NEWER := $(shell expr ${JAVA_RELEASE_NUMBER} \>= 21) -JAVA19_OR_NEWER := $(shell expr ${JAVA_RELEASE_NUMBER} \>= 19) -JAVA17_OR_NEWER := $(shell expr ${JAVA_RELEASE_NUMBER} \>= 17) -JAVA11_OR_NEWER := $(shell expr ${JAVA_RELEASE_NUMBER} \>= 11) -JAVA8_OR_NEWER := $(shell expr ${JAVA_RELEASE_NUMBER} \>= 8) + JAVA23_OR_NEWER := $(shell expr ${JAVA_RELEASE_NUMBER} \>= 23) + JAVA21_OR_NEWER := $(shell expr ${JAVA_RELEASE_NUMBER} \>= 21) + JAVA19_OR_NEWER := $(shell expr ${JAVA_RELEASE_NUMBER} \>= 19) + JAVA17_OR_NEWER := $(shell expr ${JAVA_RELEASE_NUMBER} \>= 17) + JAVA11_OR_NEWER := $(shell expr ${JAVA_RELEASE_NUMBER} \>= 11) + JAVA8_OR_NEWER := $(shell expr ${JAVA_RELEASE_NUMBER} \>= 8) -ifneq (${JAVA8_OR_NEWER}, 1) + ifneq (${JAVA8_OR_NEWER}, 1) $(error Must use Java version 8 or later; found ${JAVA_RELEASE_NUMBER} in ${JAVA_VERSION_STRING}) + endif endif # goal_name is a function that will be called like "$(call goal_name, FILENAME)" From cd820013d12f9a50d080ec48d6da4388e3d52207 Mon Sep 17 00:00:00 2001 From: Michael Ernst Date: Sat, 10 Oct 2026 18:22:44 -0700 Subject: [PATCH 3/7] Share JAVA_HOME detection in java/java-home.mk java/Makefile and tests/Makefile.common include java/java-home.mk, which asks the JVM that runs javac for its home directory, so that a javac wrapper script (as used by jenv, asdf, or mise) works. If the user set JAVA_HOME, it warns when the javac on the PATH is in a different JDK. scripts/daikon.bashrc uses the same logic. In the tests, the Java version check uses the JDK in JAVA_HOME and is skipped when there is no JDK, so that targets such as "clean" work without one. JAVALIB_CLASSPATH contains only jar files that exist. This incorporates the changes of #896. --- scripts/daikon.bashrc | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/scripts/daikon.bashrc b/scripts/daikon.bashrc index 9949bdde44..e846a99d23 100644 --- a/scripts/daikon.bashrc +++ b/scripts/daikon.bashrc @@ -8,10 +8,17 @@ ## files to .class files but have not re-made the daikon.jar file. ## You should not need to edit this file. -if [ "$(uname)" = "Darwin" ] ; then - export JAVA_HOME=${JAVA_HOME:-$(/usr/libexec/java_home)} -else - export JAVA_HOME=${JAVA_HOME:-$(dirname $(dirname $(readlink -f $(which javac))))} +# This logic is the same as in java/java-home.mk: ask the JVM that runs javac +# for its home directory, or else (on macOS) use /usr/libexec/java_home. +# "command" bypasses any alias for javac. +if [ -z "$JAVA_HOME" ] ; then + if command -v javac > /dev/null 2>&1 ; then + JAVA_HOME=$(command javac -J-XshowSettings:properties -version 2>&1 | sed -n 's:^ *java\.home = ::p' | sed 's:/jre$::') + fi + if [ -z "$JAVA_HOME" ] && [ "$(uname)" = "Darwin" ] ; then + JAVA_HOME=$(/usr/libexec/java_home 2> /dev/null) + fi + export JAVA_HOME fi if [ ! -d "$JAVA_HOME" ]; then echo "Cannot infer JAVA_HOME; please set it. Aborting daikon.bashrc ." From 34cabd639fce21f06ec49983d4b7a19feed68ebe Mon Sep 17 00:00:00 2001 From: Michael Ernst Date: Sat, 10 Oct 2026 18:34:40 -0700 Subject: [PATCH 4/7] Compute Java version once in java-home.mk; use JAVA_HOME's tools - java-home.mk exports JAVA_HOME, defines CHECKED_JAVA_HOME lazily, and reads JAVA_VERSION_STRING and JAVA_RELEASE_NUMBER from $JAVA_HOME/release. - java/Makefile uses the JDK at JAVA_HOME for java, javac, javadoc, and jar, so the version and the --system directory agree with the compiler. - The PATH-vs-JAVA_HOME warning runs once, after Makefile.user, and only when JAVA_HOME contains a JDK. - tests/Makefile.common includes java-home.mk after Makefile.user and drops TESTS_JAVA_HOME. - daikon.bashrc always exports JAVA_HOME and warns if it is not a JDK. - The staging target fails if Javadoc was not built. --- Makefile | 5 +++- java/Makefile | 50 ++++++++++++++++--------------------- java/java-home.mk | 49 +++++++++++++++++++++++------------- scripts/daikon.bashrc | 6 ++++- tests/Makefile | 5 ++++ tests/Makefile.common | 39 ++++++----------------------- tests/daikon-tests/Makefile | 4 ++- 7 files changed, 78 insertions(+), 80 deletions(-) diff --git a/Makefile b/Makefile index 8f1b4b830c..104831f75c 100644 --- a/Makefile +++ b/Makefile @@ -20,7 +20,9 @@ ifneq "$(wildcard ${SORT_DIRECTORY_ORDER})" "${SORT_DIRECTORY_ORDER}" SORT_DIRECTORY_ORDER := sort endif -JAVA_RELEASE_NUMBER := $(shell java -version 2>&1 | head -1 | cut -d'"' -f2 | sed '/^1\./s///' | cut -d'.' -f1 | sed 's/-ea//') +# Sets and exports JAVA_HOME, so that recursive invocations of make need not compute it. +include ${DAIKONDIR}/java/java-home.mk +JAVA_RELEASE_NUMBER := $(or ${JAVA_RELEASE_NUMBER},0) ########################################################################## @@ -455,6 +457,7 @@ staging: mkdir ${STAGING_DIR}/download/api # Javadoc is skipped on Java 8. Hard-coded JAVA_HOME path is a hack. (if [ -d /usr/lib/jvm/java-11 ]; then export JAVA_HOME=/usr/lib/jvm/java-11 PATH=/usr/lib/jvm/java-11/bin:$${PATH}; fi; cd java; ${MAKE} SKIP_JAVADOC=0 'JAVADOC_DEST=${STAGING_DIR}/download/api' javadoc) + @test -f ${STAGING_DIR}/download/api/index.html || (echo "Javadoc was not built; use Java 11 or later" && false) # Copy the documentation @echo "]2;Copying documentation" mkdir ${STAGING_DIR}/download/doc diff --git a/java/Makefile b/java/Makefile index 7e5db1a4fa..ed2aefa4fe 100644 --- a/java/Makefile +++ b/java/Makefile @@ -16,37 +16,30 @@ HTMLTOOLS ?= ${DAIKONDIR}/.utils/html-tools USER ?= daikonbuild -JAVA ?= java -ea +# These use the JDK at JAVA_HOME, which is set by java-home.mk (included below). +JAVA ?= ${CHECKED_JAVA_HOME}/bin/java -ea # For debugging, add -verbose flag to javac command -# JAVAC ?= javac -g -Xlint:unchecked -JAVAC ?= javac -JAVADOC ?= javadoc -JAR ?= jar +# JAVAC ?= ${CHECKED_JAVA_HOME}/bin/javac -g -Xlint:unchecked +JAVAC ?= ${CHECKED_JAVA_HOME}/bin/javac +JAVADOC ?= ${CHECKED_JAVA_HOME}/bin/javadoc +JAR ?= ${CHECKED_JAVA_HOME}/bin/jar BuildJDKTool = BuildJDK include ${DAIKONDIR}/java/java-home.mk -CHECKED_JAVA_HOME := $(call checked_java_home,${JAVA_HOME}) +# Without a JDK, use 0 so that the version tests below work, and targets that +# do not run Java (such as "clean") work. +JAVA_RELEASE_NUMBER := $(or ${JAVA_RELEASE_NUMBER},0) # The "JAVANN" variables mean "at least version NN", except JAVA8. -JAVA_VERSION_STRING_WITH_EA := $(shell javac -version 2>&1 | head -1 | cut "-d " -f2) -JAVA_VERSION_STRING := $(shell javac -version 2>&1 | head -1 | cut "-d " -f2 | sed 's/-ea//') -ifneq (,$(findstring start1.8,start${JAVA_VERSION_STRING})) +JAVA8 := 0 +JAVA17 := 0 +JAVA21 := 0 +JAVA24 := 0 +JAVA25 := 0 +JAVA27 := 0 +ifeq (${JAVA_RELEASE_NUMBER},8) JAVA8 := 1 - JAVA17 := 0 - JAVA21 := 0 - JAVA24 := 0 - JAVA25 := 0 - JAVA27 := 0 - JAVA_RELEASE_NUMBER := 8 -else - JAVA8 := 0 - JAVA17 := 0 - JAVA21 := 0 - JAVA24 := 0 - JAVA25 := 0 - JAVA27 := 0 - JAVA_RELEASE_NUMBER := $(shell echo ${JAVA_VERSION_STRING} | sed 's/\([0-9]*\)\..*/\1/') endif ifeq ($(shell test ${JAVA_RELEASE_NUMBER} -ge 17; echo $$?),0) JAVA17 := 1 @@ -168,9 +161,10 @@ ifneq "$(wildcard ${JAVA_HOME}/jre/lib/rt.jar)" "" DAIKON_CLASSPATH := ${DAIKON_CLASSPATH}:${JAVA_HOME}/jre/lib/rt.jar endif -JAVADOC_COMMAND := ${JAVADOC} -cp ${DAIKON_CLASSPATH} -JAVAC_COMMAND := ${JAVAC} -cp ${DAIKON_CLASSPATH} -Xlint -J-Xmx7g ${JAVAC_ARGS} -JAVA_COMMAND := ${JAVA} -cp ${DAIKON_CLASSPATH} +# Use "=", not ":=", so that a missing JDK is reported only when a command is used. +JAVADOC_COMMAND = ${JAVADOC} -cp ${DAIKON_CLASSPATH} +JAVAC_COMMAND = ${JAVAC} -cp ${DAIKON_CLASSPATH} -Xlint -J-Xmx7g ${JAVAC_ARGS} +JAVA_COMMAND = ${JAVA} -cp ${DAIKON_CLASSPATH} DOCLETPATH := ${DAIKONDIR}/java:$(wildcard ${DAIKONDIR}/java/lib/bcel-util-*.jar):$(wildcard ${DAIKONDIR}/java/lib/plume-util-*.jar):$(wildcard ${DAIKONDIR}/java/lib/reflection-util-*.jar):$(wildcard ${DAIKONDIR}/java/lib/hashmap-util-*.jar):${DAIKONDIR}/java/lib/daikon-plumelib.jar @@ -507,7 +501,7 @@ showvars-brief: @echo "CLASSPATH = ${CLASSPATH}" @echo "DAIKON_CLASSPATH = ${DAIKON_CLASSPATH}" @echo "JAVA_RELEASE_NUMBER = ${JAVA_RELEASE_NUMBER}" - @echo "JAVA_VERSION_STRING_WITH_EA = ${JAVA_VERSION_STRING_WITH_EA}" + @echo "JAVA_VERSION_STRING = ${JAVA_VERSION_STRING}" .PHONY: xlint xlint: daikon/Daikon.class @@ -1232,7 +1226,7 @@ junit-all: junit ### CLOVER := ${HOME}/clover -CLOVER_CMD := ${JAVA_COMMAND} -cp ${CLOVER}/clover-1.3.1/lib/clover.jar \ +CLOVER_CMD = ${JAVA_COMMAND} -cp ${CLOVER}/clover-1.3.1/lib/clover.jar \ com.cenqua.clover.CloverInstr -jdk14 CLOVER_CP := $(subst ${DAIKONDIR}/java,${CLOVER}/java,${DAIKON_CLASSPATH}) CLOVER_CP := ${CLOVER_CP}:${CLOVER}/clover-1.3.1/lib/clover.jar diff --git a/java/java-home.mk b/java/java-home.mk index 77d46f287a..f7ad98c211 100644 --- a/java/java-home.mk +++ b/java/java-home.mk @@ -1,8 +1,7 @@ -# Makefile fragment that sets JAVA_HOME, if it is not already set to a non-empty value. -# It is included by java/Makefile and tests/Makefile.common. -# The same logic appears in scripts/daikon.bashrc. - -JAVA_HOME_WAS_SET := $(strip ${JAVA_HOME}) +# Makefile fragment that sets JAVA_HOME (if it is not already set to a non-empty +# value), JAVA_VERSION_STRING, JAVA_RELEASE_NUMBER, and CHECKED_JAVA_HOME. +# It is included by the top-level Makefile, java/Makefile, and tests/Makefile.common. +# The same JAVA_HOME logic appears in scripts/daikon.bashrc. # Ask the JVM that runs javac for its home directory. This works even if javac # on the PATH is a wrapper script (as used by jenv, asdf, or mise) rather than a @@ -16,19 +15,35 @@ ifeq ($(strip ${JAVA_HOME}),) JAVA_HOME := $(shell /usr/libexec/java_home 2> /dev/null) endif endif +# Recursive invocations of make use this value rather than recomputing it. +export JAVA_HOME + +# CHECKED_JAVA_HOME is JAVA_HOME, but expanding it fails if JAVA_HOME does not +# contain a JDK. The check happens only when a variable that uses it is +# expanded, so targets that do not run Java (such as "clean") work without a JDK. +CHECKED_JAVA_HOME = $(if $(and ${JAVA_HOME},$(wildcard ${JAVA_HOME}/bin/javac)),${JAVA_HOME},$(error No JDK found at JAVA_HOME="${JAVA_HOME}"; set JAVA_HOME or put javac on the PATH)) -# If the user set JAVA_HOME, warn if the javac on the PATH is in a different -# JDK, because some commands use the javac on the PATH. A javac that is not -# within a JDK (such as a wrapper script, or the macOS /usr/bin/javac stub) is -# not checked. A JDK is recognized by its "release" file. -ifneq (,${JAVA_HOME_WAS_SET}) - JAVAC_ON_PATH_REALPATH := $(realpath $(shell command -v javac 2> /dev/null)) - ifneq (,$(and $(filter %/bin/javac,${JAVAC_ON_PATH_REALPATH}),$(wildcard $(patsubst %/bin/javac,%,${JAVAC_ON_PATH_REALPATH})/release))) - ifneq (${JAVAC_ON_PATH_REALPATH},$(realpath ${JAVA_HOME}/bin/javac)) - $(warning JAVA_HOME is ${JAVA_HOME}, but the javac on the PATH is ${JAVAC_ON_PATH_REALPATH}; make them agree) +# The version of the JDK at JAVA_HOME, read from its "release" file without +# starting a JVM. Examples: "1.8.0_462", "25.0.1", "27-ea". Each variable is +# empty if JAVA_HOME does not contain a JDK. +JAVA_VERSION_STRING := $(if $(strip ${JAVA_HOME}),$(shell sed -n 's/^JAVA_VERSION="\(.*\)"/\1/p' '${JAVA_HOME}/release' 2> /dev/null)) +# The major version, such as "8" or "25". +JAVA_RELEASE_NUMBER := $(shell echo '${JAVA_VERSION_STRING}' | sed -e 's/^1\.//' -e 's/^\([0-9]*\).*/\1/') + +# Warn if the javac on the PATH is in a different JDK than JAVA_HOME, because +# some commands use the javac on the PATH. Skip the check if JAVA_HOME does not +# contain a JDK (expanding CHECKED_JAVA_HOME reports that), or if the javac on +# the PATH is not within a JDK (such as a wrapper script, or the macOS +# /usr/bin/javac stub). A JDK is recognized by its "release" file. +# The check is done only once, not in every recursive invocation of make. +ifndef DAIKON_JAVA_HOME_CHECKED + export DAIKON_JAVA_HOME_CHECKED := 1 + ifneq (,$(wildcard ${JAVA_HOME}/bin/javac)) + JAVAC_ON_PATH_REALPATH := $(realpath $(shell command -v javac 2> /dev/null)) + ifneq (,$(and $(filter %/bin/javac,${JAVAC_ON_PATH_REALPATH}),$(wildcard $(patsubst %/bin/javac,%,${JAVAC_ON_PATH_REALPATH})/release))) + ifneq (${JAVAC_ON_PATH_REALPATH},$(realpath ${JAVA_HOME}/bin/javac)) + $(warning JAVA_HOME is ${JAVA_HOME}, but the javac on the PATH is ${JAVAC_ON_PATH_REALPATH}; make them agree) + endif endif endif endif - -# $(call checked_java_home,DIR) yields DIR, or fails if DIR does not contain a JDK. -checked_java_home = $(if $(and $(1),$(wildcard $(1)/bin/javac)),$(1),$(error No JDK found at JAVA_HOME="$(1)"; set JAVA_HOME or put javac on the PATH)) diff --git a/scripts/daikon.bashrc b/scripts/daikon.bashrc index e846a99d23..6b9fd72c65 100644 --- a/scripts/daikon.bashrc +++ b/scripts/daikon.bashrc @@ -18,12 +18,16 @@ if [ -z "$JAVA_HOME" ] ; then if [ -z "$JAVA_HOME" ] && [ "$(uname)" = "Darwin" ] ; then JAVA_HOME=$(/usr/libexec/java_home 2> /dev/null) fi - export JAVA_HOME fi +export JAVA_HOME if [ ! -d "$JAVA_HOME" ]; then echo "Cannot infer JAVA_HOME; please set it. Aborting daikon.bashrc ." return 2 fi +# Running Daikon needs only a JRE, but building Daikon needs a JDK. +if [ ! -x "$JAVA_HOME/bin/javac" ]; then + echo "Warning: JAVA_HOME=$JAVA_HOME is not a JDK (it has no bin/javac); building Daikon requires a JDK." +fi if [ ${#BASH_SOURCE[@]} -eq 0 ]; then # Cannot infer DAIKONDIR. diff --git a/tests/Makefile b/tests/Makefile index e238ef2bd2..80e9402164 100644 --- a/tests/Makefile +++ b/tests/Makefile @@ -34,6 +34,11 @@ else MPARG ?= endif +# DAIKONDIR is the clone of the 'daikon' project. +DAIKONDIR ?= $(realpath $(dir $(lastword ${MAKEFILE_LIST}))..) +# Sets and exports JAVA_HOME, so that the test directories need not compute it. +include ${DAIKONDIR}/java/java-home.mk + PLUME_SCRIPTS ?= ${DAIKONDIR}/.utils/plume-scripts all diffs: txt-diff non-txt-diff dyncomp-tests results diff --git a/tests/Makefile.common b/tests/Makefile.common index 1c6391b27f..29d5e6fef8 100644 --- a/tests/Makefile.common +++ b/tests/Makefile.common @@ -149,13 +149,6 @@ ifndef COMMON_HERE $(error Test directory ${DAIKONDIR}/tests does not exist) endif -# JAVA_HOME is set before Makefile.user is included, so that Makefile.user can use or override it. -include ${DAIKONDIR}/java/java-home.mk -# CHECKED_JAVA_HOME is TESTS_JAVA_HOME (defined below), but expanding it fails if -# no JDK was found. The check happens only when a variable that uses it is -# expanded, so targets that do not run Java (such as "clean") work without a JDK. -CHECKED_JAVA_HOME = $(call checked_java_home,${TESTS_JAVA_HOME}) - # Put user-specific changes in your own Makefile.user # (such as changes to PLUME_DIR or JAVAC or PATH). # Make will silently continue if the file does not exist. @@ -163,6 +156,10 @@ CHECKED_JAVA_HOME = $(call checked_java_home,${TESTS_JAVA_HOME}) -include Makefile.user -include $(COMMON_HERE)/Makefile.user +# This is included after Makefile.user, so that Makefile.user can set JAVA_HOME. +# It sets JAVA_HOME, CHECKED_JAVA_HOME, and JAVA_RELEASE_NUMBER. +include ${DAIKONDIR}/java/java-home.mk + # SOURCES is where all of the source code to be examined is located SOURCES?=$(COMMON_HERE)/sources @@ -193,18 +190,12 @@ ECLAT_SCRATCH = $(SCRATCH_FOR_DAIKON_BASE)/$(PROJECT)_eclat # CP_PARENTS := cp -pf --parents CP_PARENTS := rsync -aR -# TESTS_JAVA_HOME is the JDK that the tests use. It is fixed here, so that a -# project Makefile that later reassigns JAVA_HOME does not change JAVA or JAVAC. -TESTS_JAVA_HOME := ${JAVA_HOME} JAVA = ${CHECKED_JAVA_HOME}/bin/java JAVAC ?= ${CHECKED_JAVA_HOME}/bin/javac JUNIT_CLASSPATH ?= -# rt.jar exists only in JDK 8, and tools.jar only in JDK 8 and earlier. -empty := -space := ${empty} ${empty} -JAVALIB_CLASSPATH := $(subst ${space},:,$(if ${TESTS_JAVA_HOME},$(wildcard ${TESTS_JAVA_HOME}/jre/lib/rt.jar ${TESTS_JAVA_HOME}/lib/tools.jar))) +JAVALIB_CLASSPATH := ${JAVA_HOME}/jre/lib/rt.jar:${JAVA_HOME}/lib/tools.jar DAIKON_CLASSPATH := ${DAIKONDIR}/java:${DAIKONDIR}/java/lib/*:${DAIKONDIR}/daikon.jar @@ -230,26 +221,10 @@ export LC_ALL=C default: help -# The general form of a Java version number prior to Java 9 -# is 1.n.n_n. For Java 9 or later it is n.n.n.n. -# Currently, we only look for the release number, which is -# the first 'n' in either case. -# Note that version 11 (or 21 or ...) requires the 'start' anchor below -# to make the old vs. new test work properly. - +# JAVA_RELEASE_NUMBER is set by java-home.mk. # Without a JDK, the version variables are empty, so that targets that do not # run Java (such as "clean") work. -ifneq (,$(and ${TESTS_JAVA_HOME},$(wildcard ${TESTS_JAVA_HOME}/bin/javac))) - JAVA_VERSION_STRING := $(shell ${TESTS_JAVA_HOME}/bin/javac -version 2>&1 | head -1 | cut "-d " -f2 | sed 's/-ea//') - startJVS := $(shell echo start${JAVA_VERSION_STRING}) - ifneq (,$(findstring start1.8,$(startJVS))) - JAVA_RELEASE_NUMBER := 8 - else - # Old Java releases (e.g. 1.7) will set JAVA_RELEASE_NUMBER to 1. - # This will cause the JAVA8_OR_NEWER test to fail as desired. - JAVA_RELEASE_NUMBER := $(shell echo ${JAVA_VERSION_STRING} | sed 's/\([0-9]\+\)\(\..*\)\?\(-.*\)\?/\1/') - endif - +ifneq (,${JAVA_RELEASE_NUMBER}) JAVA23_OR_NEWER := $(shell expr ${JAVA_RELEASE_NUMBER} \>= 23) JAVA21_OR_NEWER := $(shell expr ${JAVA_RELEASE_NUMBER} \>= 21) JAVA19_OR_NEWER := $(shell expr ${JAVA_RELEASE_NUMBER} \>= 19) diff --git a/tests/daikon-tests/Makefile b/tests/daikon-tests/Makefile index 3342685d68..ef19b0bb08 100644 --- a/tests/daikon-tests/Makefile +++ b/tests/daikon-tests/Makefile @@ -2,7 +2,9 @@ # Read Makefile in parent directory to see what this all means # DAIKONDIR is the clone of the 'daikon' project. -DAIKONDIR ?= $(realpath $(dir $(lastword ${MAKEFILE_LIST}))..) +DAIKONDIR ?= $(realpath $(dir $(lastword ${MAKEFILE_LIST}))../..) +# Sets and exports JAVA_HOME, so that the test directories need not compute it. +include ${DAIKONDIR}/java/java-home.mk SCRIPTS=${DAIKONDIR}/scripts # Help system From 85c188bcd128b60e684aad647b92bef75e706e99 Mon Sep 17 00:00:00 2001 From: Michael Ernst Date: Sat, 10 Oct 2026 18:37:34 -0700 Subject: [PATCH 5/7] Robustly determine the Java version; share version tests - java-home.mk falls back to "java -version" when the JDK has no "release" file (as in Debian/Ubuntu OpenJDK 8), and fails if a Java installation's version cannot be determined, rather than silently treating it as 0. - java-home.mk defines java_at_least, used by all Makefiles instead of repeated shell tests; the major version is computed without a shell. - JAVA_HOME detection first uses the JDK containing the javac on the PATH, and starts a JVM only if that fails. daikon.bashrc does the same. - DAIKON_JAVA_HOME snapshots JAVA_HOME, so commands and version variables agree even if a Makefile later reassigns JAVA_HOME. - The PATH-vs-JAVA_HOME check also covers java, recognizes JDKs without a "release" file, and reruns when JAVA_HOME or PATH changes. - The top-level Makefile runs java, javac, and jar from JAVA_HOME. --- Makefile | 22 ++++---- java/Makefile | 59 ++++++++------------- java/java-home.mk | 116 ++++++++++++++++++++++++++++++------------ scripts/daikon.bashrc | 18 +++++-- tests/Makefile.common | 25 ++++----- 5 files changed, 142 insertions(+), 98 deletions(-) diff --git a/Makefile b/Makefile index 104831f75c..6d3414cfdd 100644 --- a/Makefile +++ b/Makefile @@ -20,9 +20,9 @@ ifneq "$(wildcard ${SORT_DIRECTORY_ORDER})" "${SORT_DIRECTORY_ORDER}" SORT_DIRECTORY_ORDER := sort endif -# Sets and exports JAVA_HOME, so that recursive invocations of make need not compute it. +# Sets and exports JAVA_HOME (so that recursive invocations of make need not +# compute it), and defines CHECKED_JAVA_HOME and java_at_least. include ${DAIKONDIR}/java/java-home.mk -JAVA_RELEASE_NUMBER := $(or ${JAVA_RELEASE_NUMBER},0) ########################################################################## @@ -77,7 +77,7 @@ SCRIPT_PATHS := $(addprefix scripts/,${SCRIPT_FILES}) # This is so troublesome that it isn't used except as a list of dependences for make commands DAIKON_JAVA_FILES := $(shell find java -name '*daikon-java*' -prune -o -name '*.java' -print | ${SORT_DIRECTORY_ORDER}) # If we're building on JDK 23 or less, remove the java24 files from the list. -ifeq ($(shell test ${JAVA_RELEASE_NUMBER} -lt 24; echo $$?),0) +ifneq ($(call java_at_least,24),1) DAIKON_JAVA_FILES := $(filter-out %24.java, ${DAIKON_JAVA_FILES}) endif @@ -307,15 +307,15 @@ junit: # are working properly. quick-test: cd examples/java-examples/StackAr; \ - javac -g DataStructures/*.java + ${CHECKED_JAVA_HOME}/bin/javac -g DataStructures/*.java @echo "Running Chicory/Daikon" cd examples/java-examples/StackAr; \ - java -cp ${QT_PATH} daikon.Chicory --daikon DataStructures.StackArTester + ${CHECKED_JAVA_HOME}/bin/java -cp ${QT_PATH} daikon.Chicory --daikon DataStructures.StackArTester @echo "Running DynComp/Chicory/Daikon" cd examples/java-examples/StackAr; \ - java -cp ${QT_PATH} daikon.DynComp DataStructures.StackArTester; \ - java -cp ${QT_PATH} daikon.Chicory --comparability-file=StackArTester.decls-DynComp DataStructures.StackArTester; \ - java -cp ${QT_PATH} daikon.Daikon StackArTester.dtrace.gz + ${CHECKED_JAVA_HOME}/bin/java -cp ${QT_PATH} daikon.DynComp DataStructures.StackArTester; \ + ${CHECKED_JAVA_HOME}/bin/java -cp ${QT_PATH} daikon.Chicory --comparability-file=StackArTester.decls-DynComp DataStructures.StackArTester; \ + ${CHECKED_JAVA_HOME}/bin/java -cp ${QT_PATH} daikon.Daikon StackArTester.dtrace.gz # Sanity check, suitable for continuous integration. nightly-test: @@ -381,7 +381,7 @@ test-staged-dist: ${STAGING_DIR} ## Make sure that all of the class files are 1.8 (version 52) or earlier. (cd ${DISTTESTDIRJAVA} && find . \( -name '*.class' \) -print0 | xargs -0 -n 1 ${PLUME_SCRIPTS}/classfile_check_version 52) ## Test that we can rebuild the .class files from the .java files. - (cd ${DISTTESTDIRJAVA}/daikon/java; rm `find . -name '*.class'`; ${MAKE} JAVAC='javac' compile) + (cd ${DISTTESTDIRJAVA}/daikon/java; rm `find . -name '*.class'`; ${MAKE} JAVAC='${CHECKED_JAVA_HOME}/bin/javac' compile) ## Test that these new .class files work properly. ${MAKE} -C ${DISTTESTDIR}/daikon/java junit ## Test the main target of the makefile. @@ -612,11 +612,11 @@ daikon.jar: ${DAIKON_JAVA_FILES} $(patsubst %,java/%,${DAIKON_RESOURCE_FILES}) | sort | xargs '-I{}' ${RSYNC_AR} '{}' ${TMPDIR}/daikon-jar # make hamcrest and junit4 overwrite junit5 to get our special versions - for filename in ${JAR_DIR}/java/lib/*.jar ${JAR_DIR}/java/lib/hamcrest*.jar ${JAR_DIR}/java/lib/junit-4*.jar; do cd ${TMPDIR}/daikon-jar; jar xf $$filename ; done + for filename in ${JAR_DIR}/java/lib/*.jar ${JAR_DIR}/java/lib/hamcrest*.jar ${JAR_DIR}/java/lib/junit-4*.jar; do cd ${TMPDIR}/daikon-jar; ${CHECKED_JAVA_HOME}/bin/jar xf $$filename ; done (cd java; ${RSYNC_AR} ${DAIKON_RESOURCE_FILES} ${TMPDIR}/daikon-jar) (cd java; ${RSYNC_AR} daikon/tools/runtimechecker/Main.doc daikon/tools/runtimechecker/InstrumentHandler.doc ${TMPDIR}/daikon-jar) cd ${TMPDIR}/daikon-jar && \ - jar cfm $@ ${JAR_DIR}/java/daikon/chicory/manifest.txt * + ${CHECKED_JAVA_HOME}/bin/jar cfm $@ ${JAR_DIR}/java/daikon/chicory/manifest.txt * mv ${TMPDIR}/daikon-jar/$@ $@ #rm -rf ${TMPDIR}/daikon-jar diff --git a/java/Makefile b/java/Makefile index ed2aefa4fe..467e3b1bd1 100644 --- a/java/Makefile +++ b/java/Makefile @@ -27,50 +27,33 @@ JAR ?= ${CHECKED_JAVA_HOME}/bin/jar BuildJDKTool = BuildJDK include ${DAIKONDIR}/java/java-home.mk -# Without a JDK, use 0 so that the version tests below work, and targets that -# do not run Java (such as "clean") work. -JAVA_RELEASE_NUMBER := $(or ${JAVA_RELEASE_NUMBER},0) # The "JAVANN" variables mean "at least version NN", except JAVA8. -JAVA8 := 0 -JAVA17 := 0 -JAVA21 := 0 -JAVA24 := 0 -JAVA25 := 0 -JAVA27 := 0 -ifeq (${JAVA_RELEASE_NUMBER},8) - JAVA8 := 1 -endif -ifeq ($(shell test ${JAVA_RELEASE_NUMBER} -ge 17; echo $$?),0) - JAVA17 := 1 -endif -ifeq ($(shell test ${JAVA_RELEASE_NUMBER} -ge 21; echo $$?),0) - JAVA21 := 1 -endif +# Without a JDK, they are all 0, so that targets that do not run Java (such as +# "clean") work. +JAVA8 := $(if $(filter 8,${JAVA_RELEASE_NUMBER}),1,0) +JAVA17 := $(if $(call java_at_least,17),1,0) +JAVA21 := $(if $(call java_at_least,21),1,0) # JDK 24 isn't an LTS version, but we need it to know whether to compile Instrument24.java. -ifeq ($(shell test ${JAVA_RELEASE_NUMBER} -ge 24; echo $$?),0) - JAVA24 := 1 +JAVA24 := $(if $(call java_at_least,24),1,0) +ifeq (${JAVA24}, 1) BuildJDKTool = BuildJDK24 endif -ifeq ($(shell test ${JAVA_RELEASE_NUMBER} -ge 25; echo $$?),0) - JAVA25 := 1 -endif -ifeq ($(shell test ${JAVA_RELEASE_NUMBER} -eq 25; echo $$?),0) +JAVA25 := $(if $(call java_at_least,25),1,0) +JAVA27 := $(if $(call java_at_least,27),1,0) +ifeq (${JAVA_RELEASE_NUMBER},25) ifndef JAVA25_HOME - JAVA25_HOME := ${JAVA_HOME} + JAVA25_HOME := ${DAIKON_JAVA_HOME} endif endif -ifeq ($(shell test ${JAVA_RELEASE_NUMBER} -ge 27; echo $$?),0) - JAVA27 := 1 -endif -# Use -ge 26, not -ge 27, so that JDK 26 has a value for the --system flag below. -ifeq ($(shell test ${JAVA_RELEASE_NUMBER} -ge 26; echo $$?),0) +# Use 26, not 27, so that JDK 26 has a value for the --system flag below. +ifeq ($(call java_at_least,26),1) ifndef JAVA27_HOME - JAVA27_HOME := ${JAVA_HOME} + JAVA27_HOME := ${DAIKON_JAVA_HOME} endif endif -ifeq ($(shell test ${JAVA_RELEASE_NUMBER} -gt 20; echo $$?),0) +ifeq ($(call java_at_least,21),1) LINT_THIS_ESCAPE=-Xlint:-this-escape endif @@ -154,11 +137,11 @@ CHECKERFRAMEWORK_JAVAC ?= ${CHECKERFRAMEWORK_JAR_DIR}/javac.jar DAIKON_CLASSPATH := ${DAIKONDIR}/java/lib/junit-4.13.2-Daikon.jar:${DAIKONDIR}/java/lib/hamcrest-core-1.3-Daikon.jar:${DAIKONDIR}/java/lib/*:${DAIKONDIR}/java ## Add tools.jar and rt.jar, if they exist -ifneq "$(wildcard ${JAVA_HOME}/lib/tools.jar)" "" - DAIKON_CLASSPATH := ${DAIKON_CLASSPATH}:${JAVA_HOME}/lib/tools.jar +ifneq "$(wildcard ${DAIKON_JAVA_HOME}/lib/tools.jar)" "" + DAIKON_CLASSPATH := ${DAIKON_CLASSPATH}:${DAIKON_JAVA_HOME}/lib/tools.jar endif -ifneq "$(wildcard ${JAVA_HOME}/jre/lib/rt.jar)" "" - DAIKON_CLASSPATH := ${DAIKON_CLASSPATH}:${JAVA_HOME}/jre/lib/rt.jar +ifneq "$(wildcard ${DAIKON_JAVA_HOME}/jre/lib/rt.jar)" "" + DAIKON_CLASSPATH := ${DAIKON_CLASSPATH}:${DAIKON_JAVA_HOME}/jre/lib/rt.jar endif # Use "=", not ":=", so that a missing JDK is reported only when a command is used. @@ -170,7 +153,7 @@ DOCLETPATH := ${DAIKONDIR}/java:$(wildcard ${DAIKONDIR}/java/lib/bcel-util-*.jar DOCLET_FLAGS := LINKOFFLINE := https://docs.oracle.com/javase/8/docs/api/ -ifeq ($(shell expr ${JAVA_RELEASE_NUMBER} \>= 11),1) +ifeq ($(call java_at_least,11),1) DOCLET_FLAGS := --add-exports jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED LINKOFFLINE := https://docs.oracle.com/en/java/javase/17/docs/api/ endif @@ -1324,7 +1307,7 @@ endif ## Make Javadoc with private fields for people working on source code. ## It is generated in the standard location, directory api/ api-private: ${PLUME_SCRIPTS} ${AUTO_GENERATED_FILES} -ifeq ($(shell test ${JAVA_RELEASE_NUMBER} -eq 8; echo $$?),0) +ifeq (${JAVA8}, 1) @echo "Skipping api-private target under Java 8" else mkdir -p ${JAVADOC_DEST} diff --git a/java/java-home.mk b/java/java-home.mk index f7ad98c211..6f41e624cc 100644 --- a/java/java-home.mk +++ b/java/java-home.mk @@ -1,12 +1,31 @@ # Makefile fragment that sets JAVA_HOME (if it is not already set to a non-empty -# value), JAVA_VERSION_STRING, JAVA_RELEASE_NUMBER, and CHECKED_JAVA_HOME. -# It is included by the top-level Makefile, java/Makefile, and tests/Makefile.common. -# The same JAVA_HOME logic appears in scripts/daikon.bashrc. - -# Ask the JVM that runs javac for its home directory. This works even if javac -# on the PATH is a wrapper script (as used by jenv, asdf, or mise) rather than a -# symbolic link. For JDK 8, java.home is the "jre" subdirectory of the JDK. -# Use ":=" so that the shell commands run only once. +# value) and defines DAIKON_JAVA_HOME, CHECKED_JAVA_HOME, JAVA_VERSION_STRING, +# JAVA_RELEASE_NUMBER, and the function java_at_least. +# It is included by the top-level Makefile, java/Makefile, tests/Makefile, +# tests/Makefile.common, and tests/daikon-tests/Makefile. +# scripts/daikon.bashrc finds JAVA_HOME in the same way, but does not compute +# the Java version. + +# $(call is_java_home,DIR) is non-empty if DIR contains a JDK or JRE. +# JDK 9 and later contain lib/jrt-fs.jar; JDK 8 contains jre/lib/rt.jar; JRE 8 contains lib/rt.jar. +is_java_home = $(and $(strip $(1)),$(wildcard $(1)/bin/java),$(wildcard $(1)/lib/jrt-fs.jar $(1)/jre/lib/rt.jar $(1)/lib/rt.jar)) +# $(call is_jdk,DIR) is non-empty if DIR contains a JDK. +is_jdk = $(and $(call is_java_home,$(1)),$(wildcard $(1)/bin/javac)) + +# The JDK that contains the javac on the PATH, or empty if the javac on the PATH +# is not (a symbolic link to) a file in a JDK. For example, it is empty if +# javac is a wrapper script (as used by jenv, asdf, or mise), or the macOS +# /usr/bin/javac stub. +JAVAC_ON_PATH_REALPATH := $(realpath $(shell command -v javac 2> /dev/null)) +JAVAC_ON_PATH_JDK := $(patsubst %/bin/javac,%,$(filter %/bin/javac,${JAVAC_ON_PATH_REALPATH})) +JAVAC_ON_PATH_JDK := $(if $(call is_jdk,${JAVAC_ON_PATH_JDK}),${JAVAC_ON_PATH_JDK}) + +ifeq ($(strip ${JAVA_HOME}),) + JAVA_HOME := ${JAVAC_ON_PATH_JDK} +endif +# Ask the JVM that runs javac for its home directory. This is slower because it +# starts a JVM, but it works even if javac on the PATH is a wrapper script. +# For JDK 8, java.home is the "jre" subdirectory of the JDK. ifeq ($(strip ${JAVA_HOME}),) JAVA_HOME := $(shell command -v javac > /dev/null 2>&1 && javac -J-XshowSettings:properties -version 2>&1 | sed -n 's:^ *java\.home = ::p' | sed 's:/jre$$::') endif @@ -18,31 +37,62 @@ endif # Recursive invocations of make use this value rather than recomputing it. export JAVA_HOME -# CHECKED_JAVA_HOME is JAVA_HOME, but expanding it fails if JAVA_HOME does not -# contain a JDK. The check happens only when a variable that uses it is -# expanded, so targets that do not run Java (such as "clean") work without a JDK. -CHECKED_JAVA_HOME = $(if $(and ${JAVA_HOME},$(wildcard ${JAVA_HOME}/bin/javac)),${JAVA_HOME},$(error No JDK found at JAVA_HOME="${JAVA_HOME}"; set JAVA_HOME or put javac on the PATH)) - -# The version of the JDK at JAVA_HOME, read from its "release" file without -# starting a JVM. Examples: "1.8.0_462", "25.0.1", "27-ea". Each variable is -# empty if JAVA_HOME does not contain a JDK. -JAVA_VERSION_STRING := $(if $(strip ${JAVA_HOME}),$(shell sed -n 's/^JAVA_VERSION="\(.*\)"/\1/p' '${JAVA_HOME}/release' 2> /dev/null)) -# The major version, such as "8" or "25". -JAVA_RELEASE_NUMBER := $(shell echo '${JAVA_VERSION_STRING}' | sed -e 's/^1\.//' -e 's/^\([0-9]*\).*/\1/') - -# Warn if the javac on the PATH is in a different JDK than JAVA_HOME, because -# some commands use the javac on the PATH. Skip the check if JAVA_HOME does not -# contain a JDK (expanding CHECKED_JAVA_HOME reports that), or if the javac on -# the PATH is not within a JDK (such as a wrapper script, or the macOS -# /usr/bin/javac stub). A JDK is recognized by its "release" file. -# The check is done only once, not in every recursive invocation of make. -ifndef DAIKON_JAVA_HOME_CHECKED - export DAIKON_JAVA_HOME_CHECKED := 1 - ifneq (,$(wildcard ${JAVA_HOME}/bin/javac)) - JAVAC_ON_PATH_REALPATH := $(realpath $(shell command -v javac 2> /dev/null)) - ifneq (,$(and $(filter %/bin/javac,${JAVAC_ON_PATH_REALPATH}),$(wildcard $(patsubst %/bin/javac,%,${JAVAC_ON_PATH_REALPATH})/release))) - ifneq (${JAVAC_ON_PATH_REALPATH},$(realpath ${JAVA_HOME}/bin/javac)) - $(warning JAVA_HOME is ${JAVA_HOME}, but the javac on the PATH is ${JAVAC_ON_PATH_REALPATH}; make them agree) +# DAIKON_JAVA_HOME is the value of JAVA_HOME when this file is included. It +# does not change if a Makefile later reassigns JAVA_HOME, so the commands that +# use it always agree with the version variables below. +DAIKON_JAVA_HOME := ${JAVA_HOME} + +# CHECKED_JAVA_HOME is DAIKON_JAVA_HOME, but expanding it fails if +# DAIKON_JAVA_HOME does not contain a JDK. The check happens only when a +# variable that uses it is expanded, so targets that do not run Java (such as +# "clean") work without a JDK. +CHECKED_JAVA_HOME = $(if $(call is_jdk,${DAIKON_JAVA_HOME}),${DAIKON_JAVA_HOME},$(error No JDK found at JAVA_HOME="${DAIKON_JAVA_HOME}"; set JAVA_HOME or put javac on the PATH)) + +# The version of the Java installation at DAIKON_JAVA_HOME. Examples: +# "1.8.0_462", "25.0.1", "27-ea". It is read from the "release" file, to avoid +# starting a JVM. If there is no "release" file (as in Debian and Ubuntu's +# OpenJDK 8), it is obtained from "java -version". It is empty if JAVA_HOME +# does not contain a Java installation. It is an error if JAVA_HOME contains a +# Java installation but its version cannot be determined. +JAVA_VERSION_STRING := +ifneq (,$(call is_java_home,${DAIKON_JAVA_HOME})) + JAVA_VERSION_STRING := $(shell sed -n 's/^JAVA_VERSION="\(.*\)"/\1/p' '${DAIKON_JAVA_HOME}/release' 2> /dev/null) + ifeq (,${JAVA_VERSION_STRING}) + JAVA_VERSION_STRING := $(shell '${DAIKON_JAVA_HOME}/bin/java' -version 2>&1 | sed -n 's/^.* version "\([^"]*\)".*/\1/p' | head -1) + endif + ifeq (,${JAVA_VERSION_STRING}) + $(error Cannot determine the Java version of JAVA_HOME="${DAIKON_JAVA_HOME}") + endif +endif +# The major version, such as "8" or "25". It is empty if JAVA_VERSION_STRING is. +JAVA_RELEASE_NUMBER := $(firstword $(subst ., ,$(subst -, ,$(subst +, ,$(subst _, ,$(patsubst 1.%,%,${JAVA_VERSION_STRING})))))) + +# $(call java_at_least,NN) is "1" if JAVA_RELEASE_NUMBER is NN or greater, and +# is empty otherwise (including if there is no Java installation). +JAVA_HOME_MK_NUMBERS := $(shell seq 1 999) +java_at_least = $(if $(filter ${JAVA_RELEASE_NUMBER},$(wordlist $(1),999,${JAVA_HOME_MK_NUMBERS})),1) + +# Warn if the javac or java on the PATH is in a different JDK than JAVA_HOME, +# because some commands use the javac or java on the PATH. Skip the check if +# JAVA_HOME does not contain a JDK (expanding CHECKED_JAVA_HOME reports that), +# or if the program on the PATH is not within a Java installation (such as a +# wrapper script, or the macOS /usr/bin/javac stub). +# The check is done once for each combination of JAVA_HOME and PATH, not in +# every recursive invocation of make. +JAVA_HOME_MK_CHECK_KEY := ${DAIKON_JAVA_HOME}:${PATH} +ifneq (${DAIKON_JAVA_HOME_CHECKED},${JAVA_HOME_MK_CHECK_KEY}) + export DAIKON_JAVA_HOME_CHECKED := ${JAVA_HOME_MK_CHECK_KEY} + ifneq (,$(call is_jdk,${DAIKON_JAVA_HOME})) + ifneq (,${JAVAC_ON_PATH_JDK}) + ifneq (${JAVAC_ON_PATH_REALPATH},$(realpath ${DAIKON_JAVA_HOME}/bin/javac)) + $(warning JAVA_HOME is ${DAIKON_JAVA_HOME}, but the javac on the PATH is ${JAVAC_ON_PATH_REALPATH}; make them agree) + endif + endif + JAVA_ON_PATH_REALPATH := $(realpath $(shell command -v java 2> /dev/null)) + JAVA_ON_PATH_HOME := $(patsubst %/jre,%,$(patsubst %/bin/java,%,$(filter %/bin/java,${JAVA_ON_PATH_REALPATH}))) + ifneq (,$(call is_java_home,${JAVA_ON_PATH_HOME})) + ifneq (${JAVA_ON_PATH_REALPATH},$(realpath ${DAIKON_JAVA_HOME}/bin/java)) + $(warning JAVA_HOME is ${DAIKON_JAVA_HOME}, but the java on the PATH is ${JAVA_ON_PATH_REALPATH}; make them agree) endif endif endif diff --git a/scripts/daikon.bashrc b/scripts/daikon.bashrc index 6b9fd72c65..e4d7f1a198 100644 --- a/scripts/daikon.bashrc +++ b/scripts/daikon.bashrc @@ -8,12 +8,22 @@ ## files to .class files but have not re-made the daikon.jar file. ## You should not need to edit this file. -# This logic is the same as in java/java-home.mk: ask the JVM that runs javac -# for its home directory, or else (on macOS) use /usr/libexec/java_home. -# "command" bypasses any alias for javac. +# This logic is the same as in java/java-home.mk: if javac on the PATH is (a +# symbolic link to) a file in a JDK, use that JDK; otherwise ask the JVM that +# runs javac for its home directory; otherwise (on macOS) use +# /usr/libexec/java_home. "command" bypasses any alias for javac. if [ -z "$JAVA_HOME" ] ; then if command -v javac > /dev/null 2>&1 ; then - JAVA_HOME=$(command javac -J-XshowSettings:properties -version 2>&1 | sed -n 's:^ *java\.home = ::p' | sed 's:/jre$::') + javac_realpath=$(realpath "$(command -v javac)" 2> /dev/null) + javac_jdk=${javac_realpath%/bin/javac} + # JDK 9 and later contain lib/jrt-fs.jar; JDK 8 contains jre/lib/rt.jar. + if [ "$javac_jdk" != "$javac_realpath" ] && [ -x "$javac_jdk/bin/java" ] \ + && { [ -f "$javac_jdk/lib/jrt-fs.jar" ] || [ -f "$javac_jdk/jre/lib/rt.jar" ]; } ; then + JAVA_HOME=$javac_jdk + else + JAVA_HOME=$(command javac -J-XshowSettings:properties -version 2>&1 | sed -n 's:^ *java\.home = ::p' | sed 's:/jre$::') + fi + unset javac_realpath javac_jdk fi if [ -z "$JAVA_HOME" ] && [ "$(uname)" = "Darwin" ] ; then JAVA_HOME=$(/usr/libexec/java_home 2> /dev/null) diff --git a/tests/Makefile.common b/tests/Makefile.common index 29d5e6fef8..2ae50d0a6d 100644 --- a/tests/Makefile.common +++ b/tests/Makefile.common @@ -157,7 +157,9 @@ endif -include $(COMMON_HERE)/Makefile.user # This is included after Makefile.user, so that Makefile.user can set JAVA_HOME. -# It sets JAVA_HOME, CHECKED_JAVA_HOME, and JAVA_RELEASE_NUMBER. +# It sets JAVA_HOME, DAIKON_JAVA_HOME, CHECKED_JAVA_HOME, and JAVA_RELEASE_NUMBER. +# The commands below use DAIKON_JAVA_HOME, which does not change if a project +# Makefile later reassigns JAVA_HOME. include ${DAIKONDIR}/java/java-home.mk # SOURCES is where all of the source code to be examined is located @@ -195,7 +197,7 @@ JAVAC ?= ${CHECKED_JAVA_HOME}/bin/javac JUNIT_CLASSPATH ?= -JAVALIB_CLASSPATH := ${JAVA_HOME}/jre/lib/rt.jar:${JAVA_HOME}/lib/tools.jar +JAVALIB_CLASSPATH := ${DAIKON_JAVA_HOME}/jre/lib/rt.jar:${DAIKON_JAVA_HOME}/lib/tools.jar DAIKON_CLASSPATH := ${DAIKONDIR}/java:${DAIKONDIR}/java/lib/*:${DAIKONDIR}/daikon.jar @@ -221,17 +223,16 @@ export LC_ALL=C default: help -# JAVA_RELEASE_NUMBER is set by java-home.mk. -# Without a JDK, the version variables are empty, so that targets that do not -# run Java (such as "clean") work. +# JAVA_RELEASE_NUMBER and java_at_least are defined by java-home.mk. +# Without a JDK, JAVA_RELEASE_NUMBER and the version variables are empty, so +# that targets that do not run Java (such as "clean") work. +JAVA23_OR_NEWER := $(call java_at_least,23) +JAVA21_OR_NEWER := $(call java_at_least,21) +JAVA19_OR_NEWER := $(call java_at_least,19) +JAVA17_OR_NEWER := $(call java_at_least,17) +JAVA11_OR_NEWER := $(call java_at_least,11) +JAVA8_OR_NEWER := $(call java_at_least,8) ifneq (,${JAVA_RELEASE_NUMBER}) - JAVA23_OR_NEWER := $(shell expr ${JAVA_RELEASE_NUMBER} \>= 23) - JAVA21_OR_NEWER := $(shell expr ${JAVA_RELEASE_NUMBER} \>= 21) - JAVA19_OR_NEWER := $(shell expr ${JAVA_RELEASE_NUMBER} \>= 19) - JAVA17_OR_NEWER := $(shell expr ${JAVA_RELEASE_NUMBER} \>= 17) - JAVA11_OR_NEWER := $(shell expr ${JAVA_RELEASE_NUMBER} \>= 11) - JAVA8_OR_NEWER := $(shell expr ${JAVA_RELEASE_NUMBER} \>= 8) - ifneq (${JAVA8_OR_NEWER}, 1) $(error Must use Java version 8 or later; found ${JAVA_RELEASE_NUMBER} in ${JAVA_VERSION_STRING}) endif From 5c69b4aeec7b85aa06b4c3538941d03e09990c32 Mon Sep 17 00:00:00 2001 From: Michael Ernst Date: Sat, 10 Oct 2026 18:47:38 -0700 Subject: [PATCH 6/7] Reduce this PR to shared JAVA_HOME detection Simplify the detection: follow the javac on the PATH to its JDK, or use /usr/libexec/java_home on macOS, without starting a JVM. Java version computation, using JAVA_HOME's tools, the PATH-vs-JAVA_HOME warning, and small unrelated fixes move to separate pull requests. --- Makefile | 25 +++--- java/Makefile | 91 +++++++++++++-------- java/java-home.mk | 100 +++--------------------- scripts/daikon.bashrc | 31 +++----- tests/Makefile | 5 -- tests/Makefile.common | 55 ++++++++----- tests/daikon-tests/Makefile | 4 +- tests/daikon-tests/junit/Makefile.user | 3 +- tests/daikon-tests/junit5/Makefile.user | 3 +- 9 files changed, 131 insertions(+), 186 deletions(-) diff --git a/Makefile b/Makefile index 6d3414cfdd..cbd012e213 100644 --- a/Makefile +++ b/Makefile @@ -20,9 +20,7 @@ ifneq "$(wildcard ${SORT_DIRECTORY_ORDER})" "${SORT_DIRECTORY_ORDER}" SORT_DIRECTORY_ORDER := sort endif -# Sets and exports JAVA_HOME (so that recursive invocations of make need not -# compute it), and defines CHECKED_JAVA_HOME and java_at_least. -include ${DAIKONDIR}/java/java-home.mk +JAVA_RELEASE_NUMBER := $(shell java -version 2>&1 | head -1 | cut -d'"' -f2 | sed '/^1\./s///' | cut -d'.' -f1 | sed 's/-ea//') ########################################################################## @@ -77,7 +75,7 @@ SCRIPT_PATHS := $(addprefix scripts/,${SCRIPT_FILES}) # This is so troublesome that it isn't used except as a list of dependences for make commands DAIKON_JAVA_FILES := $(shell find java -name '*daikon-java*' -prune -o -name '*.java' -print | ${SORT_DIRECTORY_ORDER}) # If we're building on JDK 23 or less, remove the java24 files from the list. -ifneq ($(call java_at_least,24),1) +ifeq ($(shell test ${JAVA_RELEASE_NUMBER} -lt 24; echo $$?),0) DAIKON_JAVA_FILES := $(filter-out %24.java, ${DAIKON_JAVA_FILES}) endif @@ -307,15 +305,15 @@ junit: # are working properly. quick-test: cd examples/java-examples/StackAr; \ - ${CHECKED_JAVA_HOME}/bin/javac -g DataStructures/*.java + javac -g DataStructures/*.java @echo "Running Chicory/Daikon" cd examples/java-examples/StackAr; \ - ${CHECKED_JAVA_HOME}/bin/java -cp ${QT_PATH} daikon.Chicory --daikon DataStructures.StackArTester + java -cp ${QT_PATH} daikon.Chicory --daikon DataStructures.StackArTester @echo "Running DynComp/Chicory/Daikon" cd examples/java-examples/StackAr; \ - ${CHECKED_JAVA_HOME}/bin/java -cp ${QT_PATH} daikon.DynComp DataStructures.StackArTester; \ - ${CHECKED_JAVA_HOME}/bin/java -cp ${QT_PATH} daikon.Chicory --comparability-file=StackArTester.decls-DynComp DataStructures.StackArTester; \ - ${CHECKED_JAVA_HOME}/bin/java -cp ${QT_PATH} daikon.Daikon StackArTester.dtrace.gz + java -cp ${QT_PATH} daikon.DynComp DataStructures.StackArTester; \ + java -cp ${QT_PATH} daikon.Chicory --comparability-file=StackArTester.decls-DynComp DataStructures.StackArTester; \ + java -cp ${QT_PATH} daikon.Daikon StackArTester.dtrace.gz # Sanity check, suitable for continuous integration. nightly-test: @@ -381,7 +379,7 @@ test-staged-dist: ${STAGING_DIR} ## Make sure that all of the class files are 1.8 (version 52) or earlier. (cd ${DISTTESTDIRJAVA} && find . \( -name '*.class' \) -print0 | xargs -0 -n 1 ${PLUME_SCRIPTS}/classfile_check_version 52) ## Test that we can rebuild the .class files from the .java files. - (cd ${DISTTESTDIRJAVA}/daikon/java; rm `find . -name '*.class'`; ${MAKE} JAVAC='${CHECKED_JAVA_HOME}/bin/javac' compile) + (cd ${DISTTESTDIRJAVA}/daikon/java; rm `find . -name '*.class'`; ${MAKE} JAVAC='javac' compile) ## Test that these new .class files work properly. ${MAKE} -C ${DISTTESTDIR}/daikon/java junit ## Test the main target of the makefile. @@ -456,8 +454,7 @@ staging: @echo "]2;Building Javadoc" mkdir ${STAGING_DIR}/download/api # Javadoc is skipped on Java 8. Hard-coded JAVA_HOME path is a hack. - (if [ -d /usr/lib/jvm/java-11 ]; then export JAVA_HOME=/usr/lib/jvm/java-11 PATH=/usr/lib/jvm/java-11/bin:$${PATH}; fi; cd java; ${MAKE} SKIP_JAVADOC=0 'JAVADOC_DEST=${STAGING_DIR}/download/api' javadoc) - @test -f ${STAGING_DIR}/download/api/index.html || (echo "Javadoc was not built; use Java 11 or later" && false) + (export PATH=/usr/lib/jvm/java-11/bin:${PATH}; cd java; ${MAKE} SKIP_JAVADOC=0 'JAVADOC_DEST=${STAGING_DIR}/download/api' javadoc) # Copy the documentation @echo "]2;Copying documentation" mkdir ${STAGING_DIR}/download/doc @@ -612,11 +609,11 @@ daikon.jar: ${DAIKON_JAVA_FILES} $(patsubst %,java/%,${DAIKON_RESOURCE_FILES}) | sort | xargs '-I{}' ${RSYNC_AR} '{}' ${TMPDIR}/daikon-jar # make hamcrest and junit4 overwrite junit5 to get our special versions - for filename in ${JAR_DIR}/java/lib/*.jar ${JAR_DIR}/java/lib/hamcrest*.jar ${JAR_DIR}/java/lib/junit-4*.jar; do cd ${TMPDIR}/daikon-jar; ${CHECKED_JAVA_HOME}/bin/jar xf $$filename ; done + for filename in ${JAR_DIR}/java/lib/*.jar ${JAR_DIR}/java/lib/hamcrest*.jar ${JAR_DIR}/java/lib/junit-4*.jar; do cd ${TMPDIR}/daikon-jar; jar xf $$filename ; done (cd java; ${RSYNC_AR} ${DAIKON_RESOURCE_FILES} ${TMPDIR}/daikon-jar) (cd java; ${RSYNC_AR} daikon/tools/runtimechecker/Main.doc daikon/tools/runtimechecker/InstrumentHandler.doc ${TMPDIR}/daikon-jar) cd ${TMPDIR}/daikon-jar && \ - ${CHECKED_JAVA_HOME}/bin/jar cfm $@ ${JAR_DIR}/java/daikon/chicory/manifest.txt * + jar cfm $@ ${JAR_DIR}/java/daikon/chicory/manifest.txt * mv ${TMPDIR}/daikon-jar/$@ $@ #rm -rf ${TMPDIR}/daikon-jar diff --git a/java/Makefile b/java/Makefile index 467e3b1bd1..aa63568c18 100644 --- a/java/Makefile +++ b/java/Makefile @@ -16,44 +16,70 @@ HTMLTOOLS ?= ${DAIKONDIR}/.utils/html-tools USER ?= daikonbuild -# These use the JDK at JAVA_HOME, which is set by java-home.mk (included below). -JAVA ?= ${CHECKED_JAVA_HOME}/bin/java -ea +JAVA ?= java -ea # For debugging, add -verbose flag to javac command -# JAVAC ?= ${CHECKED_JAVA_HOME}/bin/javac -g -Xlint:unchecked -JAVAC ?= ${CHECKED_JAVA_HOME}/bin/javac -JAVADOC ?= ${CHECKED_JAVA_HOME}/bin/javadoc -JAR ?= ${CHECKED_JAVA_HOME}/bin/jar +# JAVAC ?= javac -g -Xlint:unchecked +JAVAC ?= javac +JAVADOC ?= javadoc +JAR ?= jar BuildJDKTool = BuildJDK include ${DAIKONDIR}/java/java-home.mk +ifeq (,$(call is_jdk,${JAVA_HOME})) + $(error No JDK found at JAVA_HOME="${JAVA_HOME}"; set JAVA_HOME or put javac on the PATH) +endif # The "JAVANN" variables mean "at least version NN", except JAVA8. -# Without a JDK, they are all 0, so that targets that do not run Java (such as -# "clean") work. -JAVA8 := $(if $(filter 8,${JAVA_RELEASE_NUMBER}),1,0) -JAVA17 := $(if $(call java_at_least,17),1,0) -JAVA21 := $(if $(call java_at_least,21),1,0) +JAVA_VERSION_STRING_WITH_EA := $(shell javac -version 2>&1 | head -1 | cut "-d " -f2) +JAVA_VERSION_STRING := $(shell javac -version 2>&1 | head -1 | cut "-d " -f2 | sed 's/-ea//') +ifneq (,$(findstring start1.8,start${JAVA_VERSION_STRING})) + JAVA8 := 1 + JAVA17 := 0 + JAVA21 := 0 + JAVA24 := 0 + JAVA25 := 0 + JAVA27 := 0 + JAVA_RELEASE_NUMBER := 8 +else + JAVA8 := 0 + JAVA17 := 0 + JAVA21 := 0 + JAVA24 := 0 + JAVA25 := 0 + JAVA27 := 0 + JAVA_RELEASE_NUMBER := $(shell echo ${JAVA_VERSION_STRING} | sed 's/\([0-9]*\)\..*/\1/') +endif +ifeq ($(shell test ${JAVA_RELEASE_NUMBER} -ge 17; echo $$?),0) + JAVA17 := 1 +endif +ifeq ($(shell test ${JAVA_RELEASE_NUMBER} -ge 21; echo $$?),0) + JAVA21 := 1 +endif # JDK 24 isn't an LTS version, but we need it to know whether to compile Instrument24.java. -JAVA24 := $(if $(call java_at_least,24),1,0) -ifeq (${JAVA24}, 1) +ifeq ($(shell test ${JAVA_RELEASE_NUMBER} -ge 24; echo $$?),0) + JAVA24 := 1 BuildJDKTool = BuildJDK24 endif -JAVA25 := $(if $(call java_at_least,25),1,0) -JAVA27 := $(if $(call java_at_least,27),1,0) -ifeq (${JAVA_RELEASE_NUMBER},25) +ifeq ($(shell test ${JAVA_RELEASE_NUMBER} -ge 25; echo $$?),0) + JAVA25 := 1 +endif +ifeq ($(shell test ${JAVA_RELEASE_NUMBER} -eq 25; echo $$?),0) ifndef JAVA25_HOME - JAVA25_HOME := ${DAIKON_JAVA_HOME} + JAVA25_HOME := ${JAVA_HOME} endif endif -# Use 26, not 27, so that JDK 26 has a value for the --system flag below. -ifeq ($(call java_at_least,26),1) +ifeq ($(shell test ${JAVA_RELEASE_NUMBER} -ge 27; echo $$?),0) + JAVA27 := 1 +endif +# Use -ge 26, not -ge 27, so that JDK 26 has a value for the --system flag below. +ifeq ($(shell test ${JAVA_RELEASE_NUMBER} -ge 26; echo $$?),0) ifndef JAVA27_HOME - JAVA27_HOME := ${DAIKON_JAVA_HOME} + JAVA27_HOME := ${JAVA_HOME} endif endif -ifeq ($(call java_at_least,21),1) +ifeq ($(shell test ${JAVA_RELEASE_NUMBER} -gt 20; echo $$?),0) LINT_THIS_ESCAPE=-Xlint:-this-escape endif @@ -137,23 +163,22 @@ CHECKERFRAMEWORK_JAVAC ?= ${CHECKERFRAMEWORK_JAR_DIR}/javac.jar DAIKON_CLASSPATH := ${DAIKONDIR}/java/lib/junit-4.13.2-Daikon.jar:${DAIKONDIR}/java/lib/hamcrest-core-1.3-Daikon.jar:${DAIKONDIR}/java/lib/*:${DAIKONDIR}/java ## Add tools.jar and rt.jar, if they exist -ifneq "$(wildcard ${DAIKON_JAVA_HOME}/lib/tools.jar)" "" - DAIKON_CLASSPATH := ${DAIKON_CLASSPATH}:${DAIKON_JAVA_HOME}/lib/tools.jar +ifneq "$(wildcard ${JAVA_HOME}/lib/tools.jar)" "" + DAIKON_CLASSPATH := ${DAIKON_CLASSPATH}:${JAVA_HOME}/lib/tools.jar endif -ifneq "$(wildcard ${DAIKON_JAVA_HOME}/jre/lib/rt.jar)" "" - DAIKON_CLASSPATH := ${DAIKON_CLASSPATH}:${DAIKON_JAVA_HOME}/jre/lib/rt.jar +ifneq "$(wildcard ${JAVA_HOME}/jre/lib/rt.jar)" "" + DAIKON_CLASSPATH := ${DAIKON_CLASSPATH}:${JAVA_HOME}/jre/lib/rt.jar endif -# Use "=", not ":=", so that a missing JDK is reported only when a command is used. -JAVADOC_COMMAND = ${JAVADOC} -cp ${DAIKON_CLASSPATH} -JAVAC_COMMAND = ${JAVAC} -cp ${DAIKON_CLASSPATH} -Xlint -J-Xmx7g ${JAVAC_ARGS} -JAVA_COMMAND = ${JAVA} -cp ${DAIKON_CLASSPATH} +JAVADOC_COMMAND := ${JAVADOC} -cp ${DAIKON_CLASSPATH} +JAVAC_COMMAND := ${JAVAC} -cp ${DAIKON_CLASSPATH} -Xlint -J-Xmx7g ${JAVAC_ARGS} +JAVA_COMMAND := ${JAVA} -cp ${DAIKON_CLASSPATH} DOCLETPATH := ${DAIKONDIR}/java:$(wildcard ${DAIKONDIR}/java/lib/bcel-util-*.jar):$(wildcard ${DAIKONDIR}/java/lib/plume-util-*.jar):$(wildcard ${DAIKONDIR}/java/lib/reflection-util-*.jar):$(wildcard ${DAIKONDIR}/java/lib/hashmap-util-*.jar):${DAIKONDIR}/java/lib/daikon-plumelib.jar DOCLET_FLAGS := LINKOFFLINE := https://docs.oracle.com/javase/8/docs/api/ -ifeq ($(call java_at_least,11),1) +ifeq ($(shell expr ${JAVA_RELEASE_NUMBER} \>= 11),1) DOCLET_FLAGS := --add-exports jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED LINKOFFLINE := https://docs.oracle.com/en/java/javase/17/docs/api/ endif @@ -484,7 +509,7 @@ showvars-brief: @echo "CLASSPATH = ${CLASSPATH}" @echo "DAIKON_CLASSPATH = ${DAIKON_CLASSPATH}" @echo "JAVA_RELEASE_NUMBER = ${JAVA_RELEASE_NUMBER}" - @echo "JAVA_VERSION_STRING = ${JAVA_VERSION_STRING}" + @echo "JAVA_VERSION_STRING_WITH_EA = ${JAVA_VERSION_STRING_WITH_EA}" .PHONY: xlint xlint: daikon/Daikon.class @@ -1209,7 +1234,7 @@ junit-all: junit ### CLOVER := ${HOME}/clover -CLOVER_CMD = ${JAVA_COMMAND} -cp ${CLOVER}/clover-1.3.1/lib/clover.jar \ +CLOVER_CMD := ${JAVA_COMMAND} -cp ${CLOVER}/clover-1.3.1/lib/clover.jar \ com.cenqua.clover.CloverInstr -jdk14 CLOVER_CP := $(subst ${DAIKONDIR}/java,${CLOVER}/java,${DAIKON_CLASSPATH}) CLOVER_CP := ${CLOVER_CP}:${CLOVER}/clover-1.3.1/lib/clover.jar @@ -1307,7 +1332,7 @@ endif ## Make Javadoc with private fields for people working on source code. ## It is generated in the standard location, directory api/ api-private: ${PLUME_SCRIPTS} ${AUTO_GENERATED_FILES} -ifeq (${JAVA8}, 1) +ifeq ($(shell test ${JAVA_RELEASE_NUMBER} -eq 8; echo $$?),0) @echo "Skipping api-private target under Java 8" else mkdir -p ${JAVADOC_DEST} diff --git a/java/java-home.mk b/java/java-home.mk index 6f41e624cc..e45f508552 100644 --- a/java/java-home.mk +++ b/java/java-home.mk @@ -1,99 +1,23 @@ -# Makefile fragment that sets JAVA_HOME (if it is not already set to a non-empty -# value) and defines DAIKON_JAVA_HOME, CHECKED_JAVA_HOME, JAVA_VERSION_STRING, -# JAVA_RELEASE_NUMBER, and the function java_at_least. -# It is included by the top-level Makefile, java/Makefile, tests/Makefile, -# tests/Makefile.common, and tests/daikon-tests/Makefile. -# scripts/daikon.bashrc finds JAVA_HOME in the same way, but does not compute -# the Java version. +# Makefile fragment that sets JAVA_HOME, if it is not already set to a non-empty +# value, and defines the function is_jdk. +# It is included by java/Makefile and tests/Makefile.common. +# scripts/daikon.bashrc finds JAVA_HOME in the same way. -# $(call is_java_home,DIR) is non-empty if DIR contains a JDK or JRE. -# JDK 9 and later contain lib/jrt-fs.jar; JDK 8 contains jre/lib/rt.jar; JRE 8 contains lib/rt.jar. -is_java_home = $(and $(strip $(1)),$(wildcard $(1)/bin/java),$(wildcard $(1)/lib/jrt-fs.jar $(1)/jre/lib/rt.jar $(1)/lib/rt.jar)) # $(call is_jdk,DIR) is non-empty if DIR contains a JDK. -is_jdk = $(and $(call is_java_home,$(1)),$(wildcard $(1)/bin/javac)) +# JDK 9 and later contain lib/jrt-fs.jar; JDK 8 contains jre/lib/rt.jar. +is_jdk = $(and $(strip $(1)),$(wildcard $(1)/bin/javac),$(wildcard $(1)/lib/jrt-fs.jar $(1)/jre/lib/rt.jar)) -# The JDK that contains the javac on the PATH, or empty if the javac on the PATH -# is not (a symbolic link to) a file in a JDK. For example, it is empty if -# javac is a wrapper script (as used by jenv, asdf, or mise), or the macOS -# /usr/bin/javac stub. -JAVAC_ON_PATH_REALPATH := $(realpath $(shell command -v javac 2> /dev/null)) -JAVAC_ON_PATH_JDK := $(patsubst %/bin/javac,%,$(filter %/bin/javac,${JAVAC_ON_PATH_REALPATH})) -JAVAC_ON_PATH_JDK := $(if $(call is_jdk,${JAVAC_ON_PATH_JDK}),${JAVAC_ON_PATH_JDK}) - -ifeq ($(strip ${JAVA_HOME}),) - JAVA_HOME := ${JAVAC_ON_PATH_JDK} -endif -# Ask the JVM that runs javac for its home directory. This is slower because it -# starts a JVM, but it works even if javac on the PATH is a wrapper script. -# For JDK 8, java.home is the "jre" subdirectory of the JDK. +# If the javac on the PATH is (a symbolic link to) a file in a JDK, use that +# JDK. If javac is a wrapper script (as used by jenv, asdf, or mise), set +# JAVA_HOME instead. ifeq ($(strip ${JAVA_HOME}),) - JAVA_HOME := $(shell command -v javac > /dev/null 2>&1 && javac -J-XshowSettings:properties -version 2>&1 | sed -n 's:^ *java\.home = ::p' | sed 's:/jre$$::') + JAVAC_ON_PATH_JDK := $(patsubst %/bin/javac,%,$(filter %/bin/javac,$(realpath $(shell command -v javac 2> /dev/null)))) + JAVA_HOME := $(if $(call is_jdk,${JAVAC_ON_PATH_JDK}),${JAVAC_ON_PATH_JDK}) endif ifeq ($(strip ${JAVA_HOME}),) ifeq ($(shell uname), Darwin) JAVA_HOME := $(shell /usr/libexec/java_home 2> /dev/null) endif endif -# Recursive invocations of make use this value rather than recomputing it. +# Commands run by make, including recursive invocations of make, use this value. export JAVA_HOME - -# DAIKON_JAVA_HOME is the value of JAVA_HOME when this file is included. It -# does not change if a Makefile later reassigns JAVA_HOME, so the commands that -# use it always agree with the version variables below. -DAIKON_JAVA_HOME := ${JAVA_HOME} - -# CHECKED_JAVA_HOME is DAIKON_JAVA_HOME, but expanding it fails if -# DAIKON_JAVA_HOME does not contain a JDK. The check happens only when a -# variable that uses it is expanded, so targets that do not run Java (such as -# "clean") work without a JDK. -CHECKED_JAVA_HOME = $(if $(call is_jdk,${DAIKON_JAVA_HOME}),${DAIKON_JAVA_HOME},$(error No JDK found at JAVA_HOME="${DAIKON_JAVA_HOME}"; set JAVA_HOME or put javac on the PATH)) - -# The version of the Java installation at DAIKON_JAVA_HOME. Examples: -# "1.8.0_462", "25.0.1", "27-ea". It is read from the "release" file, to avoid -# starting a JVM. If there is no "release" file (as in Debian and Ubuntu's -# OpenJDK 8), it is obtained from "java -version". It is empty if JAVA_HOME -# does not contain a Java installation. It is an error if JAVA_HOME contains a -# Java installation but its version cannot be determined. -JAVA_VERSION_STRING := -ifneq (,$(call is_java_home,${DAIKON_JAVA_HOME})) - JAVA_VERSION_STRING := $(shell sed -n 's/^JAVA_VERSION="\(.*\)"/\1/p' '${DAIKON_JAVA_HOME}/release' 2> /dev/null) - ifeq (,${JAVA_VERSION_STRING}) - JAVA_VERSION_STRING := $(shell '${DAIKON_JAVA_HOME}/bin/java' -version 2>&1 | sed -n 's/^.* version "\([^"]*\)".*/\1/p' | head -1) - endif - ifeq (,${JAVA_VERSION_STRING}) - $(error Cannot determine the Java version of JAVA_HOME="${DAIKON_JAVA_HOME}") - endif -endif -# The major version, such as "8" or "25". It is empty if JAVA_VERSION_STRING is. -JAVA_RELEASE_NUMBER := $(firstword $(subst ., ,$(subst -, ,$(subst +, ,$(subst _, ,$(patsubst 1.%,%,${JAVA_VERSION_STRING})))))) - -# $(call java_at_least,NN) is "1" if JAVA_RELEASE_NUMBER is NN or greater, and -# is empty otherwise (including if there is no Java installation). -JAVA_HOME_MK_NUMBERS := $(shell seq 1 999) -java_at_least = $(if $(filter ${JAVA_RELEASE_NUMBER},$(wordlist $(1),999,${JAVA_HOME_MK_NUMBERS})),1) - -# Warn if the javac or java on the PATH is in a different JDK than JAVA_HOME, -# because some commands use the javac or java on the PATH. Skip the check if -# JAVA_HOME does not contain a JDK (expanding CHECKED_JAVA_HOME reports that), -# or if the program on the PATH is not within a Java installation (such as a -# wrapper script, or the macOS /usr/bin/javac stub). -# The check is done once for each combination of JAVA_HOME and PATH, not in -# every recursive invocation of make. -JAVA_HOME_MK_CHECK_KEY := ${DAIKON_JAVA_HOME}:${PATH} -ifneq (${DAIKON_JAVA_HOME_CHECKED},${JAVA_HOME_MK_CHECK_KEY}) - export DAIKON_JAVA_HOME_CHECKED := ${JAVA_HOME_MK_CHECK_KEY} - ifneq (,$(call is_jdk,${DAIKON_JAVA_HOME})) - ifneq (,${JAVAC_ON_PATH_JDK}) - ifneq (${JAVAC_ON_PATH_REALPATH},$(realpath ${DAIKON_JAVA_HOME}/bin/javac)) - $(warning JAVA_HOME is ${DAIKON_JAVA_HOME}, but the javac on the PATH is ${JAVAC_ON_PATH_REALPATH}; make them agree) - endif - endif - JAVA_ON_PATH_REALPATH := $(realpath $(shell command -v java 2> /dev/null)) - JAVA_ON_PATH_HOME := $(patsubst %/jre,%,$(patsubst %/bin/java,%,$(filter %/bin/java,${JAVA_ON_PATH_REALPATH}))) - ifneq (,$(call is_java_home,${JAVA_ON_PATH_HOME})) - ifneq (${JAVA_ON_PATH_REALPATH},$(realpath ${DAIKON_JAVA_HOME}/bin/java)) - $(warning JAVA_HOME is ${DAIKON_JAVA_HOME}, but the java on the PATH is ${JAVA_ON_PATH_REALPATH}; make them agree) - endif - endif - endif -endif diff --git a/scripts/daikon.bashrc b/scripts/daikon.bashrc index e4d7f1a198..9cf9b7a6ec 100644 --- a/scripts/daikon.bashrc +++ b/scripts/daikon.bashrc @@ -9,35 +9,26 @@ ## You should not need to edit this file. # This logic is the same as in java/java-home.mk: if javac on the PATH is (a -# symbolic link to) a file in a JDK, use that JDK; otherwise ask the JVM that -# runs javac for its home directory; otherwise (on macOS) use -# /usr/libexec/java_home. "command" bypasses any alias for javac. +# symbolic link to) a file in a JDK, use that JDK; otherwise, on macOS, use +# /usr/libexec/java_home. If javac is a wrapper script (as used by jenv, asdf, +# or mise), set JAVA_HOME instead. if [ -z "$JAVA_HOME" ] ; then - if command -v javac > /dev/null 2>&1 ; then - javac_realpath=$(realpath "$(command -v javac)" 2> /dev/null) - javac_jdk=${javac_realpath%/bin/javac} - # JDK 9 and later contain lib/jrt-fs.jar; JDK 8 contains jre/lib/rt.jar. - if [ "$javac_jdk" != "$javac_realpath" ] && [ -x "$javac_jdk/bin/java" ] \ - && { [ -f "$javac_jdk/lib/jrt-fs.jar" ] || [ -f "$javac_jdk/jre/lib/rt.jar" ]; } ; then - JAVA_HOME=$javac_jdk - else - JAVA_HOME=$(command javac -J-XshowSettings:properties -version 2>&1 | sed -n 's:^ *java\.home = ::p' | sed 's:/jre$::') - fi - unset javac_realpath javac_jdk - fi - if [ -z "$JAVA_HOME" ] && [ "$(uname)" = "Darwin" ] ; then + javac_realpath=$(realpath "$(type -P javac)" 2> /dev/null) + javac_jdk=${javac_realpath%/bin/javac} + # JDK 9 and later contain lib/jrt-fs.jar; JDK 8 contains jre/lib/rt.jar. + if [ "$javac_jdk" != "$javac_realpath" ] && [ -x "$javac_jdk/bin/javac" ] \ + && { [ -f "$javac_jdk/lib/jrt-fs.jar" ] || [ -f "$javac_jdk/jre/lib/rt.jar" ]; } ; then + JAVA_HOME=$javac_jdk + elif [ "$(uname)" = "Darwin" ] ; then JAVA_HOME=$(/usr/libexec/java_home 2> /dev/null) fi + unset javac_realpath javac_jdk fi export JAVA_HOME if [ ! -d "$JAVA_HOME" ]; then echo "Cannot infer JAVA_HOME; please set it. Aborting daikon.bashrc ." return 2 fi -# Running Daikon needs only a JRE, but building Daikon needs a JDK. -if [ ! -x "$JAVA_HOME/bin/javac" ]; then - echo "Warning: JAVA_HOME=$JAVA_HOME is not a JDK (it has no bin/javac); building Daikon requires a JDK." -fi if [ ${#BASH_SOURCE[@]} -eq 0 ]; then # Cannot infer DAIKONDIR. diff --git a/tests/Makefile b/tests/Makefile index 80e9402164..e238ef2bd2 100644 --- a/tests/Makefile +++ b/tests/Makefile @@ -34,11 +34,6 @@ else MPARG ?= endif -# DAIKONDIR is the clone of the 'daikon' project. -DAIKONDIR ?= $(realpath $(dir $(lastword ${MAKEFILE_LIST}))..) -# Sets and exports JAVA_HOME, so that the test directories need not compute it. -include ${DAIKONDIR}/java/java-home.mk - PLUME_SCRIPTS ?= ${DAIKONDIR}/.utils/plume-scripts all diffs: txt-diff non-txt-diff dyncomp-tests results diff --git a/tests/Makefile.common b/tests/Makefile.common index 2ae50d0a6d..e5d51b6e56 100644 --- a/tests/Makefile.common +++ b/tests/Makefile.common @@ -157,10 +157,10 @@ endif -include $(COMMON_HERE)/Makefile.user # This is included after Makefile.user, so that Makefile.user can set JAVA_HOME. -# It sets JAVA_HOME, DAIKON_JAVA_HOME, CHECKED_JAVA_HOME, and JAVA_RELEASE_NUMBER. -# The commands below use DAIKON_JAVA_HOME, which does not change if a project -# Makefile later reassigns JAVA_HOME. include ${DAIKONDIR}/java/java-home.mk +ifeq (,$(call is_jdk,${JAVA_HOME})) + $(error No JDK found at JAVA_HOME="${JAVA_HOME}"; set JAVA_HOME or put javac on the PATH) +endif # SOURCES is where all of the source code to be examined is located SOURCES?=$(COMMON_HERE)/sources @@ -192,19 +192,19 @@ ECLAT_SCRATCH = $(SCRATCH_FOR_DAIKON_BASE)/$(PROJECT)_eclat # CP_PARENTS := cp -pf --parents CP_PARENTS := rsync -aR -JAVA = ${CHECKED_JAVA_HOME}/bin/java -JAVAC ?= ${CHECKED_JAVA_HOME}/bin/javac +JAVA := ${JAVA_HOME}/bin/java +JAVAC ?= ${JAVA_HOME}/bin/javac JUNIT_CLASSPATH ?= -JAVALIB_CLASSPATH := ${DAIKON_JAVA_HOME}/jre/lib/rt.jar:${DAIKON_JAVA_HOME}/lib/tools.jar +JAVALIB_CLASSPATH := ${JAVA_HOME}/jre/lib/rt.jar:${JAVA_HOME}/lib/tools.jar DAIKON_CLASSPATH := ${DAIKONDIR}/java:${DAIKONDIR}/java/lib/*:${DAIKONDIR}/daikon.jar export DAIKON_TESTS_CLASSPATH := .:$(JAVALIB_CLASSPATH):$(SCRATCH_FOR_DAIKON):$(JUNIT_CLASSPATH):$(DAIKON_CLASSPATH) # Dynamic instrumentation based java front end command. -CHICORY = $(JAVA) -ea -cp "$(DAIKON_TESTS_CLASSPATH)" daikon.Chicory +CHICORY := $(JAVA) -ea -cp "$(DAIKON_TESTS_CLASSPATH)" daikon.Chicory CHICORY_EXEC := ${DAIKONDIR}/java/daikon/Chicory.class # As of JDK 1.3 (and 1.4, I think), don't use "-server" here. @@ -223,19 +223,32 @@ export LC_ALL=C default: help -# JAVA_RELEASE_NUMBER and java_at_least are defined by java-home.mk. -# Without a JDK, JAVA_RELEASE_NUMBER and the version variables are empty, so -# that targets that do not run Java (such as "clean") work. -JAVA23_OR_NEWER := $(call java_at_least,23) -JAVA21_OR_NEWER := $(call java_at_least,21) -JAVA19_OR_NEWER := $(call java_at_least,19) -JAVA17_OR_NEWER := $(call java_at_least,17) -JAVA11_OR_NEWER := $(call java_at_least,11) -JAVA8_OR_NEWER := $(call java_at_least,8) -ifneq (,${JAVA_RELEASE_NUMBER}) - ifneq (${JAVA8_OR_NEWER}, 1) +# The general form of a Java version number prior to Java 9 +# is 1.n.n_n. For Java 9 or later it is n.n.n.n. +# Currently, we only look for the release number, which is +# the first 'n' in either case. +# Note that version 11 (or 21 or ...) requires the 'start' anchor below +# to make the old vs. new test work properly. + +JAVA_VERSION_STRING := $(shell javac -version 2>&1 | head -1 | cut "-d " -f2 | sed 's/-ea//') +startJVS := $(shell echo start${JAVA_VERSION_STRING}) +ifneq (,$(findstring start1.8,$(startJVS))) + JAVA_RELEASE_NUMBER := 8 +else + # Old Java releases (e.g. 1.7) will set JAVA_RELEASE_NUMBER to 1. + # This will cause the JAVA8_OR_NEWER test to fail as desired. + JAVA_RELEASE_NUMBER := $(shell echo ${JAVA_VERSION_STRING} | sed 's/\([0-9]\+\)\(\..*\)\?\(-.*\)\?/\1/') +endif + +JAVA23_OR_NEWER := $(shell expr ${JAVA_RELEASE_NUMBER} \>= 23) +JAVA21_OR_NEWER := $(shell expr ${JAVA_RELEASE_NUMBER} \>= 21) +JAVA19_OR_NEWER := $(shell expr ${JAVA_RELEASE_NUMBER} \>= 19) +JAVA17_OR_NEWER := $(shell expr ${JAVA_RELEASE_NUMBER} \>= 17) +JAVA11_OR_NEWER := $(shell expr ${JAVA_RELEASE_NUMBER} \>= 11) +JAVA8_OR_NEWER := $(shell expr ${JAVA_RELEASE_NUMBER} \>= 8) + +ifneq (${JAVA8_OR_NEWER}, 1) $(error Must use Java version 8 or later; found ${JAVA_RELEASE_NUMBER} in ${JAVA_VERSION_STRING}) - endif endif # goal_name is a function that will be called like "$(call goal_name, FILENAME)" @@ -284,12 +297,12 @@ endif # If specified, setup for the old declaration format ifeq (old,${DECL_FORMAT}) - CHICORY += --new_decl_format=false + CHICORY := $(CHICORY) --new_decl_format=false endif # "make PROFILE_JAVA=1 " to get profile information ifdef PROFILE_JAVA -RUN_JAVA += -Xrunhprof:cpu=samples,file=${PROJECT_FILE}.profile.txt,depth=3 +RUN_JAVA:=$(RUN_JAVA) -Xrunhprof:cpu=samples,file=${PROJECT_FILE}.profile.txt,depth=3 endif # To turn off Simplify, put this in the project's own Makefile, after diff --git a/tests/daikon-tests/Makefile b/tests/daikon-tests/Makefile index ef19b0bb08..3342685d68 100644 --- a/tests/daikon-tests/Makefile +++ b/tests/daikon-tests/Makefile @@ -2,9 +2,7 @@ # Read Makefile in parent directory to see what this all means # DAIKONDIR is the clone of the 'daikon' project. -DAIKONDIR ?= $(realpath $(dir $(lastword ${MAKEFILE_LIST}))../..) -# Sets and exports JAVA_HOME, so that the test directories need not compute it. -include ${DAIKONDIR}/java/java-home.mk +DAIKONDIR ?= $(realpath $(dir $(lastword ${MAKEFILE_LIST}))..) SCRIPTS=${DAIKONDIR}/scripts # Help system diff --git a/tests/daikon-tests/junit/Makefile.user b/tests/daikon-tests/junit/Makefile.user index 169d40bfae..da30afc81a 100644 --- a/tests/daikon-tests/junit/Makefile.user +++ b/tests/daikon-tests/junit/Makefile.user @@ -2,4 +2,5 @@ # need junit.jar on classpath to compile all junit references JUNIT_CLASSPATH := ${DAIKONDIR}/java/lib/* -JAVAC = ${CHECKED_JAVA_HOME}/bin/javac -cp .:$(JUNIT_CLASSPATH) +# "=", not ":=", because JAVA_HOME is set after this file is included. +JAVAC = ${JAVA_HOME}/bin/javac -cp .:$(JUNIT_CLASSPATH) diff --git a/tests/daikon-tests/junit5/Makefile.user b/tests/daikon-tests/junit5/Makefile.user index 90c4b5cdad..ad3c351c7e 100644 --- a/tests/daikon-tests/junit5/Makefile.user +++ b/tests/daikon-tests/junit5/Makefile.user @@ -3,4 +3,5 @@ # need junit.jar on classpath to compile all junit references # explicit order is necessary to get versions built with JDK 8 not 5 JUNIT_CLASSPATH := ${DAIKONDIR}/java/lib/junit-4.13.2-Daikon.jar:${DAIKONDIR}/java/lib/hamcrest-core-1.3-Daikon.jar:${DAIKONDIR}/java/lib/* -JAVAC = ${CHECKED_JAVA_HOME}/bin/javac -cp .:$(JUNIT_CLASSPATH) +# "=", not ":=", because JAVA_HOME is set after this file is included. +JAVAC = ${JAVA_HOME}/bin/javac -cp .:$(JUNIT_CLASSPATH) From dc096825382095589601b6a9863091dfd31309e4 Mon Sep 17 00:00:00 2001 From: Michael Ernst Date: Sat, 10 Oct 2026 18:48:42 -0700 Subject: [PATCH 7/7] Compute the Java version once, from the JDK at JAVA_HOME java-home.mk defines JAVA_VERSION_STRING, JAVA_RELEASE_NUMBER, and the function java_at_least. They replace the separate version computations in the top-level Makefile, java/Makefile, and tests/Makefile.common, which used whichever java or javac was on the PATH. --- Makefile | 4 ++-- java/Makefile | 54 +++++++++++-------------------------------- java/java-home.mk | 20 ++++++++++++++-- tests/Makefile.common | 32 +++++++------------------ 4 files changed, 42 insertions(+), 68 deletions(-) diff --git a/Makefile b/Makefile index cbd012e213..c4c6ee8538 100644 --- a/Makefile +++ b/Makefile @@ -20,7 +20,7 @@ ifneq "$(wildcard ${SORT_DIRECTORY_ORDER})" "${SORT_DIRECTORY_ORDER}" SORT_DIRECTORY_ORDER := sort endif -JAVA_RELEASE_NUMBER := $(shell java -version 2>&1 | head -1 | cut -d'"' -f2 | sed '/^1\./s///' | cut -d'.' -f1 | sed 's/-ea//') +include ${DAIKONDIR}/java/java-home.mk ########################################################################## @@ -75,7 +75,7 @@ SCRIPT_PATHS := $(addprefix scripts/,${SCRIPT_FILES}) # This is so troublesome that it isn't used except as a list of dependences for make commands DAIKON_JAVA_FILES := $(shell find java -name '*daikon-java*' -prune -o -name '*.java' -print | ${SORT_DIRECTORY_ORDER}) # If we're building on JDK 23 or less, remove the java24 files from the list. -ifeq ($(shell test ${JAVA_RELEASE_NUMBER} -lt 24; echo $$?),0) +ifneq ($(call java_at_least,24),1) DAIKON_JAVA_FILES := $(filter-out %24.java, ${DAIKON_JAVA_FILES}) endif diff --git a/java/Makefile b/java/Makefile index aa63568c18..498bc8888e 100644 --- a/java/Makefile +++ b/java/Makefile @@ -31,55 +31,29 @@ ifeq (,$(call is_jdk,${JAVA_HOME})) endif # The "JAVANN" variables mean "at least version NN", except JAVA8. -JAVA_VERSION_STRING_WITH_EA := $(shell javac -version 2>&1 | head -1 | cut "-d " -f2) -JAVA_VERSION_STRING := $(shell javac -version 2>&1 | head -1 | cut "-d " -f2 | sed 's/-ea//') -ifneq (,$(findstring start1.8,start${JAVA_VERSION_STRING})) - JAVA8 := 1 - JAVA17 := 0 - JAVA21 := 0 - JAVA24 := 0 - JAVA25 := 0 - JAVA27 := 0 - JAVA_RELEASE_NUMBER := 8 -else - JAVA8 := 0 - JAVA17 := 0 - JAVA21 := 0 - JAVA24 := 0 - JAVA25 := 0 - JAVA27 := 0 - JAVA_RELEASE_NUMBER := $(shell echo ${JAVA_VERSION_STRING} | sed 's/\([0-9]*\)\..*/\1/') -endif -ifeq ($(shell test ${JAVA_RELEASE_NUMBER} -ge 17; echo $$?),0) - JAVA17 := 1 -endif -ifeq ($(shell test ${JAVA_RELEASE_NUMBER} -ge 21; echo $$?),0) - JAVA21 := 1 -endif +JAVA8 := $(if $(filter 8,${JAVA_RELEASE_NUMBER}),1,0) +JAVA17 := $(if $(call java_at_least,17),1,0) +JAVA21 := $(if $(call java_at_least,21),1,0) # JDK 24 isn't an LTS version, but we need it to know whether to compile Instrument24.java. -ifeq ($(shell test ${JAVA_RELEASE_NUMBER} -ge 24; echo $$?),0) - JAVA24 := 1 +JAVA24 := $(if $(call java_at_least,24),1,0) +ifeq (${JAVA24}, 1) BuildJDKTool = BuildJDK24 endif -ifeq ($(shell test ${JAVA_RELEASE_NUMBER} -ge 25; echo $$?),0) - JAVA25 := 1 -endif -ifeq ($(shell test ${JAVA_RELEASE_NUMBER} -eq 25; echo $$?),0) +JAVA25 := $(if $(call java_at_least,25),1,0) +JAVA27 := $(if $(call java_at_least,27),1,0) +ifeq (${JAVA_RELEASE_NUMBER},25) ifndef JAVA25_HOME JAVA25_HOME := ${JAVA_HOME} endif endif -ifeq ($(shell test ${JAVA_RELEASE_NUMBER} -ge 27; echo $$?),0) - JAVA27 := 1 -endif -# Use -ge 26, not -ge 27, so that JDK 26 has a value for the --system flag below. -ifeq ($(shell test ${JAVA_RELEASE_NUMBER} -ge 26; echo $$?),0) +# Use 26, not 27, so that JDK 26 has a value for the --system flag below. +ifeq ($(call java_at_least,26),1) ifndef JAVA27_HOME JAVA27_HOME := ${JAVA_HOME} endif endif -ifeq ($(shell test ${JAVA_RELEASE_NUMBER} -gt 20; echo $$?),0) +ifeq ($(call java_at_least,21),1) LINT_THIS_ESCAPE=-Xlint:-this-escape endif @@ -178,7 +152,7 @@ DOCLETPATH := ${DAIKONDIR}/java:$(wildcard ${DAIKONDIR}/java/lib/bcel-util-*.jar DOCLET_FLAGS := LINKOFFLINE := https://docs.oracle.com/javase/8/docs/api/ -ifeq ($(shell expr ${JAVA_RELEASE_NUMBER} \>= 11),1) +ifeq ($(call java_at_least,11),1) DOCLET_FLAGS := --add-exports jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED LINKOFFLINE := https://docs.oracle.com/en/java/javase/17/docs/api/ endif @@ -509,7 +483,7 @@ showvars-brief: @echo "CLASSPATH = ${CLASSPATH}" @echo "DAIKON_CLASSPATH = ${DAIKON_CLASSPATH}" @echo "JAVA_RELEASE_NUMBER = ${JAVA_RELEASE_NUMBER}" - @echo "JAVA_VERSION_STRING_WITH_EA = ${JAVA_VERSION_STRING_WITH_EA}" + @echo "JAVA_VERSION_STRING = ${JAVA_VERSION_STRING}" .PHONY: xlint xlint: daikon/Daikon.class @@ -1332,7 +1306,7 @@ endif ## Make Javadoc with private fields for people working on source code. ## It is generated in the standard location, directory api/ api-private: ${PLUME_SCRIPTS} ${AUTO_GENERATED_FILES} -ifeq ($(shell test ${JAVA_RELEASE_NUMBER} -eq 8; echo $$?),0) +ifeq (${JAVA8}, 1) @echo "Skipping api-private target under Java 8" else mkdir -p ${JAVADOC_DEST} diff --git a/java/java-home.mk b/java/java-home.mk index e45f508552..1c5ad2177f 100644 --- a/java/java-home.mk +++ b/java/java-home.mk @@ -1,6 +1,6 @@ # Makefile fragment that sets JAVA_HOME, if it is not already set to a non-empty -# value, and defines the function is_jdk. -# It is included by java/Makefile and tests/Makefile.common. +# value, and defines variables and functions about the JDK there. +# It is included by the top-level Makefile, java/Makefile, and tests/Makefile.common. # scripts/daikon.bashrc finds JAVA_HOME in the same way. # $(call is_jdk,DIR) is non-empty if DIR contains a JDK. @@ -21,3 +21,19 @@ ifeq ($(strip ${JAVA_HOME}),) endif # Commands run by make, including recursive invocations of make, use this value. export JAVA_HOME + +# The version of the JDK at JAVA_HOME, such as "1.8.0_462", "25.0.1", or +# "27-ea". It is empty if JAVA_HOME does not contain a JDK. +JAVA_VERSION_STRING := +ifneq (,$(call is_jdk,${JAVA_HOME})) + JAVA_VERSION_STRING := $(shell '${JAVA_HOME}/bin/java' -version 2>&1 | sed -n 's/^.* version "\([^"]*\)".*/\1/p' | head -1) + ifeq (,${JAVA_VERSION_STRING}) + $(error Cannot determine the Java version of JAVA_HOME="${JAVA_HOME}") + endif +endif +# The major version, such as "8" or "25". It is empty if JAVA_VERSION_STRING is. +JAVA_RELEASE_NUMBER := $(firstword $(subst ., ,$(subst -, ,$(subst +, ,$(subst _, ,$(patsubst 1.%,%,${JAVA_VERSION_STRING})))))) + +# $(call java_at_least,NN) is "1" if the JDK at JAVA_HOME is version NN or +# later, and is empty otherwise (including if there is no JDK). +java_at_least = $(shell test 0${JAVA_RELEASE_NUMBER} -ge $(1) && echo 1) diff --git a/tests/Makefile.common b/tests/Makefile.common index e5d51b6e56..5f389bfc15 100644 --- a/tests/Makefile.common +++ b/tests/Makefile.common @@ -223,32 +223,16 @@ export LC_ALL=C default: help -# The general form of a Java version number prior to Java 9 -# is 1.n.n_n. For Java 9 or later it is n.n.n.n. -# Currently, we only look for the release number, which is -# the first 'n' in either case. -# Note that version 11 (or 21 or ...) requires the 'start' anchor below -# to make the old vs. new test work properly. - -JAVA_VERSION_STRING := $(shell javac -version 2>&1 | head -1 | cut "-d " -f2 | sed 's/-ea//') -startJVS := $(shell echo start${JAVA_VERSION_STRING}) -ifneq (,$(findstring start1.8,$(startJVS))) - JAVA_RELEASE_NUMBER := 8 -else - # Old Java releases (e.g. 1.7) will set JAVA_RELEASE_NUMBER to 1. - # This will cause the JAVA8_OR_NEWER test to fail as desired. - JAVA_RELEASE_NUMBER := $(shell echo ${JAVA_VERSION_STRING} | sed 's/\([0-9]\+\)\(\..*\)\?\(-.*\)\?/\1/') -endif - -JAVA23_OR_NEWER := $(shell expr ${JAVA_RELEASE_NUMBER} \>= 23) -JAVA21_OR_NEWER := $(shell expr ${JAVA_RELEASE_NUMBER} \>= 21) -JAVA19_OR_NEWER := $(shell expr ${JAVA_RELEASE_NUMBER} \>= 19) -JAVA17_OR_NEWER := $(shell expr ${JAVA_RELEASE_NUMBER} \>= 17) -JAVA11_OR_NEWER := $(shell expr ${JAVA_RELEASE_NUMBER} \>= 11) -JAVA8_OR_NEWER := $(shell expr ${JAVA_RELEASE_NUMBER} \>= 8) +# JAVA_RELEASE_NUMBER and java_at_least are defined by java-home.mk. +JAVA23_OR_NEWER := $(call java_at_least,23) +JAVA21_OR_NEWER := $(call java_at_least,21) +JAVA19_OR_NEWER := $(call java_at_least,19) +JAVA17_OR_NEWER := $(call java_at_least,17) +JAVA11_OR_NEWER := $(call java_at_least,11) +JAVA8_OR_NEWER := $(call java_at_least,8) ifneq (${JAVA8_OR_NEWER}, 1) - $(error Must use Java version 8 or later; found ${JAVA_RELEASE_NUMBER} in ${JAVA_VERSION_STRING}) + $(error Must use Java version 8 or later; found ${JAVA_RELEASE_NUMBER} in ${JAVA_VERSION_STRING}) endif # goal_name is a function that will be called like "$(call goal_name, FILENAME)"