diff --git a/core/pom.xml b/core/pom.xml index 4ba0498ae9..5188ec95be 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -63,7 +63,7 @@ THE SOFTWARE. org.jenkins-ci version-number - 1.4 + 1.5 org.jenkins-ci diff --git a/core/src/main/java/hudson/ClassicPluginStrategy.java b/core/src/main/java/hudson/ClassicPluginStrategy.java index 5d173c3839..fdd2e88dd1 100644 --- a/core/src/main/java/hudson/ClassicPluginStrategy.java +++ b/core/src/main/java/hudson/ClassicPluginStrategy.java @@ -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 getImpliedDependencies(String pluginName, String jenkinsVersion) { - List 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 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 getDetachedPlugins(@Nonnull VersionNumber since) { - List 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. - *

- * A detached plugin is one that has any of the following characteristics: - *

    - *
  • - * Was an existing plugin that at some time previously bundled with the Jenkins war file. - *
  • - *
  • - * 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). - *
  • - *
- */ - @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 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 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 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"); } diff --git a/core/src/main/java/hudson/PluginManager.java b/core/src/main/java/hudson/PluginManager.java index d6f8f794f6..64f1c65f36 100644 --- a/core/src/main/java/hudson/PluginManager.java +++ b/core/src/main/java/hudson/PluginManager.java @@ -62,6 +62,7 @@ import jenkins.YesNoMaybe; import jenkins.install.InstallState; import jenkins.install.InstallUtil; import jenkins.model.Jenkins; +import jenkins.plugins.DetachedPluginsUtil; import jenkins.security.CustomClassFilter; import jenkins.util.SystemProperties; import jenkins.util.io.OnMaster; @@ -725,7 +726,7 @@ public abstract class PluginManager extends AbstractModelObject implements OnMas LOGGER.log(INFO, "Upgrading Jenkins. The last running version was {0}. This Jenkins is version {1}.", new Object[] {lastExecVersion, Jenkins.VERSION}); - final List detachedPlugins = ClassicPluginStrategy.getDetachedPlugins(lastExecVersion); + final List detachedPlugins = DetachedPluginsUtil.getDetachedPlugins(lastExecVersion); Set loadedDetached = loadPluginsFromWar("/WEB-INF/detached-plugins", new FilenameFilter() { @Override @@ -734,7 +735,7 @@ public abstract class PluginManager extends AbstractModelObject implements OnMas // If this was a plugin that was detached some time in the past i.e. not just one of the // plugins that was bundled "for fun". - if (ClassicPluginStrategy.isDetachedPlugin(name)) { + if (DetachedPluginsUtil.isDetachedPlugin(name)) { VersionNumber installedVersion = getPluginVersion(rootDir, name); VersionNumber bundledVersion = getPluginVersion(dir, name); // If the plugin is already installed, we need to decide whether to replace it with the bundled version. @@ -747,7 +748,7 @@ public abstract class PluginManager extends AbstractModelObject implements OnMas } // If it's a plugin that was detached since the last running version. - for (ClassicPluginStrategy.DetachedPlugin detachedPlugin : detachedPlugins) { + for (DetachedPluginsUtil.DetachedPlugin detachedPlugin : detachedPlugins) { if (detachedPlugin.getShortName().equals(name)) { return true; } @@ -763,9 +764,9 @@ public abstract class PluginManager extends AbstractModelObject implements OnMas InstallUtil.saveLastExecVersion(); } else { - final Set forceUpgrade = new HashSet<>(); + final Set forceUpgrade = new HashSet<>(); // TODO using getDetachedPlugins here seems wrong; should be forcing an upgrade when the installed version is older than that in WEB-INF/detached-plugins/ - for (ClassicPluginStrategy.DetachedPlugin p : ClassicPluginStrategy.getDetachedPlugins()) { + for (DetachedPluginsUtil.DetachedPlugin p : DetachedPluginsUtil.getDetachedPlugins()) { VersionNumber installedVersion = getPluginVersion(rootDir, p.getShortName()); VersionNumber requiredVersion = p.getRequiredVersion(); if (installedVersion != null && installedVersion.isOlderThan(requiredVersion)) { @@ -780,7 +781,7 @@ public abstract class PluginManager extends AbstractModelObject implements OnMas @Override public boolean accept(File dir, String name) { name = normalisePluginName(name); - for (ClassicPluginStrategy.DetachedPlugin detachedPlugin : forceUpgrade) { + for (DetachedPluginsUtil.DetachedPlugin detachedPlugin : forceUpgrade) { if (detachedPlugin.getShortName().equals(name)) { return true; } diff --git a/core/src/main/java/hudson/model/UpdateSite.java b/core/src/main/java/hudson/model/UpdateSite.java index 739229faa8..03c89e5eda 100644 --- a/core/src/main/java/hudson/model/UpdateSite.java +++ b/core/src/main/java/hudson/model/UpdateSite.java @@ -25,7 +25,6 @@ package hudson.model; -import hudson.ClassicPluginStrategy; import hudson.ExtensionList; import hudson.PluginManager; import hudson.PluginWrapper; @@ -67,6 +66,7 @@ import javax.annotation.Nullable; import jenkins.model.Jenkins; import jenkins.model.DownloadSettings; +import jenkins.plugins.DetachedPluginsUtil; import jenkins.security.UpdateSiteWarningsConfiguration; import jenkins.util.JSONSignatureValidator; import jenkins.util.SystemProperties; @@ -551,7 +551,7 @@ public class UpdateSite { for(Map.Entry e : (Set>)o.getJSONObject("plugins").entrySet()) { Plugin p = new Plugin(sourceId, e.getValue()); // JENKINS-33308 - include implied dependencies for older plugins that may need them - List implicitDeps = ClassicPluginStrategy.getImpliedDependencies(p.name, p.requiredCore); + List implicitDeps = DetachedPluginsUtil.getImpliedDependencies(p.name, p.requiredCore); if(!implicitDeps.isEmpty()) { for(PluginWrapper.Dependency dep : implicitDeps) { if(!p.dependencies.containsKey(dep.shortName)) { diff --git a/core/src/main/java/jenkins/plugins/DetachedPluginsUtil.java b/core/src/main/java/jenkins/plugins/DetachedPluginsUtil.java new file mode 100644 index 0000000000..55c9e079c9 --- /dev/null +++ b/core/src/main/java/jenkins/plugins/DetachedPluginsUtil.java @@ -0,0 +1,224 @@ +package jenkins.plugins; + +import com.google.common.annotations.VisibleForTesting; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableSet; +import hudson.ClassicPluginStrategy; +import hudson.PluginWrapper; +import hudson.util.VersionNumber; +import jenkins.util.java.JavaUtils; +import org.kohsuke.accmod.Restricted; +import org.kohsuke.accmod.restrictions.NoExternalUse; + +import javax.annotation.Nonnull; +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.List; +import java.util.Set; +import java.util.logging.Level; +import java.util.logging.Logger; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +/** + * Dedicated class to handle the logic related to so-called detached plugins. + * + *

Originally, some features were directly in Jenkins core. Over time, more and more features got extracted in dedicated plugins. + * Issue is: many plugins had started depending on these features, that now were not in the core anymore. + * So the chosen design strategy has been that the jenkins.war would embed these plugins, and automatically install them and mark them as optional dependencies. + * This way, older plugins would keep working without having to be modified.

+ *

+ * This code was originally moved from {@link ClassicPluginStrategy}. + * + * @since TODO + */ +@Restricted(NoExternalUse.class) +public class DetachedPluginsUtil { + private static final Logger LOGGER = Logger.getLogger(DetachedPluginsUtil.class.getName()); + + /** + * Record of which plugins which removed from core and when. + */ + @VisibleForTesting + static final List 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 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(" "); + + // defaults to Java 1.0 to install unconditionally if unspecified + return new DetachedPluginsUtil.DetachedPlugin(pieces[0], + pieces[1] + ".*", + pieces[2], + pieces.length == 4 ? pieces[3] : "1.0"); + }).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 DetachedPluginsUtil() { + } + + /** + * Returns all the plugin dependencies that are implicit based on a particular Jenkins version + * + * @since 2.0 + */ + @Nonnull + public static List getImpliedDependencies(String pluginName, String jenkinsVersion) { + List out = new ArrayList<>(); + for (DetachedPlugin detached : getDetachedPlugins()) { + // 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; + } + + /** + * Get the list of all plugins that have ever been {@link DetachedPlugin detached} from Jenkins core, applicable to the current Java runtime. + * + * @return A {@link List} of {@link DetachedPlugin}s. + * @see JavaUtils#getCurrentJavaRuntimeVersionNumber() + */ + public static @Nonnull + List getDetachedPlugins() { + return DETACHED_LIST.stream() + .filter(plugin -> JavaUtils.getCurrentJavaRuntimeVersionNumber().isNewerThanOrEqualTo(plugin.getMinimumJavaVersion())) + .collect(Collectors.toList()); + } + + /** + * 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. + * @see #getDetachedPlugins() + */ + public static @Nonnull + List getDetachedPlugins(@Nonnull VersionNumber since) { + return getDetachedPlugins().stream() + .filter(detachedPlugin -> !detachedPlugin.getSplitWhen().isOlderThan(since)) + .collect(Collectors.toList()); + } + + /** + * 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}. + */ + public static boolean isDetachedPlugin(@Nonnull String pluginId) { + for (DetachedPlugin detachedPlugin : getDetachedPlugins()) { + if (detachedPlugin.getShortName().equals(pluginId)) { + return true; + } + } + + return false; + } + + private static Stream configLines(InputStream is) throws IOException { + return org.apache.commons.io.IOUtils.readLines(is, StandardCharsets.UTF_8).stream().filter(line -> !line.matches("#.*|\\s*")); + } + + /** + * Information about plugins that were originally in the core. + *

+ * A detached plugin is one that has any of the following characteristics: + *

    + *
  • + * Was an existing plugin that at some time previously bundled with the Jenkins war file. + *
  • + *
  • + * 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). + *
  • + *
+ */ + @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 final VersionNumber minJavaVersion; + + private DetachedPlugin(String shortName, String splitWhen, String requiredVersion, String minJavaVersion) { + this.shortName = shortName; + this.splitWhen = new VersionNumber(splitWhen); + this.requiredVersion = requiredVersion; + this.minJavaVersion = new VersionNumber(minJavaVersion); + } + + /** + * 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; + } + + @Nonnull + public VersionNumber getMinimumJavaVersion() { + return minJavaVersion; + } + } +} diff --git a/core/src/main/resources/jenkins/split-plugin-cycles.txt b/core/src/main/resources/jenkins/split-plugin-cycles.txt index 8c7871ef91..3cec061d7e 100644 --- a/core/src/main/resources/jenkins/split-plugin-cycles.txt +++ b/core/src/main/resources/jenkins/split-plugin-cycles.txt @@ -10,3 +10,25 @@ script-security command-launcher script-security matrix-auth script-security matrix-project script-security windows-slaves + +# Weird unexpected cycle that showed up during testing of this new plugin +# so breaking all potential cycles until JENKINS-28942 +# FIXME: remove when JENKINS-28942 is in (and used in the JAXB plugin) +script-security jaxb +maven-plugin jaxb +subversion jaxb +cvs jaxb +ant jaxb +javadoc jaxb +external-monitor-job jaxb +ldap jaxb +pam-auth jaxb +mailer jaxb +matrix-auth jaxb +windows-slaves jaxb +antisamy-markup-formatter jaxb +matrix-project jaxb +junit jaxb +bouncycastle-api jaxb +command-launcher jaxb +jdk-tool jaxb diff --git a/core/src/main/resources/jenkins/split-plugins.txt b/core/src/main/resources/jenkins/split-plugins.txt index cb0a447db2..6b09bbe877 100644 --- a/core/src/main/resources/jenkins/split-plugins.txt +++ b/core/src/main/resources/jenkins/split-plugins.txt @@ -1,6 +1,6 @@ # See ClassicPluginStrategy.DETACHED_LIST. As of JENKINS-47634 also used by plugin-compat-tester. -# Columns are: plugin ID, last core release still containing the plugin's functionality, plugin version that's implied. +# Columns are: plugin ID, last core release still containing the plugin's functionality, plugin version that's implied, minimum Java specification version where to install the plugin # Note that all split plugins between and including matrix-auth and jdk-tool incorrectly use the first # core release without the plugin's functionality when they should use the immediately prior release. @@ -25,3 +25,6 @@ bouncycastle-api 2.16 2.16.0 command-launcher 2.86 1.0 # JENKINS-22367 jdk-tool 2.112 1.0 + +# JENKINS-55681 +jaxb 2.163 2.3.0 11 diff --git a/core/src/test/java/hudson/ClassicPluginStrategyTest.java b/core/src/test/java/hudson/ClassicPluginStrategyTest.java index bb19c5f81b..766676ad21 100644 --- a/core/src/test/java/hudson/ClassicPluginStrategyTest.java +++ b/core/src/test/java/hudson/ClassicPluginStrategyTest.java @@ -24,12 +24,13 @@ package hudson; import hudson.util.VersionNumber; +import jenkins.plugins.DetachedPluginsUtil; import org.junit.Assert; import org.junit.Test; import java.util.List; -import static hudson.ClassicPluginStrategy.DetachedPlugin; +import static jenkins.plugins.DetachedPluginsUtil.DetachedPlugin; /** * @author tom.fennelly@gmail.com @@ -38,20 +39,20 @@ public class ClassicPluginStrategyTest { @Test public void test_getDetachedPlugins() { - List list = ClassicPluginStrategy.getDetachedPlugins(new VersionNumber("1.296")); + List list = DetachedPluginsUtil.getDetachedPlugins(new VersionNumber("1.296")); Assert.assertTrue(list.size() >= 14); // There were 14 at the time of writing this test Assert.assertNotNull(findPlugin("maven-plugin", list)); Assert.assertNotNull(findPlugin("subversion", list)); // Narrow the list to since "1.310" (the subversion detach version). - list = ClassicPluginStrategy.getDetachedPlugins(new VersionNumber("1.310")); + list = DetachedPluginsUtil.getDetachedPlugins(new VersionNumber("1.310")); // Maven should no longer be in the list, but subversion should. Assert.assertNull(findPlugin("maven-plugin", list)); Assert.assertNotNull(findPlugin("subversion", list)); // Narrow the list to since "1.311" (after the subversion detach version). - list = ClassicPluginStrategy.getDetachedPlugins(new VersionNumber("1.311")); + list = DetachedPluginsUtil.getDetachedPlugins(new VersionNumber("1.311")); // Neither Maven or subversion should be in the list. Assert.assertNull(findPlugin("maven-plugin", list)); Assert.assertNull(findPlugin("subversion", list)); diff --git a/core/src/test/java/jenkins/plugins/DetachedPluginsUtilTest.java b/core/src/test/java/jenkins/plugins/DetachedPluginsUtilTest.java new file mode 100644 index 0000000000..611a9b34d7 --- /dev/null +++ b/core/src/test/java/jenkins/plugins/DetachedPluginsUtilTest.java @@ -0,0 +1,70 @@ +package jenkins.plugins; + +import hudson.util.VersionNumber; +import jenkins.util.java.JavaUtils; +import org.apache.commons.io.IOUtils; +import org.junit.Test; + +import java.io.IOException; +import java.util.List; +import java.util.stream.Collectors; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +public class DetachedPluginsUtilTest { + @Test + public void checkJaxb() { + final List plugins = + DetachedPluginsUtil.DETACHED_LIST.stream() + .filter(plugin -> plugin.getShortName().equals("jaxb")) + .collect(Collectors.toList()); + assertEquals(1, plugins.size()); + + DetachedPluginsUtil.DetachedPlugin jaxb = plugins.get(0); + + assertEquals(new VersionNumber("11"), jaxb.getMinimumJavaVersion()); + + final List detachedPlugins = DetachedPluginsUtil.getDetachedPlugins(); + if (JavaUtils.isRunningWithJava8OrBelow()) { + assertEquals(0, detachedPlugins.stream() + .filter(plugin -> plugin.getShortName().equals("jaxb")) + .collect(Collectors.toList()).size()); + } else if (JavaUtils.getCurrentJavaRuntimeVersionNumber().isNewerThanOrEqualTo(new VersionNumber("11.0.2"))) { + assertEquals(1, detachedPlugins.stream() + .filter(plugin -> plugin.getShortName().equals("jaxb")) + .collect(Collectors.toList()).size()); + + final List detachedPluginsSince2_161 = + DetachedPluginsUtil.getDetachedPlugins(new VersionNumber("2.161")); + + assertEquals(1, detachedPluginsSince2_161.size()); + assertEquals("jaxb", detachedPluginsSince2_161.get(0).getShortName()); + } + } + + /** + * Checks the format of the /jenkins/split-plugins.txt file has maximum 4 columns. + */ + @Test + public void checkSplitPluginsFileFormat() throws IOException { + final List splitPluginsLines = IOUtils.readLines(getClass().getResourceAsStream("/jenkins/split-plugins.txt")); + assertTrue(!splitPluginsLines.isEmpty()); + + // File is not only comments + final List linesWithoutComments = splitPluginsLines.stream() + .filter(line -> !line.startsWith("#")).collect(Collectors.toList()); + assertFalse( "weird, split-plugins.txt only has comments?", linesWithoutComments.isEmpty()); + + // + assertFalse("no whitespaces only lines allowed" ,linesWithoutComments.stream() + .filter(line -> line.trim().isEmpty()) + .anyMatch(line -> !line.isEmpty())); + + + assertTrue( "max 4 columns is supported", linesWithoutComments.stream() + .map(line -> line.split(" ")) + .noneMatch(line -> line.length > 4)); + } +} diff --git a/test/src/test/java/jenkins/install/LoadDetachedPluginsTest.java b/test/src/test/java/jenkins/install/LoadDetachedPluginsTest.java index 17157ce963..2c181cdb4d 100644 --- a/test/src/test/java/jenkins/install/LoadDetachedPluginsTest.java +++ b/test/src/test/java/jenkins/install/LoadDetachedPluginsTest.java @@ -24,8 +24,8 @@ package jenkins.install; -import hudson.ClassicPluginStrategy; -import hudson.ClassicPluginStrategy.DetachedPlugin; +import jenkins.plugins.DetachedPluginsUtil; +import jenkins.plugins.DetachedPluginsUtil.DetachedPlugin; import hudson.Plugin; import hudson.PluginManager; import hudson.PluginManagerUtil; @@ -63,7 +63,7 @@ public class LoadDetachedPluginsTest { public void upgradeFromJenkins1() throws IOException { VersionNumber since = new VersionNumber("1.550"); rr.then(r -> { - List detachedPlugins = ClassicPluginStrategy.getDetachedPlugins(since); + List detachedPlugins = DetachedPluginsUtil.getDetachedPlugins(since); assertThat("Plugins have been detached since the pre-upgrade version", detachedPlugins.size(), greaterThan(4)); assertThat("Plugins detached between the pre-upgrade version and the current version should be installed", @@ -78,7 +78,7 @@ public class LoadDetachedPluginsTest { public void upgradeFromJenkins2() { VersionNumber since = new VersionNumber("2.0"); rr.then(r -> { - List detachedPlugins = ClassicPluginStrategy.getDetachedPlugins(since); + List detachedPlugins = DetachedPluginsUtil.getDetachedPlugins(since); assertThat("Plugins have been detached since the pre-upgrade version", detachedPlugins.size(), greaterThan(1)); assertThat("Plugins detached between the pre-upgrade version and the current version should be installed", @@ -93,7 +93,7 @@ public class LoadDetachedPluginsTest { public void upgradeFromJenkins2WithNewerDependency() { VersionNumber since = new VersionNumber("2.0"); rr.then(r -> { - List detachedPlugins = ClassicPluginStrategy.getDetachedPlugins(since); + List detachedPlugins = DetachedPluginsUtil.getDetachedPlugins(since); assertThat("Plugins have been detached since the pre-upgrade version", detachedPlugins.size(), greaterThan(1)); assertThat("Plugins detached between the pre-upgrade version and the current version should be installed", @@ -111,7 +111,7 @@ public class LoadDetachedPluginsTest { public void upgradeFromJenkins2WithOlderDependency() { VersionNumber since = new VersionNumber("2.0"); rr.then(r -> { - List detachedPlugins = ClassicPluginStrategy.getDetachedPlugins(since); + List detachedPlugins = DetachedPluginsUtil.getDetachedPlugins(since); assertThat("Plugins have been detached since the pre-upgrade version", detachedPlugins.size(), greaterThan(1)); assertThat("Plugins detached between the pre-upgrade version and the current version should be installed", @@ -131,7 +131,7 @@ public class LoadDetachedPluginsTest { // @LocalData has command-launcher 1.2 installed, which should not be downgraded to the detached version: 1.0. VersionNumber since = new VersionNumber("2.0"); rr.then(r -> { - List detachedPlugins = ClassicPluginStrategy.getDetachedPlugins(since); + List detachedPlugins = DetachedPluginsUtil.getDetachedPlugins(since); assertThat("Plugins have been detached since the pre-upgrade version", detachedPlugins.size(), greaterThan(1)); assertThat("Plugins detached between the pre-upgrade version and the current version should be installed", @@ -146,14 +146,14 @@ public class LoadDetachedPluginsTest { @Test public void newInstallation() { rr.then(r -> { - List detachedPlugins = ClassicPluginStrategy.getDetachedPlugins(); + List detachedPlugins = DetachedPluginsUtil.getDetachedPlugins(); assertThat("Detached plugins should exist", detachedPlugins, not(empty())); assertThat("Detached plugins should not be installed on a new instance", getInstalledDetachedPlugins(r, detachedPlugins), empty()); assertNoFailedPlugins(r); }); rr.then(r -> { - List detachedPlugins = ClassicPluginStrategy.getDetachedPlugins(); + List detachedPlugins = DetachedPluginsUtil.getDetachedPlugins(); assertThat("Detached plugins should exist", detachedPlugins, not(empty())); assertThat("Detached plugins should not be installed after restarting", getInstalledDetachedPlugins(r, detachedPlugins), empty()); diff --git a/war/pom.xml b/war/pom.xml index 73ffeb2f33..7822db814b 100644 --- a/war/pom.xml +++ b/war/pom.xml @@ -414,6 +414,12 @@ THE SOFTWARE. 1.0 hpi + + io.jenkins.plugins + jaxb + 2.3.0 + hpi + ${project.build.directory}/${project.build.finalName}/WEB-INF/detached-plugins true