Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[JENKINS-55048] Add support for plugin Java requirement metadata #3016

Merged
merged 25 commits into from
Jan 10, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
3fa7427
[JENKINS-20679] Add support for plugin Java requirement metadata
daniel-beck Sep 11, 2017
e65ceca
Rename 'minimumJavaVersion' to 'requiredJava'
daniel-beck Sep 11, 2017
d1c972a
Adapt tests to changed localizable strings
daniel-beck Sep 11, 2017
725fb30
Update Javadocs and comments
daniel-beck Sep 11, 2017
f55864f
Add Java version check to setup wizard and installNecessaryPlugins
daniel-beck Sep 11, 2017
5cdbf39
Use java.specification.version
daniel-beck Sep 20, 2018
e364a84
Adapt to newer core internals
daniel-beck Sep 20, 2018
27c49bf
Merge branch 'master' into JENKINS-20679-filter-out-plugins-compiled-…
batmat Jan 7, 2019
29de9aa
Rename to minimumJavaVersion to keep the name across tooling
batmat Jan 7, 2019
89d20ef
Add warning if minimumJavaVersion is not parseable
batmat Jan 7, 2019
246adab
Add test covering methods for "is*ForNewerJava()"
batmat Jan 7, 2019
8a666ce
Log warning in case of unparseable minimum Java version
batmat Jan 7, 2019
e2a5b77
Fix Javadoc: isForNewerJava() is the one checking the current plugin
batmat Jan 8, 2019
1f7cb5a
Factor out the message and plugin name in submethod
batmat Jan 8, 2019
6fe6615
Remove json comment
batmat Jan 8, 2019
c61031d
Add missing space in javadoc
batmat Jan 9, 2019
791253d
Remove duplicated Javadoc
batmat Jan 9, 2019
ee04f92
Add reference to related JIRA entry
batmat Jan 9, 2019
282e42b
Provide more information about where/how the `Minimum-Java-Version` i…
batmat Jan 9, 2019
94c114a
Clarify what `null` means
batmat Jan 9, 2019
36d32ed
Remove apostrophes to avoid issues in string un/expansion
batmat Jan 9, 2019
3b07b35
Remove spurious 'it' (and duplicate doc)
batmat Jan 9, 2019
3ced8bb
Make the phrasing less awkward
batmat Jan 9, 2019
2225631
Remove also param given minimumJavaVersion is a class member already
batmat Jan 9, 2019
1afa724
Introduce method to fetch java.specification.version
batmat Jan 9, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions core/src/main/java/hudson/PluginManager.java
Original file line number Diff line number Diff line change
Expand Up @@ -1824,6 +1824,9 @@ public List<Future<UpdateCenter.UpdateCenterJob>> prevalidateConfig(InputStream
if (toInstall.isForNewerHudson()) {
LOGGER.log(WARNING, "{0}@{1} was built for a newer Jenkins", new Object[] {toInstall.name, toInstall.version});
}
if (toInstall.isForNewerJava()) {
LOGGER.log(WARNING, "{0}@{1} was built for a newer Java", new Object[] {toInstall.name, toInstall.version});
}
jobs.add(toInstall.deploy(true));
} else if (pw.isOlderThan(requestedPlugin.getValue())) { // upgrade
UpdateSite.Plugin toInstall = uc.getPlugin(requestedPlugin.getKey(), requestedPlugin.getValue());
Expand All @@ -1841,6 +1844,9 @@ public List<Future<UpdateCenter.UpdateCenterJob>> prevalidateConfig(InputStream
if (toInstall.isForNewerHudson()) {
LOGGER.log(WARNING, "{0}@{1} was built for a newer Jenkins", new Object[] {toInstall.name, toInstall.version});
}
if (toInstall.isForNewerJava()) {
LOGGER.log(WARNING, "{0}@{1} was built for a newer Java", new Object[] {toInstall.name, toInstall.version});
}
if (!toInstall.isCompatibleWithInstalledVersion()) {
LOGGER.log(WARNING, "{0}@{1} is incompatible with the installed @{2}", new Object[] {toInstall.name, toInstall.version, pw.getVersion()});
}
Expand Down
23 changes: 23 additions & 0 deletions core/src/main/java/hudson/PluginWrapper.java
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
import hudson.util.VersionNumber;
import jenkins.YesNoMaybe;
import jenkins.model.Jenkins;
import jenkins.util.java.JavaUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.LogFactory;
import org.kohsuke.accmod.Restricted;
Expand Down Expand Up @@ -495,6 +496,20 @@ private String getVersionOf(Manifest manifest) {
return null;
}

/**
* Returns the minimum Java version of this plugin, as specified in the plugin metadata.
* Generally coming from the <code>java.level</code> extracted as MANIFEST's metadata with
* <a href="https://github.com/jenkinsci/plugin-pom/pull/134">this addition on the plugins' parent pom</a>.
*
* @see <a href="https://github.com/jenkinsci/maven-hpi-plugin/pull/75">maven-hpi-plugin#PR-75</a>.
*
* @since TODO
*/
@Exported
public @CheckForNull String getMinimumJavaVersion() {
return manifest.getMainAttributes().getValue("Minimum-Java-Version");
}

/**
* Returns the version number of this plugin
*/
Expand Down Expand Up @@ -745,6 +760,14 @@ public boolean hasLicensesXml() {
versionDependencyError(Messages.PluginWrapper_obsoleteCore(Jenkins.getVersion().toString(), requiredCoreVersion), Jenkins.getVersion().toString(), requiredCoreVersion);
}
}

String minimumJavaVersion = getMinimumJavaVersion();
if (minimumJavaVersion != null) {
VersionNumber actualVersion = JavaUtils.getCurrentJavaRuntimeVersionNumber();
if (actualVersion.isOlderThan(new VersionNumber(minimumJavaVersion))) {
versionDependencyError(Messages.PluginWrapper_obsoleteJava(actualVersion.toString(), minimumJavaVersion), actualVersion.toString(), minimumJavaVersion);
}
}
}
// make sure dependencies exist
for (Dependency d : dependencies) {
Expand Down
68 changes: 68 additions & 0 deletions core/src/main/java/hudson/model/UpdateSite.java
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@
import jenkins.security.UpdateSiteWarningsConfiguration;
import jenkins.util.JSONSignatureValidator;
import jenkins.util.SystemProperties;
import jenkins.util.java.JavaUtils;
import net.sf.json.JSONArray;
import net.sf.json.JSONException;
import net.sf.json.JSONObject;
Expand Down Expand Up @@ -967,6 +968,13 @@ public final class Plugin extends Entry {
*/
@Exported
public final String requiredCore;
/**
* Version of Java this plugin requires to run.
*
* @since TODO
*/
@Exported
public final String minimumJavaVersion;
/**
* Categories for grouping plugins, taken from labels assigned to wiki page.
* Can be null.
Expand All @@ -993,6 +1001,7 @@ public Plugin(String sourceId, JSONObject o) {
this.title = get(o,"title");
this.excerpt = get(o,"excerpt");
this.compatibleSinceVersion = Util.intern(get(o,"compatibleSinceVersion"));
this.minimumJavaVersion = Util.intern(get(o, "minimumJavaVersion"));
this.requiredCore = Util.intern(get(o,"requiredCore"));
this.categories = o.has("labels") ? internInPlace((String[])o.getJSONArray("labels").toArray(EMPTY_STRING_ARRAY)) : null;
JSONArray ja = o.getJSONArray("dependencies");
Expand Down Expand Up @@ -1118,6 +1127,21 @@ public boolean isForNewerHudson() {
}
}

/**
* Returns true iff the plugin declares a minimum Java version and it's newer than what the Jenkins master is running on.
* @since TODO
*/
public boolean isForNewerJava() {
try {
final VersionNumber currentRuntimeJavaVersion = JavaUtils.getCurrentJavaRuntimeVersionNumber();
return minimumJavaVersion != null && new VersionNumber(minimumJavaVersion).isNewerThan(
currentRuntimeJavaVersion);
} catch (NumberFormatException nfe) {
logBadMinJavaVersion();
return false; // treat this as undeclared minimum Java version
}
}

public VersionNumber getNeededDependenciesRequiredCore() {
VersionNumber versionNumber = null;
try {
Expand All @@ -1132,6 +1156,36 @@ public VersionNumber getNeededDependenciesRequiredCore() {
return versionNumber;
}

/**
* Returns the minimum Java version needed to use the plugin and all its dependencies.
* @since TODO
* @return the minimum Java version needed to use the plugin and all its dependencies, or null if unspecified.
*/
@CheckForNull
public VersionNumber getNeededDependenciesMinimumJavaVersion() {
VersionNumber versionNumber = null;
try {
versionNumber = minimumJavaVersion == null ? null : new VersionNumber(minimumJavaVersion);
} catch (NumberFormatException nfe) {
logBadMinJavaVersion();
}
for (Plugin p: getNeededDependencies()) {
VersionNumber v = p.getNeededDependenciesMinimumJavaVersion();
if (v == null) {
continue;
}
if (versionNumber == null || v.isNewerThan(versionNumber)) {
versionNumber = v;
}
}
return versionNumber;
}

private void logBadMinJavaVersion() {
LOGGER.log(Level.WARNING, "minimumJavaVersion was specified for plugin {0} but unparseable (received {1})",
new String[]{this.name, this.minimumJavaVersion});
}

public boolean isNeededDependenciesForNewerJenkins() {
return isNeededDependenciesForNewerJenkins(new PluginManager.MetadataCache());
}
Expand All @@ -1148,6 +1202,20 @@ public boolean isNeededDependenciesForNewerJenkins(PluginManager.MetadataCache c
});
}

/**
* Returns true iff any of the plugin dependencies require a newer Java than Jenkins is running on.
*
* @since TODO
*/
public boolean isNeededDependenciesForNewerJava() {
for (Plugin p: getNeededDependencies()) {
if (p.isForNewerJava() || p.isNeededDependenciesForNewerJava()) {
return true;
}
}
return false;
}

/**
* If at least some of the plugin's needed dependencies are already installed, and the new version of the
* needed dependencies plugin have a "compatibleSinceVersion"
Expand Down
2 changes: 1 addition & 1 deletion core/src/main/java/jenkins/install/SetupWizard.java
Original file line number Diff line number Diff line change
Expand Up @@ -500,7 +500,7 @@ public JSONArray getPlatformPluginUpdates() {
for (UpdateSite site : jenkins.getUpdateCenter().getSiteList()) {
UpdateSite.Plugin sitePlug = site.getPlugin(pluginName);
if (sitePlug != null
&& !sitePlug.isForNewerHudson()
&& !sitePlug.isForNewerHudson() && !sitePlug.isForNewerJava()
&& !sitePlug.isNeededDependenciesForNewerJenkins()) {
foundCompatibleVersion = true;
break;
Expand Down
20 changes: 18 additions & 2 deletions core/src/main/java/jenkins/util/java/JavaUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
*/
package jenkins.util.java;

import hudson.util.VersionNumber;
import org.kohsuke.accmod.Restricted;
import org.kohsuke.accmod.restrictions.NoExternalUse;

Expand All @@ -42,7 +43,7 @@ private JavaUtils() {
* @return {@code true} if it is Java 8 or older version
*/
public static boolean isRunningWithJava8OrBelow() {
String javaVersion = System.getProperty("java.specification.version");
String javaVersion = getCurrentRuntimeJavaVersion();
return javaVersion.startsWith("1.");
}

Expand All @@ -51,8 +52,23 @@ public static boolean isRunningWithJava8OrBelow() {
* @return {@code true} if it is Java 9 or above
*/
public static boolean isRunningWithPostJava8() {
String javaVersion = System.getProperty("java.specification.version");
String javaVersion = getCurrentRuntimeJavaVersion();
return !javaVersion.startsWith("1.");
}

/**
* Returns the JVM's current version as a {@link VersionNumber} instance.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Best to document this with examples. IIUC these would be

  • 1.8
  • 9
  • 10
  • 11

?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes. Basically JEP-223

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great, but please link to this so that people do not have to discover it by code inspection and independent research.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jglick fix proposed in #3842

*/
public static VersionNumber getCurrentJavaRuntimeVersionNumber() {
return new VersionNumber(getCurrentRuntimeJavaVersion());
}

/**
* Returns the JVM's current version as a {@link String}.
* @see System#getProperty(String)
*/
public static String getCurrentRuntimeJavaVersion() {
// TODO: leverage Runtime.version() once on Java 9+
return System.getProperty("java.specification.version");
}
}
13 changes: 7 additions & 6 deletions core/src/main/resources/hudson/Messages.properties
Original file line number Diff line number Diff line change
Expand Up @@ -79,13 +79,14 @@ ProxyConfiguration.Success=Success

Functions.NoExceptionDetails=No Exception details

PluginWrapper.missing={0} v{1} is missing. To fix, install v{1} or later.
PluginWrapper.failed_to_load_plugin={0} v{1} failed to load.
PluginWrapper.failed_to_load_dependency={0} v{1} failed to load. Fix this plugin first.
PluginWrapper.disabledAndObsolete={0} v{1} is disabled and older than required. To fix, install v{2} or later and enable it.
PluginWrapper.missing={0} version {1} is missing. To fix, install version {1} or later.
PluginWrapper.failed_to_load_plugin={0} version {1} failed to load.
PluginWrapper.failed_to_load_dependency={0} version {1} failed to load. Fix this plugin first.
PluginWrapper.disabledAndObsolete={0} version {1} is disabled and older than required. To fix, install version {2} or later and enable it.
PluginWrapper.disabled={0} is disabled. To fix, enable it.
PluginWrapper.obsolete={0} v{1} is older than required. To fix, install v{2} or later.
PluginWrapper.obsoleteCore=You must update Jenkins from v{0} to v{1} or later to run this plugin.
PluginWrapper.obsolete={0} version {1} is older than required. To fix, install version {2} or later.
PluginWrapper.obsoleteCore=You must update Jenkins from version {0} to version {1} or later to run this plugin.
PluginWrapper.obsoleteJava=You must update Java from version {0} to version {1} or later to run this plugin.
PluginWrapper.PluginWrapperAdministrativeMonitor.DisplayName=Plugins Failed To Load
PluginWrapper.Already.Disabled=The plugin ''{0}'' was already disabled
PluginWrapper.Plugin.Has.Dependant=The plugin ''{0}'' has, at least, one dependant plugin ({1}) and the disable strategy is {2}, so it cannot be disabled
Expand Down
3 changes: 3 additions & 0 deletions core/src/main/resources/hudson/PluginManager/_table.css
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,6 @@
margin: 1em;
text-align: right;
}
.compatWarning, .securityWarning {
font-weight: bold;
}
6 changes: 6 additions & 0 deletions core/src/main/resources/hudson/PluginManager/table.jelly
Original file line number Diff line number Diff line change
Expand Up @@ -107,12 +107,18 @@ THE SOFTWARE.
<j:if test="${p.isForNewerHudson()}">
<div class="compatWarning">${%coreWarning(p.requiredCore)}</div>
</j:if>
<j:if test="${p.isForNewerJava()}">
<div class="compatWarning">${%javaWarning(p.minimumJavaVersion)}</div>
</j:if>
<j:if test="${!p.isNeededDependenciesCompatibleWithInstalledVersion(cache)}">
<div class="compatWarning">${%depCompatWarning}</div>
</j:if>
<j:if test="${p.isNeededDependenciesForNewerJenkins(cache)}">
<div class="compatWarning">${%depCoreWarning(p.getNeededDependenciesRequiredCore().toString())}</div>
</j:if>
<j:if test="${p.isNeededDependenciesForNewerJava()}">
<div class="compatWarning">${%depJavaWarning(p.getNeededDependenciesMinimumJavaVersion().toString())}</div>
</j:if>
<j:if test="${p.hasWarnings()}">
<div class="securityWarning">${%securityWarning}
<ul>
Expand Down
7 changes: 7 additions & 0 deletions core/src/main/resources/hudson/PluginManager/table.properties
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@ compatWarning=\
coreWarning=\
Warning: This plugin is built for Jenkins {0} or newer. \
Jenkins will refuse to load this plugin if installed.
javaWarning=\
Warning: This plugin requires Java {0} or newer. \
Jenkins will refuse to load this plugin if installed.
depCompatWarning=\
Warning: This plugin requires dependent plugins be upgraded and at least one of these dependent plugins claims to use a different settings format than the installed version. \
Jobs using that plugin may need to be reconfigured, and/or you may not be able to cleanly revert to the prior version without manually restoring old settings. \
Expand All @@ -34,6 +37,10 @@ depCoreWarning=\
Warning: This plugin requires dependent plugins that require Jenkins {0} or newer. \
Jenkins will refuse to load the dependent plugins requiring a newer version of Jenkins, \
and in turn loading this plugin will fail.
depJavaWarning=\
Warning: this plugin requires other plugins which in turn require Java {0} or newer. \
Jenkins will refuse to load the dependent plugins requiring a newer version of Jenkins, \
and in turn loading this plugin will fail.
securityWarning=\
Warning: This plugin version may not be safe to use. Please review the following security notices:

8 changes: 4 additions & 4 deletions core/src/test/java/hudson/PluginWrapperTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ public void jenkinsCoreTooOld() throws Exception {
pw.resolvePluginDependencies();
fail();
} catch (IOException ex) {
assertContains(ex, "fake v42 failed to load", "update Jenkins from v2.0 to v3.0");
assertContains(ex, "fake version 42 failed to load", "update Jenkins from version 2.0 to version 3.0");
}
}

Expand All @@ -64,7 +64,7 @@ public void dependencyNotInstalled() throws Exception {
pw.resolvePluginDependencies();
fail();
} catch (IOException ex) {
assertContains(ex, "dependee v42 failed to load", "dependency v42 is missing. To fix, install v42 or later");
assertContains(ex, "dependee version 42 failed to load", "dependency version 42 is missing. To fix, install version 42 or later");
}
}

Expand All @@ -76,7 +76,7 @@ public void dependencyOutdated() throws Exception {
pw.resolvePluginDependencies();
fail();
} catch (IOException ex) {
assertContains(ex, "dependee v42 failed to load", "dependency v3 is older than required. To fix, install v5 or later");
assertContains(ex, "dependee version 42 failed to load", "dependency version 3 is older than required. To fix, install version 5 or later");
}
}

Expand All @@ -88,7 +88,7 @@ public void dependencyFailedToLoad() throws Exception {
pw.resolvePluginDependencies();
fail();
} catch (IOException ex) {
assertContains(ex, "dependee v42 failed to load", "dependency v5 failed to load. Fix this plugin first");
assertContains(ex, "dependee version 42 failed to load", "dependency version 5 failed to load. Fix this plugin first");
}
}

Expand Down
26 changes: 26 additions & 0 deletions test/src/test/java/hudson/model/UpdateSiteTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,32 @@ public void shutdownWebserver() throws Exception {
assertNotEquals("plugin data is present", Collections.emptyMap(), site.getData().plugins);
}

@Issue("JENKINS-55048")
@Test public void minimumJavaVersion() throws Exception {
// TODO: factor out the sites init
PersistedList<UpdateSite> sites = j.jenkins.getUpdateCenter().getSites();
sites.clear();
URL url = new URL(baseUrl, "/plugins/minJavaVersion-update-center.json");
UpdateSite site = new UpdateSite(UpdateCenter.ID_DEFAULT, url.toString());
sites.add(site);
assertEquals(FormValidation.ok(), site.updateDirectly(false).get());
// END TODO

final UpdateSite.Plugin tasksPlugin = site.getPlugin("tasks");
assertNotNull(tasksPlugin);
assertFalse(tasksPlugin.isNeededDependenciesForNewerJava());
assertFalse(tasksPlugin.isForNewerJava());

final UpdateSite.Plugin pluginCompiledForTooRecentJava = site.getPlugin("java-too-recent");
assertFalse(pluginCompiledForTooRecentJava.isNeededDependenciesForNewerJava());
assertTrue(pluginCompiledForTooRecentJava.isForNewerJava());

final UpdateSite.Plugin pluginDependingOnPluginCompiledForTooRecentJava = site.getPlugin("depending-on-too-recent-java");
assertTrue(pluginDependingOnPluginCompiledForTooRecentJava.isNeededDependenciesForNewerJava());
assertFalse(pluginDependingOnPluginCompiledForTooRecentJava.isForNewerJava());

}

@Issue("JENKINS-31448")
@Test public void isLegacyDefault() throws Exception {
assertFalse("isLegacyDefault should be false with null id",new UpdateSite(null,"url").isLegacyDefault());
Expand Down
Loading