[JENKINS-55681] - Make JAXB a detached plugin only on Java 11 (#3865)

* Move detached plugins logic into a dedicated class

ClassicPluginStrategy is already long enough, so trying to make things a
bit more focused before adding Java 11 logic. (and even if not, the change
still splits a class that is almost 1k lines).

* Install jaxb as detached only on Java 11+

* Remove duplicate @Restricted

Given the enclosing class already has it.

* Use 1.5 release

* Add more test coverage

* getMinJavaVersion => getMinimumJavaVersion

* Rename DetachedPluginsManager as DetachedPluginsUtil

as requested by Oleg and Jesse. Mainly because of the misleading
relationship (there's no inheritance) with the existing `PluginManager`
class.

* 🤦 adjust to 1.5 release method renaming

* Clarify minimum Java version column

Co-Authored-By: batmat <bmathus@gmail.com>

* Update to 2.3.0 version of the jaxb plugin

* Bump minimum core version to next expected release version

* Add preventive cycle breaks for jaxb while waiting for JENKINS-28942

As requested by Oleg.

* Add test to fail fast on wrong format for split-plugins.txt
This commit is contained in:
Baptiste Mathus authored and Oleg Nenashev committed 2019-02-03 10:56:26 +01:00
1 parent 8ab5ab57e2
commit a78312ea0f
11 files changed
+369 -202

No files matched your search

@@ -23,29 +23,21 @@
*/
package hudson;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import java.io.FileNotFoundException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.InvalidPathException;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import jenkins.util.AntWithFindResourceClassLoader;
import jenkins.util.SystemProperties;
import com.google.common.collect.Lists;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import hudson.Plugin.DummyImpl;
import hudson.PluginWrapper.Dependency;
import hudson.model.Hudson;
import jenkins.util.AntClassLoader;
import hudson.util.CyclicGraphDetector;
import hudson.util.CyclicGraphDetector.CycleDetectedException;
import hudson.util.IOUtils;
import hudson.util.MaskingClassLoader;
import hudson.util.VersionNumber;
import jenkins.ClassLoaderReflectionToolkit;
import jenkins.ExtensionFilter;
import jenkins.plugins.DetachedPluginsUtil;
import jenkins.util.AntClassLoader;
import jenkins.util.AntWithFindResourceClassLoader;
import jenkins.util.SystemProperties;
import org.apache.commons.io.output.NullOutputStream;
import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.Project;
@@ -60,37 +52,36 @@ import org.apache.tools.ant.util.GlobPatternMapper;
import org.apache.tools.zip.ZipEntry;
import org.apache.tools.zip.ZipExtraField;
import org.apache.tools.zip.ZipOutputStream;
import org.jenkinsci.bytecode.Transformer;
import javax.annotation.Nonnull;
import java.io.Closeable;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FilenameFilter;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.InvalidPathException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.jar.Attributes;
import java.util.jar.JarFile;
import java.util.jar.Manifest;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.jenkinsci.bytecode.Transformer;
import org.kohsuke.accmod.Restricted;
import org.kohsuke.accmod.restrictions.NoExternalUse;
import javax.annotation.Nonnull;
import static org.apache.commons.io.FilenameUtils.getBaseName;
public class ClassicPluginStrategy implements PluginStrategy {
private static final Logger LOGGER = Logger.getLogger(ClassicPluginStrategy.class.getName());
/**
* Filter for jar files.
*/
@@ -266,32 +257,16 @@ public class ClassicPluginStrategy implements PluginStrategy {
if (jenkinsVersion==null)
jenkinsVersion = atts.getValue("Hudson-Version");
optionalDependencies.addAll(getImpliedDependencies(pluginName, jenkinsVersion));
optionalDependencies.addAll(DetachedPluginsUtil.getImpliedDependencies(pluginName, jenkinsVersion));
}
/**
* Returns all the plugin dependencies that are implicit based on a particular Jenkins version
* @since 2.0
* @see DetachedPluginsUtil#getImpliedDependencies(String, String)
*/
@Deprecated // since TODO
@Nonnull
public static List<PluginWrapper.Dependency> getImpliedDependencies(String pluginName, String jenkinsVersion) {
List<PluginWrapper.Dependency> out = new ArrayList<>();
for (DetachedPlugin detached : DETACHED_LIST) {
// don't fix the dependency for itself, or else we'll have a cycle
if (detached.shortName.equals(pluginName)) {
continue;
}
if (BREAK_CYCLES.contains(pluginName + ' ' + detached.shortName)) {
LOGGER.log(Level.FINE, "skipping implicit dependency {0} → {1}", new Object[] {pluginName, detached.shortName});
continue;
}
// some earlier versions of maven-hpi-plugin apparently puts "null" as a literal in Hudson-Version. watch out for them.
if (jenkinsVersion == null || jenkinsVersion.equals("null") || new VersionNumber(jenkinsVersion).compareTo(detached.splitWhen) <= 0) {
out.add(new PluginWrapper.Dependency(detached.shortName + ':' + detached.requiredVersion));
LOGGER.log(Level.FINE, "adding implicit dependency {0} → {1} because of {2}", new Object[] {pluginName, detached.shortName, jenkinsVersion});
}
}
return out;
return DetachedPluginsUtil.getImpliedDependencies(pluginName, jenkinsVersion);
}
@Deprecated
@@ -319,140 +294,6 @@ public class ClassicPluginStrategy implements PluginStrategy {
return classLoader;
}
/**
* Get the list of all plugins that have ever been {@link DetachedPlugin detached} from Jenkins core.
* @return A {@link List} of {@link DetachedPlugin}s.
*/
@Restricted(NoExternalUse.class)
public static @Nonnull List<DetachedPlugin> getDetachedPlugins() {
return DETACHED_LIST;
}
/**
* Get the list of plugins that have been detached since a specific Jenkins release version.
* @param since The Jenkins version.
* @return A {@link List} of {@link DetachedPlugin}s.
*/
@Restricted(NoExternalUse.class)
public static @Nonnull List<DetachedPlugin> getDetachedPlugins(@Nonnull VersionNumber since) {
List<DetachedPlugin> detachedPlugins = new ArrayList<>();
for (DetachedPlugin detachedPlugin : DETACHED_LIST) {
if (!detachedPlugin.getSplitWhen().isOlderThan(since)) {
detachedPlugins.add(detachedPlugin);
}
}
return detachedPlugins;
}
/**
* Is the named plugin a plugin that was detached from Jenkins at some point in the past.
* @param pluginId The plugin ID.
* @return {@code true} if the plugin is a plugin that was detached from Jenkins at some
* point in the past, otherwise {@code false}.
*/
@Restricted(NoExternalUse.class)
public static boolean isDetachedPlugin(@Nonnull String pluginId) {
for (DetachedPlugin detachedPlugin : DETACHED_LIST) {
if (detachedPlugin.getShortName().equals(pluginId)) {
return true;
}
}
return false;
}
/**
* Information about plugins that were originally in the core.
* <p>
* A detached plugin is one that has any of the following characteristics:
* <ul>
* <li>
* Was an existing plugin that at some time previously bundled with the Jenkins war file.
* </li>
* <li>
* Was previous code in jenkins core that was split to a separate-plugin (but may not have
* ever been bundled in a jenkins war file - i.e. it gets split after this 2.0 update).
* </li>
* </ul>
*/
@Restricted(NoExternalUse.class)
public static final class DetachedPlugin {
private final String shortName;
/**
* Plugins built for this Jenkins version (and earlier) will automatically be assumed to have
* this plugin in its dependency.
*
* When core/pom.xml version is 1.123-SNAPSHOT when the code is removed, then this value should
* be "1.123.*" (because 1.124 will be the first version that doesn't include the removed code.)
*/
private final VersionNumber splitWhen;
private final String requiredVersion;
private DetachedPlugin(String shortName, String splitWhen, String requiredVersion) {
this.shortName = shortName;
this.splitWhen = new VersionNumber(splitWhen);
this.requiredVersion = requiredVersion;
}
/**
* Get the short name of the plugin.
* @return The short name of the plugin.
*/
public String getShortName() {
return shortName;
}
/**
* Get the Jenkins version from which the plugin was detached.
* @return The Jenkins version from which the plugin was detached.
*/
public VersionNumber getSplitWhen() {
return splitWhen;
}
/**
* Gets the minimum required version for the current version of Jenkins.
*
* @return the minimum required version for the current version of Jenkins.
* @since 2.16
*/
public VersionNumber getRequiredVersion() {
return new VersionNumber(requiredVersion);
}
@Override
public String toString() {
return shortName + " " + splitWhen.toString().replace(".*", "") + " " + requiredVersion;
}
}
/** Record of which plugins which removed from core and when. */
private static final List<DetachedPlugin> DETACHED_LIST;
/** Implicit dependencies that are known to be unnecessary and which must be cut out to prevent a dependency cycle among bundled plugins. */
private static final Set<String> BREAK_CYCLES;
static {
try (InputStream is = ClassicPluginStrategy.class.getResourceAsStream("/jenkins/split-plugins.txt")) {
DETACHED_LIST = ImmutableList.copyOf(configLines(is).map(line -> {
String[] pieces = line.split(" ");
return new DetachedPlugin(pieces[0], pieces[1] + ".*", pieces[2]);
}).collect(Collectors.toList()));
} catch (IOException x) {
throw new ExceptionInInitializerError(x);
}
try (InputStream is = ClassicPluginStrategy.class.getResourceAsStream("/jenkins/split-plugin-cycles.txt")) {
BREAK_CYCLES = ImmutableSet.copyOf(configLines(is).collect(Collectors.toSet()));
} catch (IOException x) {
throw new ExceptionInInitializerError(x);
}
}
private static Stream<String> configLines(InputStream is) throws IOException {
return org.apache.commons.io.IOUtils.readLines(is, StandardCharsets.UTF_8).stream().filter(line -> !line.matches("#.*|\\s*"));
}
/**
* Computes the classloader that takes the class masking into account.
*
@@ -483,7 +324,7 @@ public class ClassicPluginStrategy implements PluginStrategy {
* See {@link ExtensionFinder#scout(Class, Hudson)} for the dead lock issue and what this does.
*/
if (LOGGER.isLoggable(Level.FINER))
LOGGER.log(Level.FINER,"Scout-loading ExtensionList: "+type, new Throwable());
LOGGER.log(Level.FINER, "Scout-loading ExtensionList: "+type, new Throwable());
for (ExtensionFinder finder : finders) {
finder.scout(type, hudson);
}
@@ -864,6 +705,5 @@ public class ClassicPluginStrategy implements PluginStrategy {
}
public static boolean useAntClassLoader = SystemProperties.getBoolean(ClassicPluginStrategy.class.getName()+".useAntClassLoader");
private static final Logger LOGGER = Logger.getLogger(ClassicPluginStrategy.class.getName());
public static boolean DISABLE_TRANSFORMER = SystemProperties.getBoolean(ClassicPluginStrategy.class.getName()+".noBytecodeTransformer");
}