(ns dev.build.check-class-file-versions (:require [clojure.java.io :as io] [clojure.string :as str] [metabuild-common.core :as u]) (:import (java.io DataInputStream FileInputStream InputStream) (java.nio.file Files FileVisitOption OpenOption Path) (java.util.function BiPredicate))) | |
(set! *warn-on-reflection* true) | |
See https://en.wikipedia.org/wiki/Javaversionhistory if you need to add more versions here. | (def ^:private java-version->class-file-version {21 65 20 64 19 63 18 62 17 61 16 60 15 59 14 58 13 57 12 56 11 55 10 54 9 53 8 52}) |
(defn- do-with-file-input-stream [file f] (if (instance? InputStream file) (f file) (with-open [is (FileInputStream. (io/file file))] (f is)))) | |
Get the (bytecode) version for a | (defn class-file-version [class-file] (do-with-file-input-stream class-file (fn [^InputStream class-file-input-stream] (with-open [is (DataInputStream. class-file-input-stream)] (let [first-four-bytes (.readInt is)] (assert (= first-four-bytes (unchecked-int 0xCAFEBABE)) (format "Invalid Java class file: wrong first four bytes, got 0x%H, expected 0xCAFEBABE" first-four-bytes))) (let [minor-version (bit-and (.readShort is) 0xFFFF) major-version (bit-and (.readShort is) 0xFFFF)] (double (+ major-version (/ minor-version 100.0)))))))) |
Return an (f ^Path path) | (defn reducible-jar-files [path-to-jar pred] (reify clojure.lang.IReduceInit (reduce [_this rf init] (u/with-open-jar-file-system [filesystem path-to-jar] (let [path (.getPath filesystem "/" (make-array String 0)) stream (Files/find path #_max-depth Integer/MAX_VALUE (reify BiPredicate (test [_this path _file-attributes] (boolean (pred path)))) ^"[Ljava.nio.file.FileVisitOption;" (make-array FileVisitOption 0)) it (.iterator stream)] (reduce rf init (iterator-seq it))))))) |
Return an | (defn reducible-class-files [path-to-jar] (reducible-jar-files path-to-jar (fn [^Path path] (str/ends-with? (str path) ".class")))) |
Calls (f ^InputStream input-stream) with an open | (defn do-with-open-input-stream-for-path [^Path path f] (with-open [is (Files/newInputStream path ^"[Ljava.nio.file.OpenOption;" (make-array OpenOption 0))] (f is))) |
Find files in a JAR ( | (defn find-files-compiled-for-wrong-java-version [path-to-jar max-java-version] (let [max-class-file-version (or (java-version->class-file-version max-java-version) (throw (ex-info (format "Don't know the bytecode version for Java version %s" max-java-version) {:max-java-version max-java-version}))) files (reducible-class-files path-to-jar)] (reduce (fn [_acc ^Path path] (do-with-open-input-stream-for-path path (fn [^InputStream is] (let [version (class-file-version is)] (when (> version max-class-file-version) (println (str path) version)))))) nil files))) |