diff --git a/src/BytecodeVersionJarValidatorJP.java b/src/BytecodeVersionJarValidatorJP.java new file mode 100644 index 0000000..501515b --- /dev/null +++ b/src/BytecodeVersionJarValidatorJP.java @@ -0,0 +1,110 @@ +package org.fedoraproject.javapackages.validator.validators.jp; + +import java.nio.file.Path; +import java.util.Map; +import java.util.Set; + +import org.fedoraproject.javadeptools.rpm.RpmFile; +import org.fedoraproject.javapackages.validator.Decorated; +import org.fedoraproject.javapackages.validator.TmtTest; +import org.fedoraproject.javapackages.validator.validators.BytecodeVersionJarValidator; + +@TmtTest("/java/jp/bytecode_version") +public class BytecodeVersionJarValidatorJP extends BytecodeVersionJarValidator { + private Set packagesReqVersion8 = Set.of(new String[] { + "maven", + "apache-commons-cli", + "apache-commons-codec", + "apache-commons-io", + "apache-commons-lang3", + "atinject", + "cdi-api", + "google-guice", + "guava", + "httpcomponents-client", + "httpcomponents-core", + "jakarta-annotations", + "jansi", + "jsr-305", + "maven-resolver", + "maven-shared-utils", + "maven-wagon", + "plexus-cipher", + "plexus-classworlds", + "plexus-containers", + "plexus-interpolation", + "plexus-sec-dispatcher", + "plexus-utils", + "sisu", + "slf4j", + + "ant", + "antlr", + "apache-commons-net", + "bcel", + "bsf", + "jakarta-activation1", + "jakarta-mail", + "jakarta-oro", + "jdepend", + "jsch", + "jzlib", + "regexp", + "xalan-j2", + "xerces-j2", + "xml-commons-apis", + "xml-commons-resolver", + }); + + @Override + public void validate(RpmFile rpm, Path jarPath, Map classVersions) { + for (var entry : classVersions.entrySet()) { + Path classPath = entry.getKey(); + int version = entry.getValue(); + int maxAllowedVersion = 17; + int expected; + + if (classPath.endsWith("module-info.class") || classPath.endsWith("package-info.class")) { + // pass + } else if (classPath.startsWith("META-INF/versions")) { + // JEP 238: Multi-Release JAR Files + // https://openjdk.org/jeps/238 + + if (version > 44 + (expected = Short.parseShort(classPath.getName(2).toString()))) { + fail("{0}: {1}: {2}: class bytecode version is {3} which is larger than {4}", + Decorated.rpm(rpm), + Decorated.custom(jarPath, DECORATION_JAR), + Decorated.struct(classPath), + Decorated.actual(version), + Decorated.expected(expected)); + continue; + } else { + // pass + } + } else if (packagesReqVersion8.contains(rpm.getInfo().getPackageName()) && version > 44 + 8) { + fail("{0}: {1}: {2}: class bytecode version is {3} which is larger than {4} (package is required to have a bytecode version compatible with JVM 8)", + Decorated.rpm(rpm), + Decorated.custom(jarPath, DECORATION_JAR), + Decorated.struct(classPath), + Decorated.actual(version), + Decorated.expected(44 + 8)); + continue; + } else if (version > 44 + maxAllowedVersion) { + fail("{0}: {1}: {2}: class bytecode version is {3} which is larger than {4} (all packages should have bytecode version compatible with JVM {5})", + Decorated.rpm(rpm), + Decorated.custom(jarPath, DECORATION_JAR), + Decorated.struct(classPath), + Decorated.actual(version), + Decorated.expected(44 + maxAllowedVersion), + Decorated.plain(maxAllowedVersion)); + continue; + } + + pass("{0}: {1}: {2}: class bytecode version: {3}", + Decorated.rpm(rpm), + Decorated.custom(jarPath, DECORATION_JAR), + Decorated.struct(classPath), + Decorated.actual(version)); + } + } +} diff --git a/src/DuplicateFileValidatorJP.java b/src/DuplicateFileValidatorJP.java new file mode 100644 index 0000000..aaad77b --- /dev/null +++ b/src/DuplicateFileValidatorJP.java @@ -0,0 +1,29 @@ +package org.fedoraproject.javapackages.validator.validators.jp; + +import java.io.IOException; +import java.nio.file.Path; +import java.util.Collection; + +import org.fedoraproject.javadeptools.rpm.RpmInfo; +import org.fedoraproject.javapackages.validator.TmtTest; +import org.fedoraproject.javapackages.validator.validators.DuplicateFileValidator.DuplicateFileValidatorDefault; + +@TmtTest("/jp/duplicate_files") +public class DuplicateFileValidatorJP extends DuplicateFileValidatorDefault { + @Override + public boolean allowedDuplicateFile(Path path, Collection providerRpms) throws IOException { + if (path.toString().startsWith("/usr/share/licenses/")) { + return providerRpms.stream().map(RpmInfo::getPackageName).distinct().count() == 1; + } + + if (providerRpms.stream().allMatch(rpm -> rpm.getName().startsWith("maven-openjdk"))) { + return true; + } + + if (providerRpms.stream().allMatch(rpm -> rpm.getName().startsWith("maven-local-openjdk"))) { + return true; + } + + return false; + } +} diff --git a/src/FilesValidatorJP.java b/src/FilesValidatorJP.java new file mode 100644 index 0000000..58a0935 --- /dev/null +++ b/src/FilesValidatorJP.java @@ -0,0 +1,343 @@ +package org.fedoraproject.javapackages.validator.validators.jp; + +import java.nio.file.Path; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.TreeMap; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.fedoraproject.javadeptools.rpm.RpmInfo; +import org.fedoraproject.javapackages.validator.TmtTest; +import org.fedoraproject.javapackages.validator.validators.FilesValidator; + +@TmtTest("/jp/files") +public class FilesValidatorJP extends FilesValidator { + private Map patternsCache = new TreeMap<>(); + + private static String substitute(RpmInfo rpm, String text) { + return text + .replace("${package}", rpm.getPackageName()) + .replace("${rpm.name}", rpm.getName()) + ; + } + + private static class Prefix { + static final String ETC = "/etc/${package}"; + static final String USR_BIN = "/usr/bin/"; + static final String USR_LIB_BUILD_ID = "/usr/lib/.build-id"; + } + + private static Map> nameAliases = new TreeMap<>(); + static { + nameAliases.put("maven-resolver", List.of("aether")); + nameAliases.put("google-guice", List.of("guice")); + nameAliases.put("google-guice-javadoc", List.of("guice-parent")); + nameAliases.put("jakarta-mail", List.of("javamail", "javax.mail")); + } + + private static boolean isAlias(String rpmName, String alias) { + return nameAliases.getOrDefault(rpmName, Collections.emptyList()).stream().anyMatch(a -> alias.equals(a)); + } + + private static Map> prefixes = new TreeMap<>(); + static { + prefixes.put("ant", List.of(Prefix.ETC, Prefix.USR_BIN)); + prefixes.put("antlr", List.of(Prefix.USR_BIN)); + prefixes.put("aqute-bnd", List.of(Prefix.USR_BIN)); + prefixes.put("byaccj", List.of(Prefix.USR_BIN, Prefix.USR_LIB_BUILD_ID)); + prefixes.put("jansi", List.of(Prefix.USR_LIB_BUILD_ID)); + prefixes.put("java_cup", List.of(Prefix.USR_BIN)); + prefixes.put("javapackages-bootstrap", List.of("/usr/lib/java/javapackages-bootstrap", "/usr/lib/rpm", "/etc/rpm", "/usr/libexec/javapackages-bootstrap")); + prefixes.put("javapackages-tools", List.of(Prefix.USR_BIN, "/usr/share/xmvn/conf", "/usr/lib/rpm", "/usr/share/java-utils/", "/usr/lib/eclipse", "/usr/share/eclipse", "/etc/java", "/etc/jvm", "/usr/lib/java", "/usr/lib/jvm", "/etc/ivy", "/etc/ant.d")); + prefixes.put("jflex", List.of(Prefix.USR_BIN)); + prefixes.put("maven", List.of(Prefix.ETC)); + prefixes.put("modello", List.of(Prefix.USR_BIN)); + prefixes.put("objectweb-asm", List.of(Prefix.USR_BIN)); + prefixes.put("xerces-j2", List.of(Prefix.USR_BIN)); + prefixes.put("xml-commons-resolver", List.of(Prefix.USR_BIN)); + prefixes.put("xmvn", List.of(Prefix.USR_BIN)); + prefixes.put("xmvn-generator", List.of("/usr/lib/rpm", "/etc/rpm")); + } + + private static Map> exceptionalFiles = new TreeMap<>(); + static { + exceptionalFiles.put("aqute-bnd", List.of("/etc/ant.d/aqute-bnd")); + exceptionalFiles.put("maven", List.of("/etc/m2.conf", "/etc/java/maven.conf")); + exceptionalFiles.put("jansi", List.of("/usr/lib/jansi", "/usr/lib/jansi/libjansi.so", "/usr/lib/java/jansi")); + exceptionalFiles.put("javapackages-tools", List.of("/usr/share/java-utils", "/usr/share/xmvn", "/usr/share/ivy-xmls", "/usr/share/java", "/usr/share/javadoc", "/usr/share/jvm", "/usr/share/jvm-common", "/usr/share/maven-metadata", "/usr/share/maven-poms")); + } + + private static boolean namesRelated(String lhs, String rhs) { + int index = 0; + + while (index != lhs.length() && index != rhs.length() && lhs.charAt(index) == rhs.charAt(index)) { + ++index; + } + + if (index == lhs.length() && index == rhs.length()) { + return true; + } + + if (lhs.length() > rhs.length()) { + String temp = lhs; + lhs = rhs; + rhs = temp; + } + + if (index == lhs.length() && rhs.charAt(index) == '-') { + return true; + } + + if (index > 0 && lhs.charAt(index - 1) == '-' && rhs.charAt(index - 1) == '-') { + return true; + } + + return false; + } + + private static boolean startsWithOrEquals(String string, String prefix) { + if (string.startsWith(prefix) && (string.length() == prefix.length() || string.charAt(prefix.length()) == '/')) { + return true; + } + + return false; + } + + private static final Pattern DOC_LICENSE_PATTERN = Pattern.compile("/usr/share/(doc|licenses)/([^/]*)(.*)"); + + private boolean allowedAnyFile(RpmInfo rpm, String filename) { + for (String prefix : prefixes.getOrDefault(rpm.getPackageName(), Collections.emptyList())) { + if (filename.startsWith(substitute(rpm, prefix))) { + return true; + } + } + + for (String filepath : exceptionalFiles.getOrDefault(rpm.getPackageName(), Collections.emptyList())) { + if (filename.equals(filepath)) { + return true; + } + } + + Matcher matcher = DOC_LICENSE_PATTERN.matcher(filename); + + if (matcher.matches()) { + if (namesRelated(rpm.getName(), matcher.group(2)) + || isAlias(rpm.getName(), matcher.group(2))) { + if (matcher.group(3).isEmpty()) { + return true; + } + + if (matcher.group(3).charAt(0) == '/') { + if (matcher.group(1).equals("licenses") && + matcher.group(3).codePoints().filter(c -> c == '/').count() != 1) { + return false; + } + + return true; + } + } + } + + return false; + } + + private boolean allowedJavadocFile(RpmInfo rpm, String filename) { + if (allowedAnyFile(rpm, filename)) { + return true; + } + + if (patternsCache.computeIfAbsent(rpm.getPackageName() + "/javadoc", + p -> Pattern.compile("/usr/share/javadoc/" + rpm.getPackageName() + "(:?/.*)?")) + .matcher(filename).matches()) { + return true; + } + + return false; + } + + private boolean allowedDebuginfoFile(RpmInfo rpm, String filename) { + if (allowedAnyFile(rpm, filename)) { + return true; + } + + if (startsWithOrEquals(filename, "/usr/lib/debug")) { + return true; + } + + return false; + } + + private boolean allowedDebugsourceFile(RpmInfo rpm, String filename) { + if (allowedAnyFile(rpm, filename)) { + return true; + } + + if (filename.startsWith("/usr/src/debug/")) { + return true; + } + + return false; + } + + private boolean allowedCLibraryFile(RpmInfo rpm, String filename) { + if (allowedAnyFile(rpm, filename)) { + return true; + } + + if (filename.startsWith("/usr/include/")) { + return true; + } + + if (filename.startsWith("/usr/lib64/lib") && filename.endsWith(".a")) { + return true; + } + + return false; + } + + private boolean allowedPythonLibraryFile(RpmInfo rpm, String filename) { + if (allowedAnyFile(rpm, filename)) { + return true; + } + + if (filename.startsWith("/usr/lib/python")) { + return true; + } + + return false; + } + + private boolean allowedJavaLibraryFile(RpmInfo rpm, String filename) { + if (allowedAnyFile(rpm, filename)) { + return true; + } + + String prefix; + String rpmEntryPrefix; + + if (filename.startsWith(prefix = "/usr/share/java/")) { + rpmEntryPrefix = filename.substring(prefix.length()); + if (filename.endsWith(".jar") && rpmEntryPrefix.codePoints().filter(c -> c == '/').count() <= 1) { + return true; + } + + if (namesRelated(rpm.getName(), rpmEntryPrefix) + || namesRelated(rpm.getPackageName(), rpmEntryPrefix) + || isAlias(rpm.getName(), rpmEntryPrefix)) { + return true; + } + } + + if (filename.startsWith(prefix = "/usr/lib/java/") && filename.endsWith(".jar")) { + if (filename.substring(prefix.length()).codePoints().filter(c -> c == '/').count() <= 1) { + return true; + } + } + + if (filename.startsWith(prefix = "/usr/share/maven-poms/")) { + rpmEntryPrefix = filename.substring(prefix.length()); + if (filename.endsWith(".pom") && rpmEntryPrefix.codePoints().filter(c -> c == '/').count() <= 1) { + return true; + } + + if (namesRelated(rpm.getName(), rpmEntryPrefix) + || namesRelated(rpm.getPackageName(), rpmEntryPrefix) + || isAlias(rpm.getName(), rpmEntryPrefix)) { + return true; + } + } + + if (filename.startsWith("/usr/share/maven-metadata/") && filename.endsWith(".xml")) { + return true; + } + + return false; + } + + private boolean allowedJavaApplicationFile(RpmInfo rpm, String filename) { + if (allowedJavaLibraryFile(rpm, filename)) { + return true; + } + + if (filename.startsWith("/usr/bin/" + rpm.getPackageName() + "/")) { + return true; + } + + if (filename.startsWith("/usr/share/man/man1/") && filename.endsWith(".1.gz")) { + return true; + } + + if (filename.startsWith("/usr/share/man/man7/") && filename.endsWith(".7.gz")) { + return true; + } + + String prefix; + + if (filename.startsWith(prefix = "/usr/share/bash-completion")) { + if (filename.length() == prefix.length()) { + return true; + } + + if (filename.startsWith(prefix += "/completions")) { + if (filename.length() == prefix.length()) { + return true; + } + + if (filename.charAt(prefix.length()) == '/') { + return true; + } + } + } + + if (filename.startsWith("/usr/share/maven-metadata/") && filename.endsWith(".xml")) { + return true; + } + + if (filename.startsWith(prefix = "/usr/share/")) { + String suffix = filename.substring(prefix.length()); + if (suffix.length() > 0) { + int index = suffix.indexOf('/'); + if (index == -1) { + index = suffix.length(); + } + if (namesRelated(suffix.substring(0, index), rpm.getName())) { + return true; + } + } + } + + return false; + } + + @Override + public boolean allowedFile(RpmInfo rpm, Path path) { + if (rpm.isSourcePackage()) { + // TODO + return true; + } + + // if (rpm.getPackageName().equals("javapackages-bootstrap")) { + // // TODO + // return true; + // } + + String filename = path.toString(); + + if (rpm.getName().equals(rpm.getPackageName() + "-javadoc") || rpm.getName().equals(rpm.getPackageName() + "-javadocs")) { + return allowedJavadocFile(rpm, filename); + } else if (rpm.getName().equals(rpm.getPackageName() + "-debuginfo")) { + return allowedDebuginfoFile(rpm, filename); + } else if (rpm.getName().equals(rpm.getPackageName() + "-debugsource")) { + return allowedDebugsourceFile(rpm, filename); + } else if (rpm.getName().startsWith("python3-javapackages")) { + return allowedPythonLibraryFile(rpm, filename); + } else if (rpm.getName().startsWith("antlr-C++")) { + return allowedCLibraryFile(rpm, filename); + } else { + // TODO differentiate Java applications ad libraries + return allowedJavaApplicationFile(rpm, filename); + } + } +} diff --git a/src/RpmFilesizeValidatorJP.java b/src/RpmFilesizeValidatorJP.java new file mode 100644 index 0000000..bf806c9 --- /dev/null +++ b/src/RpmFilesizeValidatorJP.java @@ -0,0 +1,33 @@ +package org.fedoraproject.javapackages.validator.validators.jp; + +import org.fedoraproject.javadeptools.rpm.RpmInfo; +import org.fedoraproject.javapackages.validator.TmtTest; +import org.fedoraproject.javapackages.validator.validators.RpmFilesizeValidator; + +@TmtTest("/jp/rpm_filesize") +public class RpmFilesizeValidatorJP extends RpmFilesizeValidator { + @Override + public boolean allowedFilesize(RpmInfo rpm, long sizeBytes) { + if (rpm.isSourcePackage()) { + return sizeBytes <= 1_000_000_000; + } + + if (rpm.getPackageName().equals("javapackages-bootstrap")) { + return sizeBytes <= 40_000_000; + } + + // javadoc rpms + if (rpm.getName().equals(rpm.getPackageName() + "-javadoc")) { + return sizeBytes <= 4_000_000; + } + + // maven-lib and xmvn-minimal bundle dependencies during bootstrap + if (rpm.getRelease().endsWith("~bootstrap")) { + if (rpm.getName().equals("maven-lib") || rpm.getName().equals("xmvn-minimal")) { + return sizeBytes <= 10_000_000; + } + } + + return sizeBytes <= 3_500_000; + } +} diff --git a/src/org.fedoraproject.javapackages.validator.Validator b/src/org.fedoraproject.javapackages.validator.Validator new file mode 100644 index 0000000..d900caa --- /dev/null +++ b/src/org.fedoraproject.javapackages.validator.Validator @@ -0,0 +1,5 @@ +org.fedoraproject.javapackages.validator.validators.NoBootstrapValidator +org.fedoraproject.javapackages.validator.validators.jp.BytecodeVersionJarValidatorJP +org.fedoraproject.javapackages.validator.validators.jp.DuplicateFileValidatorJP +org.fedoraproject.javapackages.validator.validators.jp.FilesValidatorJP +org.fedoraproject.javapackages.validator.validators.jp.RpmFilesizeValidatorJP