diff --git a/.gitpod.yml b/.gitpod.yml new file mode 100644 index 000000000000..d3bedadf1f9b --- /dev/null +++ b/.gitpod.yml @@ -0,0 +1,27 @@ +tasks: + - init: | + mvn -am -pl war,bom -P quick-build clean install + command: | + mvn -pl war jetty:run -Dhost=0.0.0.0 + name: Run + - command: gp await-port 8080 && gp url 8080 && gp preview $(gp url 8080)/jenkins/ + name: Preview + +github: + prebuilds: + pullRequestsFromForks: true + addBadge: true + +jetbrains: + intellij: + plugins: + - Stapler plugin for IntelliJ IDEA + prebuilds: + version: stable + +vscode: + extensions: + - vscjava.vscode-java-pack + +image: + file: .gitpod/Dockerfile diff --git a/.gitpod/Dockerfile b/.gitpod/Dockerfile new file mode 100644 index 000000000000..e3c88317008f --- /dev/null +++ b/.gitpod/Dockerfile @@ -0,0 +1,4 @@ +FROM gitpod/workspace-full + +RUN brew install gh && \ + bash -c ". /home/gitpod/.sdkman/bin/sdkman-init.sh && sdk install maven 3.8.4 && sdk default maven 3.8.4" diff --git a/.idea/icon.svg b/.idea/icon.svg index 8dd8fec29266..44161638ba48 100644 --- a/.idea/icon.svg +++ b/.idea/icon.svg @@ -1,81 +1 @@ - - - - - - - image/svg+xml - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + diff --git a/.mvn/jvm.config b/.mvn/jvm.config index e93a5b43ee1c..f0106d148540 100644 --- a/.mvn/jvm.config +++ b/.mvn/jvm.config @@ -1 +1 @@ --Xmx800m +-Xmx1100m diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 8eb9822ccd8e..fb32776fb370 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -31,7 +31,7 @@ You can read a description of the [building and debugging process here]. If you want simply to build the `jenkins.war` file as fast as possible without tests, run: ```sh -mvn -am -pl war,bom -DskipTests -Dspotbugs.skip -Dspotless.check.skip clean install +mvn -am -pl war,bom -Pquick-build clean install ``` The WAR file will be created in `war/target/jenkins.war`. @@ -62,6 +62,16 @@ On another terminal, move to the war folder and start a [webpack](https://webpac cd war; yarn start ``` +### Gitpod + +You can open this project as a [Gitpod workspace](https://www.gitpod.io/) which comes pre-configured with all the tools you will need. +You can use IntelliJ IDEA (preferred) or VS Code (alternate) in the browser. + +[![Open in Gitpod](https://gitpod.io/button/open-in-gitpod.svg)](https://gitpod.io/#https://github.com/jenkinsci/jenkins) + +If you prefer using IntelliJ IDEA, you can setup Gitpod integration with JetBrains Gateway using the instructions on [gitpod.io](https://www.gitpod.io/docs/ides-and-editors/intellij), +which will open the workspace in IntelliJ IDEA using JetBrains Gateway. + ## Testing changes Jenkins core includes unit and functional tests as a part of the repository. @@ -220,4 +230,4 @@ just submit a pull request. [Jenkins Pipeline]: https://www.jenkins.io/doc/book/pipeline/ [Jenkinsfile]: ./Jenkinsfile [download Maven here]: https://maven.apache.org/download.cgi -[GitHub pull request]: https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/about-pull-requests \ No newline at end of file +[GitHub pull request]: https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/about-pull-requests diff --git a/Jenkinsfile b/Jenkinsfile index 8016e9d91363..05f5fbf2398c 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -61,9 +61,13 @@ for (i = 0; i < buildTypes.size(); i++) { 'clean', 'install', ] - infra.runMaven(mavenOptions, jdk) - if (isUnix()) { - sh 'git add . && git diff --exit-code HEAD' + try { + infra.runMaven(mavenOptions, jdk) + if (isUnix()) { + sh 'git add . && git diff --exit-code HEAD' + } + } finally { + archiveArtifacts allowEmptyArchive: true, artifacts: '**/target/surefire-reports/*.dumpstream' } } } @@ -71,7 +75,6 @@ for (i = 0; i < buildTypes.size(); i++) { // Once we've built, archive the artifacts and the test results. stage("${buildType} Publishing") { - archiveArtifacts allowEmptyArchive: true, artifacts: '**/target/surefire-reports/*.dumpstream' if (!fileExists('core/target/surefire-reports/TEST-jenkins.Junit4TestsRanTest.xml')) { error 'JUnit 4 tests are no longer being run for the core package' } diff --git a/core/src/main/java/hudson/PluginManager.java b/core/src/main/java/hudson/PluginManager.java index c3cd3a750e6d..2fce8ae5a52a 100644 --- a/core/src/main/java/hudson/PluginManager.java +++ b/core/src/main/java/hudson/PluginManager.java @@ -187,6 +187,7 @@ * @author Kohsuke Kawaguchi */ @ExportedBean +@SuppressFBWarnings(value = "THROWS_METHOD_THROWS_CLAUSE_BASIC_EXCEPTION", justification = "TODO needs triage") public abstract class PluginManager extends AbstractModelObject implements OnMaster, StaplerOverridable, StaplerProxy { /** Custom plugin manager system property or context param. */ public static final String CUSTOM_PLUGIN_MANAGER = PluginManager.class.getName() + ".className"; diff --git a/core/src/main/java/hudson/cli/declarative/CLIRegisterer.java b/core/src/main/java/hudson/cli/declarative/CLIRegisterer.java index 8b2f2348c577..5fdf42672fbf 100644 --- a/core/src/main/java/hudson/cli/declarative/CLIRegisterer.java +++ b/core/src/main/java/hudson/cli/declarative/CLIRegisterer.java @@ -27,6 +27,7 @@ import static java.util.logging.Level.SEVERE; import edu.umd.cs.findbugs.annotations.NonNull; +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import hudson.AbortException; import hudson.Extension; import hudson.ExtensionComponent; @@ -71,6 +72,12 @@ * @author Kohsuke Kawaguchi */ @Extension +@SuppressFBWarnings( + value = { + "THROWS_METHOD_THROWS_CLAUSE_BASIC_EXCEPTION", + "THROWS_METHOD_THROWS_RUNTIMEEXCEPTION" + }, + justification = "TODO needs triage") public class CLIRegisterer extends ExtensionFinder { @Override public ExtensionComponentSet refresh() throws ExtensionRefreshException { diff --git a/core/src/main/java/hudson/model/AbstractItem.java b/core/src/main/java/hudson/model/AbstractItem.java index bc96c1b08dcf..2fbc1521de3f 100644 --- a/core/src/main/java/hudson/model/AbstractItem.java +++ b/core/src/main/java/hudson/model/AbstractItem.java @@ -110,6 +110,7 @@ // Item doesn't necessarily have to be Actionable, but // Java doesn't let multiple inheritance. @ExportedBean +@SuppressFBWarnings(value = "THROWS_METHOD_THROWS_CLAUSE_THROWABLE", justification = "TODO needs triage") public abstract class AbstractItem extends Actionable implements Item, HttpDeletable, AccessControlled, DescriptorByNameOwner, StaplerProxy { private static final Logger LOGGER = Logger.getLogger(AbstractItem.class.getName()); diff --git a/core/src/main/java/hudson/model/Executor.java b/core/src/main/java/hudson/model/Executor.java index e7b14b72aebf..7ffc181dd791 100644 --- a/core/src/main/java/hudson/model/Executor.java +++ b/core/src/main/java/hudson/model/Executor.java @@ -32,6 +32,7 @@ import edu.umd.cs.findbugs.annotations.CheckForNull; import edu.umd.cs.findbugs.annotations.NonNull; +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import hudson.FilePath; import hudson.Functions; import hudson.Util; @@ -89,6 +90,12 @@ * @author Kohsuke Kawaguchi */ @ExportedBean +@SuppressFBWarnings( + value = { + "THROWS_METHOD_THROWS_CLAUSE_BASIC_EXCEPTION", + "THROWS_METHOD_THROWS_CLAUSE_THROWABLE" + }, + justification = "TODO needs triage") public class Executor extends Thread implements ModelObject { protected final @NonNull Computer owner; private final Queue queue; diff --git a/core/src/main/java/hudson/model/ItemGroupMixIn.java b/core/src/main/java/hudson/model/ItemGroupMixIn.java index 748a13cc6ec4..e9a526ebbe6c 100644 --- a/core/src/main/java/hudson/model/ItemGroupMixIn.java +++ b/core/src/main/java/hudson/model/ItemGroupMixIn.java @@ -24,6 +24,7 @@ package hudson.model; +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import hudson.Util; import hudson.XmlFile; import hudson.model.listeners.ItemListener; @@ -61,6 +62,7 @@ * @author Kohsuke Kawaguchi * @see ViewGroupMixIn */ +@SuppressFBWarnings(value = "THROWS_METHOD_THROWS_CLAUSE_THROWABLE", justification = "TODO needs triage") public abstract class ItemGroupMixIn { /** * {@link ItemGroup} for which we are working. diff --git a/core/src/main/java/hudson/model/Queue.java b/core/src/main/java/hudson/model/Queue.java index 50ad97232bd5..982c74c22380 100644 --- a/core/src/main/java/hudson/model/Queue.java +++ b/core/src/main/java/hudson/model/Queue.java @@ -171,6 +171,7 @@ * @see QueueTaskDispatcher */ @ExportedBean +@SuppressFBWarnings(value = "THROWS_METHOD_THROWS_CLAUSE_BASIC_EXCEPTION", justification = "TODO needs triage") public class Queue extends ResourceController implements Saveable { /** diff --git a/core/src/main/java/hudson/model/ResourceController.java b/core/src/main/java/hudson/model/ResourceController.java index a3702ab79e61..1516272f1dcc 100644 --- a/core/src/main/java/hudson/model/ResourceController.java +++ b/core/src/main/java/hudson/model/ResourceController.java @@ -39,6 +39,12 @@ * Controls mutual exclusion of {@link ResourceList}. * @author Kohsuke Kawaguchi */ +@SuppressFBWarnings( + value = { + "THROWS_METHOD_THROWS_CLAUSE_BASIC_EXCEPTION", + "THROWS_METHOD_THROWS_CLAUSE_THROWABLE" + }, + justification = "TODO needs triage") public class ResourceController { /** * {@link ResourceList}s that are used by activities that are in progress. diff --git a/core/src/main/java/hudson/model/UpdateSite.java b/core/src/main/java/hudson/model/UpdateSite.java index 389e3cb3890f..76c14164981c 100644 --- a/core/src/main/java/hudson/model/UpdateSite.java +++ b/core/src/main/java/hudson/model/UpdateSite.java @@ -104,6 +104,7 @@ * @since 1.333 */ @ExportedBean +@SuppressFBWarnings(value = "THROWS_METHOD_THROWS_CLAUSE_BASIC_EXCEPTION", justification = "TODO needs triage") public class UpdateSite { /** * What's the time stamp of data file? diff --git a/core/src/main/java/hudson/triggers/SafeTimerTask.java b/core/src/main/java/hudson/triggers/SafeTimerTask.java index db07c96867df..5a261d0e9a2e 100644 --- a/core/src/main/java/hudson/triggers/SafeTimerTask.java +++ b/core/src/main/java/hudson/triggers/SafeTimerTask.java @@ -24,6 +24,7 @@ package hudson.triggers; +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import hudson.model.AperiodicWork; import hudson.model.AsyncAperiodicWork; import hudson.model.AsyncPeriodicWork; @@ -47,6 +48,7 @@ * @author Kohsuke Kawaguchi * @since 1.124 */ +@SuppressFBWarnings(value = "THROWS_METHOD_THROWS_CLAUSE_BASIC_EXCEPTION", justification = "TODO needs triage") public abstract class SafeTimerTask extends TimerTask { /** diff --git a/core/src/main/java/hudson/util/FormFieldValidator.java b/core/src/main/java/hudson/util/FormFieldValidator.java index 4453b60be333..f4ec7899a5b4 100644 --- a/core/src/main/java/hudson/util/FormFieldValidator.java +++ b/core/src/main/java/hudson/util/FormFieldValidator.java @@ -230,14 +230,12 @@ private void _errorWithMarkup(String message, String cssClass) throws IOExceptio ok(); } else { response.setContentType("text/html;charset=UTF-8"); - // 1x16 spacer needed for IE since it doesn't support min-height if (APPLY_CONTENT_SECURITY_POLICY_HEADERS) { for (String header : new String[]{"Content-Security-Policy", "X-WebKit-CSP", "X-Content-Security-Policy"}) { response.setHeader(header, "sandbox; default-src 'none';"); } } - response.getWriter().print("
" + + response.getWriter().print("
" + message + "
"); } } diff --git a/core/src/main/java/hudson/util/FormFillFailure.java b/core/src/main/java/hudson/util/FormFillFailure.java index 48cab2ce5fd9..5ebd0d5a31e6 100644 --- a/core/src/main/java/hudson/util/FormFillFailure.java +++ b/core/src/main/java/hudson/util/FormFillFailure.java @@ -31,7 +31,6 @@ import java.util.Locale; import javax.servlet.ServletException; import javax.servlet.http.HttpServletResponse; -import jenkins.model.Jenkins; import org.kohsuke.stapler.HttpResponse; import org.kohsuke.stapler.Stapler; import org.kohsuke.stapler.StaplerRequest; @@ -95,7 +94,7 @@ private static FormFillFailure _error(FormValidation.Kind kind, Throwable e, Str } return _errorWithMarkup(Util.escape(message) + - " " + "
" + Messages.FormValidation_Error_Details() + "
"
                 + Util.escape(Functions.printThrowable(e)) +
@@ -137,9 +136,7 @@ public String renderHtml() {
                 if (req == null) { // being called from some other context
                     return message;
                 }
-                // 1x16 spacer needed for IE since it doesn't support min-height
-                return "
" + + return "
" + message + "
"; } diff --git a/core/src/main/java/hudson/util/FormValidation.java b/core/src/main/java/hudson/util/FormValidation.java index 44f81fb1d44d..931e47ccf0ed 100644 --- a/core/src/main/java/hudson/util/FormValidation.java +++ b/core/src/main/java/hudson/util/FormValidation.java @@ -198,7 +198,7 @@ private static FormValidation _error(Kind kind, Throwable e, String message) { if (e == null) return _errorWithMarkup(Util.escape(message), kind); return _errorWithMarkup(Util.escape(message) + - " " + "
" + Messages.FormValidation_Error_Details() + "
"
             + Util.escape(Functions.printThrowable(e)) +
@@ -272,9 +272,7 @@ public String renderHtml() {
                 if (req == null) { // being called from some other context
                     return message;
                 }
-                // 1x16 spacer needed for IE since it doesn't support min-height
-                return "
" + + return "
" + message + "
"; } diff --git a/core/src/main/java/hudson/util/InterceptingProxy.java b/core/src/main/java/hudson/util/InterceptingProxy.java index 3ab7ebb0b318..af368ac5bf5e 100644 --- a/core/src/main/java/hudson/util/InterceptingProxy.java +++ b/core/src/main/java/hudson/util/InterceptingProxy.java @@ -1,5 +1,6 @@ package hudson.util; +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import java.lang.reflect.InvocationHandler; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; @@ -10,6 +11,7 @@ * * @author Kohsuke Kawaguchi */ +@SuppressFBWarnings(value = "THROWS_METHOD_THROWS_CLAUSE_THROWABLE", justification = "TODO needs triage") public abstract class InterceptingProxy { /** * Intercepts every method call. diff --git a/core/src/main/java/hudson/util/RobustReflectionConverter.java b/core/src/main/java/hudson/util/RobustReflectionConverter.java index d1bc500003e1..d7ce1ab5594f 100644 --- a/core/src/main/java/hudson/util/RobustReflectionConverter.java +++ b/core/src/main/java/hudson/util/RobustReflectionConverter.java @@ -44,6 +44,7 @@ import com.thoughtworks.xstream.mapper.Mapper; import com.thoughtworks.xstream.security.InputManipulationException; import edu.umd.cs.findbugs.annotations.NonNull; +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import hudson.diagnosis.OldDataMonitor; import hudson.model.Saveable; import hudson.security.ACL; @@ -77,6 +78,7 @@ * * */ +@SuppressFBWarnings(value = "THROWS_METHOD_THROWS_RUNTIMEEXCEPTION", justification = "TODO needs triage") @SuppressWarnings({"rawtypes", "unchecked"}) public class RobustReflectionConverter implements Converter { diff --git a/core/src/main/java/jenkins/model/Jenkins.java b/core/src/main/java/jenkins/model/Jenkins.java index 3aa50e0882a9..511ee7a610eb 100644 --- a/core/src/main/java/jenkins/model/Jenkins.java +++ b/core/src/main/java/jenkins/model/Jenkins.java @@ -337,6 +337,7 @@ * @author Kohsuke Kawaguchi */ @ExportedBean +@SuppressFBWarnings(value = "THROWS_METHOD_THROWS_CLAUSE_BASIC_EXCEPTION", justification = "TODO needs triage") public class Jenkins extends AbstractCIBase implements DirectlyModifiableTopLevelItemGroup, StaplerProxy, StaplerFallback, ModifiableViewGroup, AccessControlled, DescriptorByNameOwner, ModelObjectWithContextMenu, ModelObjectWithChildren, OnMaster { diff --git a/core/src/main/java/jenkins/model/Nodes.java b/core/src/main/java/jenkins/model/Nodes.java index c8ff0e283e9a..f39377e1bcf0 100644 --- a/core/src/main/java/jenkins/model/Nodes.java +++ b/core/src/main/java/jenkins/model/Nodes.java @@ -26,6 +26,7 @@ import edu.umd.cs.findbugs.annotations.CheckForNull; import edu.umd.cs.findbugs.annotations.NonNull; +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import hudson.BulkChange; import hudson.Util; import hudson.XmlFile; @@ -61,6 +62,7 @@ * @since 1.607 */ @Restricted(NoExternalUse.class) // for now, we may make it public later +@SuppressFBWarnings(value = "THROWS_METHOD_THROWS_CLAUSE_BASIC_EXCEPTION", justification = "TODO needs triage") public class Nodes implements Saveable { /** diff --git a/core/src/main/java/jenkins/model/TransientActionFactory.java b/core/src/main/java/jenkins/model/TransientActionFactory.java index abcd32d71693..21ecc499b9c4 100644 --- a/core/src/main/java/jenkins/model/TransientActionFactory.java +++ b/core/src/main/java/jenkins/model/TransientActionFactory.java @@ -28,6 +28,7 @@ import com.google.common.cache.CacheLoader; import com.google.common.cache.LoadingCache; import edu.umd.cs.findbugs.annotations.NonNull; +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import hudson.ExtensionList; import hudson.ExtensionListListener; import hudson.ExtensionPoint; @@ -48,6 +49,7 @@ * @see Actionable#getAllActions * @since 1.548 */ +@SuppressFBWarnings(value = "THROWS_METHOD_THROWS_CLAUSE_BASIC_EXCEPTION", justification = "TODO needs triage") public abstract class TransientActionFactory implements ExtensionPoint { /** diff --git a/core/src/main/java/jenkins/security/ImpersonatingExecutorService.java b/core/src/main/java/jenkins/security/ImpersonatingExecutorService.java index daf2cc6b0f43..d535f05f756d 100644 --- a/core/src/main/java/jenkins/security/ImpersonatingExecutorService.java +++ b/core/src/main/java/jenkins/security/ImpersonatingExecutorService.java @@ -24,6 +24,7 @@ package jenkins.security; +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import hudson.security.ACL; import hudson.security.ACLContext; import java.util.concurrent.Callable; @@ -36,6 +37,7 @@ * @see SecurityContextExecutorService * @since 2.51 */ +@SuppressFBWarnings(value = "THROWS_METHOD_THROWS_CLAUSE_BASIC_EXCEPTION", justification = "TODO needs triage") public final class ImpersonatingExecutorService extends InterceptingExecutorService { private final Authentication authentication; diff --git a/core/src/main/java/jenkins/security/ImpersonatingScheduledExecutorService.java b/core/src/main/java/jenkins/security/ImpersonatingScheduledExecutorService.java index 788806869520..adf482d1cff5 100644 --- a/core/src/main/java/jenkins/security/ImpersonatingScheduledExecutorService.java +++ b/core/src/main/java/jenkins/security/ImpersonatingScheduledExecutorService.java @@ -24,6 +24,7 @@ package jenkins.security; +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import hudson.security.ACL; import hudson.security.ACLContext; import java.util.concurrent.Callable; @@ -35,6 +36,7 @@ * Variant of {@link ImpersonatingExecutorService} for scheduled services. * @since 2.51 */ +@SuppressFBWarnings(value = "THROWS_METHOD_THROWS_CLAUSE_BASIC_EXCEPTION", justification = "TODO needs triage") public final class ImpersonatingScheduledExecutorService extends InterceptingScheduledExecutorService { private final Authentication authentication; diff --git a/core/src/main/java/jenkins/security/SecurityContextExecutorService.java b/core/src/main/java/jenkins/security/SecurityContextExecutorService.java index e2dfe2b9452e..f464ef8e6863 100644 --- a/core/src/main/java/jenkins/security/SecurityContextExecutorService.java +++ b/core/src/main/java/jenkins/security/SecurityContextExecutorService.java @@ -27,6 +27,7 @@ import static org.springframework.security.core.context.SecurityContextHolder.getContext; import static org.springframework.security.core.context.SecurityContextHolder.setContext; +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import java.util.concurrent.Callable; import java.util.concurrent.ExecutorService; import jenkins.util.InterceptingExecutorService; @@ -43,6 +44,7 @@ * @author Kohsuke Kawaguchi * @since 1.561 */ +@SuppressFBWarnings(value = "THROWS_METHOD_THROWS_CLAUSE_BASIC_EXCEPTION", justification = "TODO needs triage") public class SecurityContextExecutorService extends InterceptingExecutorService { public SecurityContextExecutorService(ExecutorService service) { diff --git a/core/src/main/java/jenkins/util/AtmostOneTaskExecutor.java b/core/src/main/java/jenkins/util/AtmostOneTaskExecutor.java index 6347842856f1..3e7dd9f0f3ef 100644 --- a/core/src/main/java/jenkins/util/AtmostOneTaskExecutor.java +++ b/core/src/main/java/jenkins/util/AtmostOneTaskExecutor.java @@ -44,6 +44,7 @@ * @author Kohsuke Kawaguchi * @see AtmostOneThreadExecutor */ +@SuppressFBWarnings(value = "THROWS_METHOD_THROWS_CLAUSE_BASIC_EXCEPTION", justification = "TODO needs triage") public class AtmostOneTaskExecutor { private static final Logger LOGGER = Logger.getLogger(AtmostOneTaskExecutor.class.getName()); diff --git a/core/src/main/java/jenkins/util/ContextResettingExecutorService.java b/core/src/main/java/jenkins/util/ContextResettingExecutorService.java index 8492719491ea..bbfa5e772da9 100644 --- a/core/src/main/java/jenkins/util/ContextResettingExecutorService.java +++ b/core/src/main/java/jenkins/util/ContextResettingExecutorService.java @@ -1,5 +1,6 @@ package jenkins.util; +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import java.util.concurrent.Callable; import java.util.concurrent.ExecutorService; @@ -11,6 +12,7 @@ * * @author Kohsuke Kawaguchi */ +@SuppressFBWarnings(value = "THROWS_METHOD_THROWS_CLAUSE_BASIC_EXCEPTION", justification = "TODO needs triage") public class ContextResettingExecutorService extends InterceptingExecutorService { public ContextResettingExecutorService(ExecutorService base) { super(base); diff --git a/core/src/main/java/jenkins/util/ProgressiveRendering.java b/core/src/main/java/jenkins/util/ProgressiveRendering.java index 42ef0b76861a..b71482da2d24 100644 --- a/core/src/main/java/jenkins/util/ProgressiveRendering.java +++ b/core/src/main/java/jenkins/util/ProgressiveRendering.java @@ -25,6 +25,7 @@ package jenkins.util; import edu.umd.cs.findbugs.annotations.NonNull; +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import hudson.model.AbstractItem; import java.lang.reflect.Field; import java.lang.reflect.InvocationHandler; @@ -73,6 +74,7 @@ * {@code ui-samples-plugin} demonstrates all this. * @since 1.484 */ +@SuppressFBWarnings(value = "THROWS_METHOD_THROWS_CLAUSE_THROWABLE", justification = "TODO needs triage") public abstract class ProgressiveRendering { private static final Logger LOG = Logger.getLogger(ProgressiveRendering.class.getName()); diff --git a/core/src/main/java/org/acegisecurity/providers/dao/AbstractUserDetailsAuthenticationProvider.java b/core/src/main/java/org/acegisecurity/providers/dao/AbstractUserDetailsAuthenticationProvider.java index 1eaf27beac4d..7b8d5f5afb2f 100644 --- a/core/src/main/java/org/acegisecurity/providers/dao/AbstractUserDetailsAuthenticationProvider.java +++ b/core/src/main/java/org/acegisecurity/providers/dao/AbstractUserDetailsAuthenticationProvider.java @@ -24,6 +24,7 @@ package org.acegisecurity.providers.dao; +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import org.acegisecurity.AcegiSecurityException; import org.acegisecurity.Authentication; import org.acegisecurity.AuthenticationException; @@ -35,6 +36,7 @@ * @deprecated use {@link org.springframework.security.authentication.dao.AbstractUserDetailsAuthenticationProvider} */ @Deprecated +@SuppressFBWarnings(value = "THROWS_METHOD_THROWS_RUNTIMEEXCEPTION", justification = "TODO needs triage") public abstract class AbstractUserDetailsAuthenticationProvider implements AuthenticationProvider { private final org.springframework.security.authentication.dao.AbstractUserDetailsAuthenticationProvider delegate = diff --git a/core/src/main/resources/lib/form/entry.jelly b/core/src/main/resources/lib/form/entry.jelly index 6799771ed99b..f8e631265294 100644 --- a/core/src/main/resources/lib/form/entry.jelly +++ b/core/src/main/resources/lib/form/entry.jelly @@ -84,13 +84,12 @@ THE SOFTWARE. -
- -
+
+ +
@@ -109,8 +108,9 @@ THE SOFTWARE.
- -
+
+ +
diff --git a/core/src/main/resources/lib/form/optionalProperty.jelly b/core/src/main/resources/lib/form/optionalProperty.jelly index 1afe8e5f538b..5d646235fdef 100644 --- a/core/src/main/resources/lib/form/optionalProperty.jelly +++ b/core/src/main/resources/lib/form/optionalProperty.jelly @@ -33,12 +33,13 @@ THE SOFTWARE. and the presence of the value. + - + diff --git a/core/src/main/resources/lib/form/select/select.js b/core/src/main/resources/lib/form/select/select.js index 09a4bc385ac7..5e6eeb123f15 100644 --- a/core/src/main/resources/lib/form/select/select.js +++ b/core/src/main/resources/lib/form/select/select.js @@ -11,12 +11,12 @@ function updateListBox(listBox,url,config) { // form entry using tables-to-divs markup. function getStatusElement() { function getStatusForTabularForms() { - return findFollowingTR(listBox, "validation-error-area").firstElementChild.nextSibling; + return listBox.parentNode.querySelector(".validation-error-area"); } function getStatusForDivBasedForms() { var settingMain = listBox.closest('.setting-main') if (!settingMain) { - console.warn("Couldn't find the expected parent element (.setting-main) for element", listBox) + console.warn("Couldn't find the expected validation element (.validation-error-area) for element", listBox.parentNode) return; } diff --git a/core/src/main/resources/lib/form/validateButton.jelly b/core/src/main/resources/lib/form/validateButton.jelly index ba8f19252703..ff98731b5d48 100644 --- a/core/src/main/resources/lib/form/validateButton.jelly +++ b/core/src/main/resources/lib/form/validateButton.jelly @@ -51,16 +51,18 @@ THE SOFTWARE. -
+
+
+ +
+
+ +
-
- -
-
diff --git a/core/src/main/resources/lib/hudson/buildLink.jelly b/core/src/main/resources/lib/hudson/buildLink.jelly index 61bcf8ff481b..18ccfc1ec675 100644 --- a/core/src/main/resources/lib/hudson/buildLink.jelly +++ b/core/src/main/resources/lib/hudson/buildLink.jelly @@ -52,7 +52,7 @@ THE SOFTWARE. - ${jobName_}#${number} + ${jobName_}#${number} diff --git a/pom.xml b/pom.xml index 3d0aac43f8ba..04a02264892e 100644 --- a/pom.xml +++ b/pom.xml @@ -28,7 +28,7 @@ THE SOFTWARE. org.jenkins-ci jenkins - 1.74 + 1.76 @@ -71,7 +71,7 @@ THE SOFTWARE. - 2.351 + 2.355 -SNAPSHOT - 2.22.2 - 2.22.2 + 4.6.1 + 2.22.7 @@ -475,7 +471,7 @@ THE SOFTWARE. -P release,sign clean install - -DskipTests -Danimal.sniffer.skip=false javadoc:javadoc deploy + -DskipTests -Danimal.sniffer.skip=false -Dspotbugs.skip -Dmaven.checkstyle.skip -Dspotless.check.skip generate-resources javadoc:javadoc deploy false true jenkins-@{project.version} diff --git a/src/spotbugs/spotbugs-excludes.xml b/src/spotbugs/spotbugs-excludes.xml index d848c9bc6a45..1e6be9807dbd 100644 --- a/src/spotbugs/spotbugs-excludes.xml +++ b/src/spotbugs/spotbugs-excludes.xml @@ -573,6 +573,194 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/test/pom.xml b/test/pom.xml index 883edac759f0..3bb7f0e85dca 100644 --- a/test/pom.xml +++ b/test/pom.xml @@ -85,7 +85,7 @@ THE SOFTWARE. ${project.groupId} jenkins-test-harness - 1752.v86627a_c48d91 + 1753.v45c760e2400f test @@ -145,13 +145,13 @@ THE SOFTWARE. org.jenkins-ci.plugins cloudbees-folder - 6.722.v8165b_a_cf25e9 + 6.729.v2b_9d1a_74d673 test org.jenkins-ci.plugins junit - 1.63 + 1119.va_a_5e9068da_d7 test diff --git a/test/src/test/java/hudson/model/AbstractProjectTest.java b/test/src/test/java/hudson/model/AbstractProjectTest.java index 2280aba81d19..e3d0f3e23df2 100644 --- a/test/src/test/java/hudson/model/AbstractProjectTest.java +++ b/test/src/test/java/hudson/model/AbstractProjectTest.java @@ -613,17 +613,16 @@ public void dangerousLabelsAreEscaped() throws Exception { *
*/ DomNodeList domNodes = htmlPage.getDocumentElement().querySelectorAll("*"); - assertThat(domNodes, hasSize(5)); + assertThat(domNodes, hasSize(4)); assertEquals("head", domNodes.get(0).getNodeName()); assertEquals("body", domNodes.get(1).getNodeName()); assertEquals("div", domNodes.get(2).getNodeName()); - assertEquals("img", domNodes.get(3).getNodeName()); - assertEquals("a", domNodes.get(4).getNodeName()); + assertEquals("a", domNodes.get(3).getNodeName()); // only: "> // the first double quote was escaped during creation (with the backslash) String unquotedLabel = Label.parseExpression(label).getName(); - HtmlAnchor anchor = (HtmlAnchor) domNodes.get(4); + HtmlAnchor anchor = (HtmlAnchor) domNodes.get(3); assertThat(anchor.getHrefAttribute(), containsString(Util.rawEncode(unquotedLabel))); assertThat(responseContent, containsString("ok")); diff --git a/test/src/test/java/lib/form/NumberTest.java b/test/src/test/java/lib/form/NumberTest.java index 0bef86dbc26f..d12840495522 100644 --- a/test/src/test/java/lib/form/NumberTest.java +++ b/test/src/test/java/lib/form/NumberTest.java @@ -177,7 +177,7 @@ private String typeValueAndGetErrorMessage(HtmlInput input, String value) throws input.reset(); // Remove the value that already in the input.type(value); // Type value to input.fireEvent(Event.TYPE_CHANGE); // The error message is triggered by change event - return input.getParentNode().getNextSibling().getChildNodes().get(1).getChildNodes().get(0).getTextContent(); + return input.getParentNode().getNextSibling().getTextContent(); } diff --git a/war/src/main/less/base/style.less b/war/src/main/less/base/style.less index b1b609dce842..7f7944ce9db9 100644 --- a/war/src/main/less/base/style.less +++ b/war/src/main/less/base/style.less @@ -542,64 +542,6 @@ div.behavior-loading { padding: 0; } - -/* ======================== error/warning message (mainly in the form.) Use them on block elements ======================== */ -.error { - color: #c00; - font-weight: bold; - padding-left: 20px; - min-height: 16px; - line-height: 16px; - background-image: url("../../images/svgs/error.svg"); - background-position: left top; - background-repeat: no-repeat; - background-size: 16px 16px; -} - -.error-inline { - color: #c00; - font-weight: bold; -} - -.warning { - color: #c4a000; - font-weight: bold; - padding-left: 20px; - min-height: 16px; - line-height: 16px; - background-image: url("../../images/svgs/warning.svg"); - background-position: left top; - background-repeat: no-repeat; - background-size: 16px 16px; -} - -.warning-inline { - color: #c4a000; - font-weight: bold; -} - -.info { - position: relative; - color: var(--text-color); - font-weight: bold; - min-height: 16px; - padding-left: 30px; - - &::before { - content: ""; - position: absolute; - top: 0; - left: 0; - bottom: 0; - width: 20px; - background: currentColor; - mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' class='ionicon' viewBox='0 0 512 512'%3E%3Ctitle%3EArrow Forward%3C/title%3E%3Cpath fill='none' stroke='currentColor' stroke-linecap='round' stroke-linejoin='round' stroke-width='32' d='M268 112l144 144-144 144M392 256H100'/%3E%3C/svg%3E"); - mask-position: center; - mask-size: contain; - mask-repeat: no-repeat; - } -} - .icon16x16 { width: 16px; height: 16px; diff --git a/war/src/main/less/form/validation.less b/war/src/main/less/form/validation.less new file mode 100644 index 000000000000..3d2a18c04d82 --- /dev/null +++ b/war/src/main/less/form/validation.less @@ -0,0 +1,84 @@ +.validation-error-area { + transition: var(--standard-transition); + opacity: 0; + height: 0; + overflow: hidden; +} + +.validation-error-area--visible { + margin-top: 0.75rem; + opacity: 1; + + & > * { + animation: animate-validation-error-area var(--standard-transition); + } +} + +@keyframes animate-validation-error-area { + from { + opacity: 0; + } + to { + opacity: 1; + } +} + +.error, +.warning, +.info { + position: relative; + padding-left: calc(22px + 0.4rem); + font-weight: 500; + + &::before { + content: ""; + position: absolute; + top: 0; + left: 0; + bottom: 0; + width: 22px; + height: 22px; + background-color: currentColor; + mask-position: top center; + mask-repeat: no-repeat; + mask-size: contain; + } +} + +.ok { + color: var(--text-color-secondary); +} + +.error { + color: var(--red); + + &::before { + mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='512' height='512' viewBox='0 0 512 512'%3E%3Ctitle%3Eionicons-v5-a%3C/title%3E%3Cpath d='M256,48C141.31,48,48,141.31,48,256s93.31,208,208,208,208-93.31,208-208S370.69,48,256,48Zm0,319.91a20,20,0,1,1,20-20A20,20,0,0,1,256,367.91Zm21.72-201.15-5.74,122a16,16,0,0,1-32,0l-5.74-121.94v-.05a21.74,21.74,0,1,1,43.44,0Z'/%3E%3C/svg%3E"); + } +} + +.error-inline { + color: var(--red); + font-weight: 500; +} + +.warning { + color: var(--orange); + + &::before { + mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='512' height='512' viewBox='0 0 512 512'%3E%3Ctitle%3Eionicons-v5-r%3C/title%3E%3Cpath d='M449.07,399.08,278.64,82.58c-12.08-22.44-44.26-22.44-56.35,0L51.87,399.08A32,32,0,0,0,80,446.25H420.89A32,32,0,0,0,449.07,399.08Zm-198.6-1.83a20,20,0,1,1,20-20A20,20,0,0,1,250.47,397.25ZM272.19,196.1l-5.74,122a16,16,0,0,1-32,0l-5.74-121.95v0a21.73,21.73,0,0,1,21.5-22.69h.21a21.74,21.74,0,0,1,21.73,22.7Z'/%3E%3C/svg%3E"); + } +} + +.warning-inline { + color: var(--orange); + font-weight: 500; +} + +.info { + color: var(--text-color); + + &::before { + mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' class='ionicon' viewBox='0 0 512 512'%3E%3Ctitle%3EArrow Forward%3C/title%3E%3Cpath fill='none' stroke='currentColor' stroke-linecap='round' stroke-linejoin='round' stroke-width='32' d='M268 112l144 144-144 144M392 256H100'/%3E%3C/svg%3E"); + } +} diff --git a/war/src/main/less/styles.less b/war/src/main/less/styles.less index ea74d1e9e6b0..22772eb8756d 100644 --- a/war/src/main/less/styles.less +++ b/war/src/main/less/styles.less @@ -32,6 +32,7 @@ html { @import './form/search'; @import './form/select'; @import './form/toggle-switch'; +@import './form/validation'; @import './modules/app-bar'; @import './modules/badges'; diff --git a/war/src/main/webapp/scripts/hudson-behavior.js b/war/src/main/webapp/scripts/hudson-behavior.js index 1e94eb6f928e..c3aebaaa8b2e 100644 --- a/war/src/main/webapp/scripts/hudson-behavior.js +++ b/war/src/main/webapp/scripts/hudson-behavior.js @@ -232,7 +232,7 @@ var FormChecker = { this.sendRequest(next.url, { method : next.method, onComplete : function(x) { - applyErrorMessage(next.target, x); + updateValidationArea(next.target, x.responseText); FormChecker.inProgress--; FormChecker.schedule(); layoutUpdateCallback.call(); @@ -500,16 +500,53 @@ var tooltip; // Behavior rules //======================================================== // using tag names in CSS selector makes the processing faster + + +/** + * Updates the validation area for a form element + * @param {HTMLElement} validationArea The validation area for a given form element + * @param {string} content The content to update the validation area with + */ +function updateValidationArea(validationArea, content) { + validationArea.classList.add("validation-error-area--visible"); + + if (content === "
") { + validationArea.classList.remove("validation-error-area--visible"); + validationArea.style.height = "0px"; + validationArea.innerHTML = content; + } else { + // Only change content if different, causes an unnecessary animation otherwise + if (validationArea.innerHTML !== content) { + validationArea.innerHTML = content; + validationArea.style.height = validationArea.children[0].offsetHeight + "px"; + + // Only include the notice in the validation-error-area, move all other elements out + if (validationArea.children.length > 1) { + Array.from(validationArea.children).slice(1).forEach((element) => { + validationArea.after(element); + }) + } + + Behaviour.applySubtree(validationArea); + // For errors with additional details, apply the subtree to the expandable details pane + if (validationArea.nextElementSibling) { + Behaviour.applySubtree(validationArea.nextElementSibling); + } + } + } +} + function registerValidator(e) { // Retrieve the validation error area - var tr = findFollowingTR(e, "validation-error-area"); + var tr = e.closest(".jenkins-form-item").querySelector(".validation-error-area"); if (!tr) { - console.warn("Couldn't find the expected parent element (.setting-main) for element", e) + console.warn("Couldn't find the expected validation element (.validation-error-area) for element", + e.closest(".jenkins-form-item")) return; } // find the validation-error-area - e.targetElement = tr.firstElementChild.nextSibling; + e.targetElement = tr; e.targetUrl = function() { var url = this.getAttribute("checkUrl"); @@ -545,19 +582,13 @@ function registerValidator(e) { } var checker = function() { - var target = this.targetElement; + const validationArea = this.targetElement; FormChecker.sendRequest(this.targetUrl(), { method : method, - onComplete : function(x) { - if (x.status == 200) { - // All FormValidation responses are 200 - target.innerHTML = x.responseText; - } else { - // Content is taken from FormValidation#_errorWithMarkup - // TODO Add i18n support - target.innerHTML = "
An internal error occurred during form field validation (HTTP " + x.status + "). Please reload the page and if the problem persists, ask the administrator for help.
"; - } - Behaviour.applySubtree(target); + onComplete: function({status, responseText}) { + // TODO Add i18n support + const errorMessage = `
An internal error occurred during form field validation (HTTP ${status}). Please reload the page and if the problem persists, ask the administrator for help.
`; + updateValidationArea(validationArea, status === 200 ? responseText : errorMessage); } }); } @@ -584,22 +615,25 @@ function registerValidator(e) { } function registerRegexpValidator(e,regexp,message) { - var tr = findFollowingTR(e, "validation-error-area"); + var tr = e.closest(".jenkins-form-item").querySelector( ".validation-error-area"); if (!tr) { - console.warn("Couldn't find the expected parent element (.setting-main) for element", e) + console.warn("Couldn't find the expected parent element (.setting-main) for element", + e.closest(".jenkins-form-item")) return; } // find the validation-error-area - e.targetElement = tr.firstElementChild.nextSibling; + e.targetElement = tr; var checkMessage = e.getAttribute('checkMessage'); if (checkMessage) message = checkMessage; var oldOnchange = e.onchange; e.onchange = function() { var set = oldOnchange != null ? oldOnchange.call(this) : false; if (this.value.match(regexp)) { - if (!set) this.targetElement.innerHTML = "
"; + if (!set) { + updateValidationArea(this.targetElement, `
`) + } } else { - this.targetElement.innerHTML = "
" + message + "
"; + updateValidationArea(this.targetElement, `
${message}
`); set = true; } return set; @@ -613,13 +647,14 @@ function registerRegexpValidator(e,regexp,message) { * @param e Input element */ function registerMinMaxValidator(e) { - var tr = findFollowingTR(e, "validation-error-area"); + var tr = e.closest(".jenkins-form-item").querySelector( ".validation-error-area"); if (!tr) { - console.warn("Couldn't find the expected parent element (.setting-main) for element", e) + console.warn("Couldn't find the expected parent element (.setting-main) for element", + e.closest(".jenkins-form-item")) return; } // find the validation-error-area - e.targetElement = tr.firstElementChild.nextSibling; + e.targetElement = tr; var checkMessage = e.getAttribute('checkMessage'); if (checkMessage) message = checkMessage; var oldOnchange = e.onchange; @@ -638,29 +673,35 @@ function registerMinMaxValidator(e) { if (min <= max) { // Add the validator if min <= max if (parseInt(min) > parseInt(this.value) || parseInt(this.value) > parseInt(max)) { // The value is out of range - this.targetElement.innerHTML = "
This value should be between " + min + " and " + max + "
"; + updateValidationArea(this.targetElement, `
This value should be between ${min} and ${max}
`); set = true; } else { - if (!set) this.targetElement.innerHTML = "
"; // The value is valid + if (!set) { + updateValidationArea(this.targetElement, `
`) + } } } } else if ((min !== null && isInteger(min)) && (max === null || !isInteger(max))) { // There is only 'min' available if (parseInt(min) > parseInt(this.value)) { - this.targetElement.innerHTML = "
This value should be larger than " + min + "
"; + updateValidationArea(this.targetElement, `
This value should be larger than ${min}
`); set = true; } else { - if (!set) this.targetElement.innerHTML = "
"; + if (!set) { + updateValidationArea(this.targetElement, `
`) + } } } else if ((min === null || !isInteger(min)) && (max !== null && isInteger(max))) { // There is only 'max' available if (parseInt(max) < parseInt(this.value)) { - this.targetElement.innerHTML = "
This value should be less than " + max + "
"; + updateValidationArea(this.targetElement, `
This value should be less than ${max}
`); set = true; } else { - if (!set) this.targetElement.innerHTML = "
"; + if (!set) { + updateValidationArea(this.targetElement, `
`) + } } } } @@ -2324,15 +2365,16 @@ function validateButton(checkUrl,paramList,button) { } }); - var spinner = $(button).up("DIV").next(); - var target = spinner.next(); + var spinner = button.up("DIV").children[0]; + var target = spinner.next().next(); spinner.style.display="block"; new Ajax.Request(checkUrl, { parameters: parameters, onComplete: function(rsp) { spinner.style.display="none"; - applyErrorMessage(target, rsp); + target.innerHTML = `
`; + updateValidationArea(target.children[0], rsp.responseText); layoutUpdateCallback.call(); var s = rsp.getResponseHeader("script"); try { @@ -2344,26 +2386,6 @@ function validateButton(checkUrl,paramList,button) { }); } -function applyErrorMessage(elt, rsp) { - if (rsp.status == 200) { - elt.innerHTML = rsp.responseText; - } else { - var id = 'valerr' + (iota++); - elt.innerHTML = 'ERROR'; - var error = document.getElementById('error-description'); // cf. oops.jelly - if (error) { - var div = document.getElementById(id); - while (div.firstElementChild) { - div.removeChild(div.firstElementChild); - } - div.appendChild(error); - } - } - Behaviour.applySubtree(elt); -} - // create a combobox. // @param idOrField // ID of the element that becomes a combobox, or the field itself.