Merge commit '16f2c7af0122a4f3cfd709bd44d9b5402a6ec8bf' into security

Updating branch base to 1.532.
This commit is contained in:
Jesse Glick committed 2014-07-30 17:39:01 -07:00
commit 1239ed3bd9
864 files changed
+23292 -4987

No files matched your search

+4
View File
@@ -35,4 +35,8 @@ debian/configure-stamp
jenkins_*.build
jenkins_*.changes
*.deb
*.pkg
*.zip
push-build.sh
war/node_modules/
.java-version
+64
View File
@@ -0,0 +1,64 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="CompilerConfiguration">
<option name="DEFAULT_COMPILER" value="Javac" />
<excludeFromCompile>
<directory url="file://$PROJECT_DIR$/core/target/generated-sources/annotations" includeSubdirectories="true" />
<directory url="file://$PROJECT_DIR$/core/target/generated-test-sources/test-annotations" includeSubdirectories="true" />
<directory url="file://$PROJECT_DIR$/ui-samples-plugin/target/generated-sources/annotations" includeSubdirectories="true" />
<directory url="file://$PROJECT_DIR$/ui-samples-plugin/target/generated-test-sources/test-annotations" includeSubdirectories="true" />
<directory url="file://$PROJECT_DIR$/cli/target/generated-sources/annotations" includeSubdirectories="true" />
<directory url="file://$PROJECT_DIR$/cli/target/generated-test-sources/test-annotations" includeSubdirectories="true" />
<directory url="file://$PROJECT_DIR$/maven-plugin/target/generated-sources/annotations" includeSubdirectories="true" />
<directory url="file://$PROJECT_DIR$/maven-plugin/target/generated-test-sources/test-annotations" includeSubdirectories="true" />
<directory url="file://$PROJECT_DIR$/war/target/generated-sources/annotations" includeSubdirectories="true" />
<directory url="file://$PROJECT_DIR$/war/target/generated-test-sources/test-annotations" includeSubdirectories="true" />
<directory url="file://$PROJECT_DIR$/test/target/generated-sources/annotations" includeSubdirectories="true" />
<directory url="file://$PROJECT_DIR$/test/target/generated-test-sources/test-annotations" includeSubdirectories="true" />
</excludeFromCompile>
<resourceExtensions />
<wildcardResourcePatterns>
<entry name="?*.properties" />
<entry name="?*.xml" />
<entry name="?*.gif" />
<entry name="?*.png" />
<entry name="?*.jpeg" />
<entry name="?*.jpg" />
<entry name="?*.html" />
<entry name="?*.dtd" />
<entry name="?*.tld" />
<entry name="?*.ftl" />
</wildcardResourcePatterns>
<annotationProcessing>
<profile default="true" name="Default" enabled="false">
<processorPath useClasspath="true" />
</profile>
<profile default="false" name="Maven default annotation processors profile" enabled="true">
<sourceOutputDir name="target/generated-sources/annotations" />
<sourceTestOutputDir name="target/generated-test-sources/test-annotations" />
<outputRelativeToContentRoot value="true" />
<processorPath useClasspath="true" />
<module name="cli" />
<module name="jenkins-core" />
<module name="jenkins-test-harness" />
<module name="jenkins-war" />
<module name="maven-plugin" />
<module name="ui-samples-plugin" />
</profile>
</annotationProcessing>
<bytecodeTargetLevel>
<module name="cli" target="1.6" />
<module name="jenkins-core" target="1.6" />
<module name="jenkins-test-harness" target="1.6" />
<module name="jenkins-war" target="1.6" />
<module name="maven-plugin" target="1.6" />
<module name="plugin" target="1.5" />
<module name="pom" target="1.6" />
<module name="ui-samples-plugin" target="1.6" />
</bytecodeTargetLevel>
</component>
<component name="JavacSettings">
<option name="MAXIMUM_HEAP_SIZE" value="192" />
</component>
</project>
+14
View File
@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="Encoding" useUTFGuessing="true" native2AsciiForPropertiesFiles="true" defaultCharsetForPropertiesFiles="ISO-8859-1">
<file url="file://$PROJECT_DIR$" charset="UTF-8" />
<file url="file://$PROJECT_DIR$/cli" charset="UTF-8" />
<file url="file://$PROJECT_DIR$/core" charset="UTF-8" />
<file url="file://$PROJECT_DIR$/maven-plugin" charset="UTF-8" />
<file url="file://$PROJECT_DIR$/plugins" charset="UTF-8" />
<file url="file://$PROJECT_DIR$/test" charset="UTF-8" />
<file url="file://$PROJECT_DIR$/ui-samples-plugin" charset="UTF-8" />
<file url="file://$PROJECT_DIR$/war" charset="UTF-8" />
</component>
</project>
+11
View File
@@ -0,0 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="GroovyCompilerConfiguration">
<excludes>
<directory url="file://$PROJECT_DIR$/maven-plugin/src/main/resources" includeSubdirectories="true" />
<directory url="file://$PROJECT_DIR$/core/src/main/resources" includeSubdirectories="true" />
<directory url="file://$PROJECT_DIR$/ui-samples-plugin/src/main/resources" includeSubdirectories="true" />
</excludes>
</component>
</project>
+1 -1
View File
@@ -2,7 +2,7 @@
Jenkins CI
==========
Copyright &copy; 2004-, Kohsuke Kawaguchi, Sun Microsystems, Inc., and a number of other of contributers. Licensed under [MIT License].
Copyright © 2004–, Kohsuke Kawaguchi, Sun Microsystems, Inc., and a number of other of contributors. Licensed under [MIT License].
About
-----
+563 -2
View File
@@ -61,7 +61,566 @@ Upcoming changes</a>
<!-- these changes are controlled by the release process. DO NOT MODIFY -->
<div id="rc" style="display:none;"><!--=BEGIN=-->
<h3><a name=v1.509>What's new in 1.509</a> <!--=DATE=--></h3>
<h3><a name=v1.532>What's new in 1.532</a> <!--=DATE=--></h3>
<ul class=image>
<li class='major bug'>
Working around a GZip compression bug in jzlib affecting transfer of certain large, repetitive artifacts.
(<a href="https://issues.jenkins-ci.org/browse/JENKINS-19473">issue 19473</a>)
<li class='major bug'>
Lazy-loading bug: builds go missing.
(<a href="https://issues.jenkins-ci.org/browse/JENKINS-19418">issue 19418</a>)
<li class=bug>
(re)create build number->id symlink if missing when updating permalink.
(<a href="https://issues.jenkins-ci.org/browse/JENKINS-19034">issue 19034</a>)
<li class=rfe>
Display the full display name in title for jobs and views.
(<a href="https://github.com/jenkinsci/jenkins/pull/884">pull request 884</a>)
<li class='major rfe'>
Added a new extension point to control where archived artifacts get stored.
(<a href="https://issues.jenkins-ci.org/browse/JENKINS-17236">issue 17236</a>)
<li class=rfe>
Use fine-grained permissions for node manipulation via REST API &amp; CLI
(<a href="https://issues.jenkins-ci.org/browse/JENKINS-18485">issue 18485</a>)
</ul>
</div><!--=END=-->
<h3><a name=v1.531>What's new in 1.531</a> (2013/09/16)</h3>
<ul class=image>
<li class=bug>
Deleting an external run did not immediately remove it from build list, leading to errors from log rotation.
(<a href="https://issues.jenkins-ci.org/browse/JENKINS-19377">issue 19377</a>)
<li class=bug>
When copying a directory from master to slave fails due to an error on the slave, properly report it.
(<a href="https://issues.jenkins-ci.org/browse/JENKINS-9540">issue 9540</a>)
<li class=bug>
Identify user agent for Internet Explorer 11.
(<a href="https://issues.jenkins-ci.org/browse/JENKINS-19171">issue 19171</a>)
<li class=bug>
Since 1.518, fingerprint serialization broke when job or file names contained XML special characters like ampersands.
(<a href="https://issues.jenkins-ci.org/browse/JENKINS-18337">issue 18337</a>)
<li class=bug>
Robustness against truncated fingerprint files.
(<a href="https://issues.jenkins-ci.org/browse/JENKINS-19515">issue 19515</a>)
<li class=rfe>
JavaScript error in the checkUrl computation shouldn't break the job configuration page.
(<a href="https://issues.jenkins-ci.org/browse/JENKINS-19457">issue 19457</a>)
<li class=rfe>
Annotate the Advanced section if some fields are already customized.
(<a href="https://issues.jenkins-ci.org/browse/JENKINS-3107">issue 3107</a>)
<li class=bug>
No events fired when project is enable/disable or the description is changed
(<a href="https://issues.jenkins-ci.org/browse/JENKINS-17108">issue 17108</a>)
</ul>
<h3><a name=v1.530>What's new in 1.530</a> (2013/09/09)</h3>
<ul class=image>
<li class=bug>
Send Maven agent JARs to slaves on demand, not unconditionally upon connection.
(<a href="https://issues.jenkins-ci.org/browse/JENKINS-16261">issue 16261</a>)
<li class=bug>
Occasional race condition during startup.
(<a href="https://issues.jenkins-ci.org/browse/JENKINS-18775">issue 18775</a>)
<li class='major bug'>
Robustness against startup error for users of Global Build Stats plugin.
(<a href="https://issues.jenkins-ci.org/browse/JENKINS-17248">issue 17248</a>)
<li class='major bug'>
404s from Javadoc and HTML Publisher plugins.
(<a href="https://issues.jenkins-ci.org/browse/JENKINS-19168">issue 19168</a>)
<li class=bug>
Build number symlinks and permalinks not updated for Maven module builds.
(<a href="https://issues.jenkins-ci.org/browse/JENKINS-18846">issue 18846</a>)
</ul>
<h3><a name=v1.529>What's new in 1.529</a> (2013/08/26)</h3>
<ul class=image>
<li class=rfe>
With Apache Maven 3.1 build, logging configuration from the Apache Maven distribution is not used.
<li class=rfe>
Avoid log duplication with Apache Maven 3.1 builds
<li class=bug>
Ungraceful handling of empty matrix project axes.
(<a href="https://issues.jenkins-ci.org/browse/JENKINS-19135">issue 19135</a>)
<li class=bug>
Updated Groovy to 1.8.9 to avoid <a href="http://jira.codehaus.org/browse/GROOVY-4292">GROOVY-4292</a>.
<li class='major bug'>
CLI login command broken on Windows since 1.518.
(<a href="https://issues.jenkins-ci.org/browse/JENKINS-19192">issue 19192</a>)
<li class=bug>
A malformed JUnit result file should mark that test suite as a failure, but not interrupt archiving of other tests.
(<a href="https://issues.jenkins-ci.org/browse/JENKINS-19186">issue 19186</a>)
<li class=rfe>
Build for $username now shows also build scheduled by user
(<a href="https://issues.jenkins-ci.org/browse/JENKINS-16178">issue 16178</a>)
</ul>
<h3><a name=v1.528>What's new in 1.528</a> (2013/08/18)</h3>
<ul class=image>
<li class=rfe>
Command line now supports "--sessionTimeout" option for controlling session timeout
<li class=bug>
Form validation methods weren't getting triggered when one of its dependency controls change.
(<a href="https://issues.jenkins-ci.org/browse/JENKINS-19124">issue 19124</a>)
<li class=bug>
When POST is required for some HTTP operation but GET was used, the response should have status code 405.
(<a href="https://issues.jenkins-ci.org/browse/JENKINS-16918">issue 16918</a>)
<li class=bug>
Correct help text of Label field in automatic installation of tools in global configuration.
(<a href="https://issues.jenkins-ci.org/browse/JENKINS-19091">issue 19091</a>)
<li class=rfe>
Use Guice from Google rather than a fork
<li class=bug>
Jenkins does not invoke ProcessKillers for Windows
(<a href="https://issues.jenkins-ci.org/browse/JENKINS-19156">issue 19156</a>)
</ul>
<h3><a name=v1.527>What's new in 1.527</a> (2013/08/12)</h3>
<ul class=image>
<li class=bug>
Fixed <tt>NoSuchFieldError: triggers</tt> with older Maven plugin
(<a href="https://issues.jenkins-ci.org/browse/JENKINS-18677">issue 18677</a>)
<li class=rfe>
Added bytecode transformation driven compatibility ensurance mechanism
(<a href="https://groups.google.com/forum/#!topic/jenkinsci-dev/7qCClb36njo">discussion</a>)
<li class=rfe>
Improve search to locate items inside folders.
(<a href="https://github.com/jenkinsci/jenkins/pull/848">pull request 848</a>)
(<a href="https://github.com/jenkinsci/jenkins/pull/893">pull request 893</a>)
<li class=bug>
Windows path separators not correctly escaped in Maven properties configuration.
(<a href="https://issues.jenkins-ci.org/browse/JENKINS-10539">issue 10539</a>)
<li class=rfe>
Improved <tt>EnvironmentContributor</tt> to support project-level insertion.
(<a href="https://issues.jenkins-ci.org/browse/JENKINS-19042">issue 19042</a>)
</ul>
<h3><a name=v1.526>What's new in 1.526</a> (2013/08/05)</h3>
<ul class=image>
<li class=rfe>
Report an user friendly error page if a deletion of a build fails.
(<a href="https://github.com/jenkinsci/jenkins/pull/827">pull request 827</a>)
<li class=bug>
Maven build failure wasn't describing errors like Maven CLI does.
(<a href="https://issues.jenkins-ci.org/browse/JENKINS-15025">issue 15025</a>)
<li class=bug>
<code>MavenModuleSetBuild.getResult</code> is expensive.
(<a href="https://issues.jenkins-ci.org/browse/JENKINS-18895">issue 18895</a>)
<li class=bug>
Revisited fix to be compatible for plugins.
(<a href="https://issues.jenkins-ci.org/browse/JENKINS-18119">issue 18119</a>)
<li class=bug>
Ensuring <code>/log/all</code> shows only <code>INFO</code> and above messages, even if custom loggers display <code>FINE</code> or below.
(<a href="https://issues.jenkins-ci.org/browse/JENKINS-18959">issue 18959</a>)
<li class=rfe>
Added a new monitor that detects and fixse out-of-order builds records.
(<a href="https://issues.jenkins-ci.org/browse/JENKINS-18289">issue 18289</a>)
<li class=rfe>
Can't build using maven 3.1.0
(<a href="https://issues.jenkins-ci.org/browse/JENKINS-15935">issue 15935</a>)
<li class=bug>
Fixed Winstone+mod_proxy_ajp+SSL combo issue.
(<a href="https://issues.jenkins-ci.org/browse/JENKINS-5753">issue 5753</a>)
<li class=bug>
<code>JENKINS_DEBUG_LEVEL</code> misinterpreted by Winstone, causing excessive logging.
(<a href="https://issues.jenkins-ci.org/browse/JENKINS-18701">issue 18701</a>)
<li class='major bug'>
Since 1.520, Jenkins requires Java 6 or later, breaking Maven builds set to use JDK 5. Now falls back to JVM of slave agent but sets compile/test flags to use defined JDK.
(<a href="https://issues.jenkins-ci.org/browse/JENKINS-18403">issue 18403</a>)
<li class='major bug'>
Since 1.517, Maven projects using Maven 2 could not build projects using extensions depending on Apache Commons Codec.
(<a href="https://issues.jenkins-ci.org/browse/JENKINS-18178">issue 18178</a>)
<li class=bug>
Test harness was packing copies of Maven into plugin archives under some conditions.
(<a href="https://issues.jenkins-ci.org/browse/JENKINS-18918">issue 18918</a>)
<li class=bug>
Provided maven settings.xml in maven builder is lost.
(<a href="https://issues.jenkins-ci.org/browse/JENKINS-15976">issue 15976</a>)
<li class=bug>
Exception when running polling with a Maven installation not defined on master.
(<a href="https://issues.jenkins-ci.org/browse/JENKINS-18898">issue 18898</a>)
<li class=bug>
Since 1.477 GET on <code>/view/…/config.xml</code> included a spurious wrapper element.
(<a href="https://issues.jenkins-ci.org/browse/JENKINS-17302">issue 17302</a>)
<li class=rfe>
Clearer display of log messages: chronological order, and coloration of repeated vs. fresh metadata (date, log level, log source).
<li class=bug>
Fixed a regression that broke some plugins' form validation
(<a href="https://issues.jenkins-ci.org/browse/JENKINS-18776">issue 18776</a>)
<li class=bug>
People View does Not Populate if JQuery plugin enabled.
(<a href="https://issues.jenkins-ci.org/browse/JENKINS-18641">issue 18641</a>)
</ul>
<h3><a name=v1.525>What's new in 1.525</a> (2013/07/29)</h3>
<p>Same as 1.524; botched release.</p>
<h3><a name=v1.524>What's new in 1.524</a> (2013/07/23)</h3>
<ul class=image>
<li class=bug>
Clock Difference broken on Manage Nodes page
(<a href="https://issues.jenkins-ci.org/browse/JENKINS-18671">issue 18671</a>)
<li class=bug>
Fixed another possible cause of an NPE from MatrixConfiguration.newBuild.
(<a href="https://issues.jenkins-ci.org/browse/JENKINS-17728">issue 17728</a>)
<li class=bug>
NPE in MavenFingerprinter.getArtifactRepositoryMaven21.
(<a href="https://issues.jenkins-ci.org/browse/JENKINS-18441">issue 18441</a>)
<li class=rfe>
More reliability improvement in remote slave reconnection.
</ul>
<h3><a name=v1.523>What's new in 1.523</a> (2013/07/14)</h3>
<ul class=image>
<li class=bug>
Fixed: claiming of tests doesn't work in Maven jobs (claim-plugin)
(<a href="https://issues.jenkins-ci.org/browse/JENKINS-14585">issue 14585</a>)
</ul>
<h3><a name=v1.522>What's new in 1.522</a> (2013/07/06)</h3>
<ul class=image>
<li class=bug>
Fixed a regression in the config form with some plugins
(<a href="https://issues.jenkins-ci.org/browse/JENKINS-18585">issue 18585</a>)
<li class=bug>
Fixed a dead lock in the <tt>Project</tt> class and improved the signature of the persisted XML form a bit.
(<a href="https://issues.jenkins-ci.org/browse/JENKINS-18589">issue 18589</a>)
<li class=bug>
Improved memory efficiency in parsing test reports with large stdio output files.
(<a href="https://issues.jenkins-ci.org/browse/JENKINS-15382">issue 15382</a>)
<li class=rfe>
Node monitoring now happens concurrently across all the slaves, so it'll be affected less by problematic slaves.
(<a href="https://issues.jenkins-ci.org/browse/JENKINS-18438">issue 18438</a>)
<li class=bug>
Deadlock during Maven builds Parsing POM step
(<a href="https://issues.jenkins-ci.org/browse/JENKINS-15846">issue 15846</a>)
<li class=bug>
If every node is restricted to tied jobs only, Matrix build jobs can never start.
</ul>
<h3><a name=v1.521>What's new in 1.521</a> (2013/07/02)</h3>
<ul class=image>
<li class=bug>
Build with parameters returns empty web page
(<a href="https://issues.jenkins-ci.org/browse/JENKINS-18425">issue 18425</a>)
<li class=bug>
Access denied error results in ERR_CONTENT_DECODING_FAILED on most browsers, masking the root cause.
(<a href="https://issues.jenkins-ci.org/browse/JENKINS-15437">issue 15437</a>)
<li class=bug>
Fixed the master/slave handshake problem when a slave runs on non-ASCII compatible encoding (such as EBCDIC.)
<li class=rfe>
Added a diagnosis for <tt>StreamCorruptedException</tt> problem
(<a href="https://issues.jenkins-ci.org/browse/JENKINS-8856">issue 8856</a>)
<li class=rfe>
Matrix project's parent can be now tied to labels/slaves.
(<a href="https://issues.jenkins-ci.org/browse/JENKINS-7825">issue 7825</a>)
<li class=bug>
Clean up fingerprint records that correspond to the deleted build recods
(<a href="https://issues.jenkins-ci.org/browse/JENKINS-18417">issue 18417</a>)
<li class=bug>
Fixed "Comparison method violates its general contract" error in BuildTrigger.execute
(<a href="https://issues.jenkins-ci.org/browse/JENKINS-17247">issue 17247</a>)
<li class=bug>
Edited description wasn't reflected when pressing the "Apply" button.
(<a href="https://issues.jenkins-ci.org/browse/JENKINS-18436">issue 18436</a>)
<li class=bug>
Fixed a regression in remoting since 1.519 that caused FindBugs plugins to break.
(<a href="https://issues.jenkins-ci.org/browse/JENKINS-18349">issue 18349</a>,
<a href="https://issues.jenkins-ci.org/browse/JENKINS-18405">issue 18405</a>)
<li class=rfe>
Revisited the extension point added in 1.519 that adds custom plexus components.
</ul>
<h3><a name=v1.520>What's new in 1.520</a> (2013/06/25)</h3>
<ul class=image>
<li class=bug>
Slave launch thread should have the background activity credential.
(<a href="https://issues.jenkins-ci.org/browse/JENKINS-15578">issue 15578</a>)
<li class=bug>
“Build Now” link did not work for multijobs.
(<a href="https://issues.jenkins-ci.org/browse/JENKINS-16974">issue 16974</a>)
<li class=bug>
Unix vs. Windows mode not correctly retained for command launchers under some conditions.
(<a href="https://issues.jenkins-ci.org/browse/JENKINS-18368">issue 18368</a>)
<li class=bug>
Edit views with non-ASCII names did not work since 1.500.
(<a href="https://issues.jenkins-ci.org/browse/JENKINS-18373">issue 18373</a>)
<li class='major bug'>
Fixed API incompatibility since 1.489.
(<a href="https://issues.jenkins-ci.org/browse/JENKINS-18356">issue 18356</a>)
<li class=bug>
“Projects tied to slave” shows unrelated Maven module jobs.
(<a href="https://issues.jenkins-ci.org/browse/JENKINS-17451">issue 17451</a>)
<li class=bug>
Fixed file descriptor leak in fingerprint computation.
(<a href="https://issues.jenkins-ci.org/browse/JENKINS-18351">issue 18351</a>)
<li class=bug>
Test history was not shown if suite name was part of the test name.
(<a href="https://issues.jenkins-ci.org/browse/JENKINS-15380">issue 15380</a>)
<li class=rfe>
Added a new extension point to monitor the flow of stuff in the queue.
<li class=rfe>
Added a new extension point to monitor the provisioning of nodes from clouds.
(<a href="https://github.com/jenkinsci/jenkins/pull/819">pull request 819</a>)
<li class=rfe>
Possible to create a custom <code>AbstractDiskSpaceMonitor</code>.
<li class=rfe>
Executors running the builds can be now a subject of access control.
(<a href="https://issues.jenkins-ci.org/browse/JENKINS-18285">issue 18285</a>)
<li class='major rfe'>
Core started relying on Java 1.6 as per the agreement in the dev list.
If you have a serious objection against it, please let us know
before we really start relying on 1.6 features.
<li class='major bug'>
Some actions confirmed by dialog were not working when CSRF crumbs were enabled.
(<a href="https://issues.jenkins-ci.org/browse/JENKINS-17977">issue 17977</a>)
(<a href="https://issues.jenkins-ci.org/browse/JENKINS-18032">issue 18032</a>)
<li class=rfe>
CLI list-jobs command should list all nested jobs.
(<a href="https://github.com/jenkinsci/jenkins/pull/793">pull request 793</a>)
<li class=rfe>
Provide a mechanism to differentiate between node properties that are applicable
to the master node only and node properties that can be applied to all nodes
(<a href="https://issues.jenkins-ci.org/browse/JENKINS-18381">issue 18381</a>)
<li class=bug>
Maven module links in the module list page are broken.
(<a href="https://issues.jenkins-ci.org/browse/JENKINS-17713">issue 17713</a>)
<li class='major bug'>
100% CPU pegging in <tt>Deflator.deflateBytes</tt>
(<a href="https://issues.jenkins-ci.org/browse/JENKINS-14362">issue 14362</a>)
</ul>
<h3><a name=v1.519>What's new in 1.519</a> (2013/06/17)</h3>
<ul class=image>
<li class='major bug'>
Log cluttered with irrelevant warnings about build timestamps when running on Windows on Java 6.
(<a href="https://issues.jenkins-ci.org/browse/JENKINS-15587">issue 15587</a>)
<li class='major bug'>
Fingerprint action deserialization problem fixed.
(<a href="https://issues.jenkins-ci.org/browse/JENKINS-17125">issue 17125</a>)
<li class='rfe'>
Updating the master computer's configuration from the slave list UI had no immediate effect.
(<a href="https://issues.jenkins-ci.org/browse/JENKINS-17276">issue 17276</a>)
<li class='rfe'>
Improved the tracking of queued jobs and their eventual builds in the REST API.
<li class='rfe'>
Configured log recorders can now pick up messages logged from slaves.
(<a href="https://issues.jenkins-ci.org/browse/JENKINS-18274">issue 18274</a>)
<li class='rfe'>
Added a new extension point to contribute custom plexus components into Maven for the maven project type.
<li class='major rfe'>
Remoting classloader performance improvement upon reconnection to the same slave.
(<a href="https://issues.jenkins-ci.org/browse/JENKINS-15120">issue 15120</a>)
</ul>
<h3><a name=v1.518>What's new in 1.518</a> (2013/06/11)</h3>
<ul class=image>
<li class=bug>
NPE in <code>DefaultMatrixExecutionStrategyImpl.waitForCompletion</code>.
(<a href="https://issues.jenkins-ci.org/browse/JENKINS-18024">issue 18024</a>)
<li class=bug>
Optimizations in fingerprint recording.
(<a href="https://issues.jenkins-ci.org/browse/JENKINS-16301">issue 16301</a>)
<li class=bug>
Using JNR-POSIX rather than JNA-POSIX for better platform support.
(<a href="https://issues.jenkins-ci.org/browse/JENKINS-14351">issue 14351</a>)
<li class='major bug'>
Errors searching build records when builds were misordered.
(<a href="https://issues.jenkins-ci.org/browse/JENKINS-15652">issue 15652</a>)
<li class='major bug'>
Finding the last failed build for a job (e.g. from a view column) broke lazy loading.
(<a href="https://issues.jenkins-ci.org/browse/JENKINS-16023">issue 16023</a>)
<li class=bug>
Do not fail startup in case <code>ListView.includeRegex</code> was syntactically malformed.
<li class=bug>
CSS stylesheets misrendered in Chrome due to caching.
(<a href="https://issues.jenkins-ci.org/browse/JENKINS-17684">issue 17684</a>)
<li class=bug>
User icon in People broken if Jenkins root URL unconfigured.
(<a href="https://issues.jenkins-ci.org/browse/JENKINS-18118">issue 18118</a>)
<li class=bug>
Progress bar sometimes broken in People.
(<a href="https://issues.jenkins-ci.org/browse/JENKINS-18119">issue 18119</a>)
</ul>
<h3><a name=v1.517>What's new in 1.517</a> (2013/06/02)</h3>
<ul class=image>
<li class=rfe>
Enable word breaking in potentially long strings like job names.
(<a href="https://issues.jenkins-ci.org/browse/JENKINS-17030">issue 17030</a>)
<li class=rfe>
Allow filtering of the Run parameter build list by result.
(<a href="https://issues.jenkins-ci.org/browse/JENKINS-7280">issue 7280</a>)
<li class=rfe>
Add support for scalatest-maven-plugin.
(<a href="https://issues.jenkins-ci.org/browse/JENKINS-18086">issue 18086</a>)
<li class=bug>
When copying a folder, the display names of contained jobs were gratuitously cleared.
(<a href="https://issues.jenkins-ci.org/browse/JENKINS-18074">issue 18074</a>)
<li class=bug>
“Recurse in subfolders” option for list views produced exceptions when used with native Maven projects.
(<a href="https://issues.jenkins-ci.org/browse/JENKINS-18025">issue 18025</a>)
<li class=bug>
Using proper directory separator character for permalinks on Windows.
(<a href="https://issues.jenkins-ci.org/browse/JENKINS-17681">issue 17681</a>)
<li class="bug">
Use markup formater to display parameter description.
(<a href="https://issues.jenkins-ci.org/browse/JENKINS-18427">issue 18427</a>)
</li>
</ul>
<h3><a name=v1.516>What's new in 1.516</a> (2013/05/27)</h3>
<ul class=image>
<li class=bug>
NPE from <code>Run.getDynamic</code>.
(<a href="https://issues.jenkins-ci.org/browse/JENKINS-17935">issue 17935</a>)
<li class=bug>
Should be able to collect all log records at a given level using a blank logger name.
(<a href="https://issues.jenkins-ci.org/browse/JENKINS-17983">issue 17983</a>)
<li class=bug>
Reworked Upload Plugin gesture to work more like installation from an update center, and in particular to support dynamic load.
(<a href="https://issues.jenkins-ci.org/browse/JENKINS-16652">issue 16652</a>)
<li class=bug>
Errors in <code>init.groovy</code> halted startup; changed to just log a warning.
(<a href="https://issues.jenkins-ci.org/browse/JENKINS-17933">issue 17933</a>)
</ul>
<h3><a name=v1.515>What's new in 1.515</a> (2013/05/18)</h3>
<ul class=image>
<li class=rfe>
Windows services now auto-restart in case of abnormal process termination.
<li class=rfe>
&lt;f:dropdownDescriptorSelector> does not allow defaulting to specifig instance
(<a href="https://issues.jenkins-ci.org/browse/JENKINS-17858">issue 17858</a>)
<li class=rfe>
mark maven settings / global settings as default for new jobs
(<a href="https://issues.jenkins-ci.org/browse/JENKINS-17723">issue 17723</a>)
<li class=bug>
Display Name is not shown.
(<a href="https://issues.jenkins-ci.org/browse/JENKINS-17715">issue 17715</a>)
<li class='major bug'>
Symlink handling problem with build permalinks on Windows.
(<a href="https://issues.jenkins-ci.org/browse/JENKINS-17681">issue 17681</a>)
<li class=bug>
List views missing a required field were unloadable.
(<a href="https://issues.jenkins-ci.org/browse/JENKINS-15309">issue 15309</a>)
<li class=bug>
Maven module artifacts were not being deleted by the log rotator.
(<a href="https://issues.jenkins-ci.org/browse/JENKINS-17508">issue 17508</a>)
<li class='major bug'>
Properly find parent POMs when fingerprinting a Maven project.
(<a href="https://issues.jenkins-ci.org/browse/JENKINS-17775">issue 17775</a>)
<li class=rfe>
Allow the combination filter to accept parameter values.
(<a href="https://issues.jenkins-ci.org/browse/JENKINS-7285">issue 7285</a>)
<li class=rfe>
Extension point to transform test names (for use with alternative JVM languages).
(<a href="https://issues.jenkins-ci.org/browse/JENKINS-17478">issue 17478</a>)
</ul>
<h3><a name=v1.514>What's new in 1.514</a> (2013/05/01)</h3>
<ul class=image>
<li class=rfe>
Added a new <tt>set-build-parameter</tt> command that can update a build variable from within a build.
<li class=rfe>
Can use <code>-Dhudson.udp=-1</code> to disable UDP broadcast without showing an ugly exception.
<li class=bug>
Third-party license display for core was broken since 1.506.
(<a href="https://issues.jenkins-ci.org/browse/JENKINS-17724">issue 17724</a>)
<li class='major bug'>
Mitigation of exception from fingerprinting in a Maven project when a parent POM could not be located.
(<a href="https://issues.jenkins-ci.org/browse/JENKINS-17775">issue 17775</a>)
<li class=bug>
NPE from <code>MatrixConfiguration.newBuild</code>.
(<a href="https://issues.jenkins-ci.org/browse/JENKINS-17728">issue 17728</a>)
<li class='major bug'>
NPE configuring Copy Artifact with Maven jobs.
(<a href="https://issues.jenkins-ci.org/browse/JENKINS-17402">issue 17402</a>)
<li class=rfe>
<code>/about</code> now links to license information for plugins as well.
<li class=rfe>
Updated bundled plugins.
</ul>
<h3><a name=v1.513>What's new in 1.513</a> (2013/04/28)</h3>
<ul class=image>
<li class=rfe>
Slave status monitor page shows when the data is last obtained
<li class=rfe>
Delete button to highlight what it is going to delete.
<li class=bug>
<code>StringIndexOutOfBoundsException</code> in <code>PackageResult.findCorrespondingResult</code>.
(<a href="https://issues.jenkins-ci.org/browse/JENKINS-17721">issue 17721</a>)
<li class=rfe>
Breadcrumb is reworked to show descendants to provide additional navigational shortcuts.
(<a href="https://wiki.jenkins-ci.org/display/JENKINS/FOSDEM+UI+Enhancement+discussion+notes+2013">discussion</a>)
<li class=bug>
<code>hpi:run</code> did not work for bundled plugins.
(<a href="https://issues.jenkins-ci.org/browse/JENKINS-18352">issue 18352</a>)
<li class=bug>
Fixed CSRF vulnerabilities
(SECURITY-63,SECURITY-69)
<li class=bug>
Fixed an XSS vulnerability via stylesheet
(SECURITY-67)
<li class=bug>
Fixed an XSS vulnerability to copy arbitrary text into clipboard
(SECURITY-71/CVE-2013-1808)
</ul>
<h3><a name=v1.512>What's new in 1.512</a> (2013/04/21)</h3>
<ul class=image>
<li class=rfe>
Views can now include jobs located within folders.
(<a href="https://github.com/jenkinsci/jenkins/pull/757">pull 757</a>)
<li class=rfe>
Added confirmation dialog before reloading configuration from disk.
(<a href="https://issues.jenkins-ci.org/browse/JENKINS-15340">issue 15340</a>)
<li class=rfe>
Switched confirmation before deleting jobs or wiping out workspace to a dialog.
<li class=rfe>
Different text than “Build Now” for parameterized jobs.
(<a href="https://issues.jenkins-ci.org/browse/JENKINS-10738">issue 10738</a>)
<li class=rfe>
Check the view name with ajax.
<li class=bug>
“Build Now” context menu item broken for parameterized jobs.
(<a href="https://issues.jenkins-ci.org/browse/JENKINS-17110">issue 17110</a>)
<li class=bug>
Incorrect redirection after delete of job in folder in view.
(<a href="https://issues.jenkins-ci.org/browse/JENKINS-17575">issue 17575</a>)
<li class=bug>
”My Views" links leads to 404 Not Found.
(<a href="https://issues.jenkins-ci.org/browse/JENKINS-17317">issue 17317</a>)
<li class=bug>
Quoting Issue with JDK Installer with Windows Installer.
(<a href="https://issues.jenkins-ci.org/browse/JENKINS-5408">issue 5408</a>)
<li class=bug>
Restored compatibility in <code>ArtifactArchiver</code> signature; broken in 1.509 and could affect plugins.
(<a href="https://issues.jenkins-ci.org/browse/JENKINS-17637">issue 17637</a>)
<li class=bug>
Fixed a bug in the logic that hides context menu anchor 'v'
(<a href="https://issues.jenkins-ci.org/browse/JENKINS-13995">issue 13995</a>)
</ul>
<h3><a name=v1.511>What's new in 1.511</a> (2013/04/14)</h3>
<ul class=image>
<li class=bug>
JUnit result archiver should only fail builds if there are really no results - i.e. also no skipped tests.
(<a href="https://issues.jenkins-ci.org/browse/JENKINS-7970">issue 7970</a>)
<li class='major bug'>
<code>NullPointerException</code> related to lazy loading when loading some builds using fingerprinting.
(<a href="https://issues.jenkins-ci.org/browse/JENKINS-16845">issue 16845</a>)
<li class=rfe>
Better display of parameters in queue items.
(<a href="https://issues.jenkins-ci.org/browse/JENKINS-17454">issue 17454</a>)
<li class='bug'>
sort order of plugin list is not working by default.
(<a href="https://issues.jenkins-ci.org/browse/JENKINS-17039">issue 17039</a>)
</ul>
<h3><a name=v1.510>What's new in 1.510</a> (2013/04/06)</h3>
<ul class=image>
<li class=bug>
<tt>UnsatisfiedLinkError</tt> on <tt>CreateSymbolicLinkw</tt> on Windows XP.
(<a href="https://issues.jenkins-ci.org/browse/JENKINS-17343">issue 17343</a>)
<li class=bug>
Flyweight tasks should execute on the master if there's no static
executors available.
(<a href="https://issues.jenkins-ci.org/browse/JENKINS-7291">issue 7291</a>)
<li class='major bug'>
Download tool installations directly from the slave when possible, since this is much faster than going through the master.
(<a href="https://issues.jenkins-ci.org/browse/JENKINS-17330">issue 17330</a>)
<li class=bug>
Improved UI for implicitly locked builds.
(<a href="https://issues.jenkins-ci.org/browse/JENKINS-10197">issue 10197</a>)
<li class=bug>
Incorrect URL computation broke context menu for computers with spaces in their names.
(<a href="https://issues.jenkins-ci.org/browse/JENKINS-18236">issue 18236</a>)
<li class=rfe>
Promote the use of 'H' in cron.
(<a href="https://issues.jenkins-ci.org/browse/JENKINS-17311">issue 17311</a>)
<li class='major rfe'>
Context menu no longer automatically pops up
(<a href="https://issues.jenkins-ci.org/browse/JENKINS-13995">issue 13995</a>)
</ul>
<h3><a name=v1.509>What's new in 1.509</a> (2013/04/02)</h3>
<ul class=image>
<li class='major bug'>
Heavy thread congestion saving fingerprints.
@@ -91,7 +650,6 @@ Upcoming changes</a>
View name should not allow "..".
(<a href="https://issues.jenkins-ci.org/browse/JENKINS-16608">issue 16608</a>)
</ul>
</div><!--=END=-->
<h3><a name=v1.508>What's new in 1.508</a> (2013/03/25)</h3>
<ul class=image>
<li class='major bug'>
@@ -143,6 +701,9 @@ Upcoming changes</a>
<li class=bug>
JNA-related error from Windows slave monitoring thrown repeatedly.
(<a href="https://issues.jenkins-ci.org/browse/JENKINS-15796">issue 15796</a>)
<li class=bug>
New JSON library corrects problems such as form values starting with <code>[</code>.
(<a href="https://issues.jenkins-ci.org/browse/JENKINS-14827">issue 14827</a>)
<li class=rfe>
Improved the request handling performance (where the file lookup is expensive, such as on NFS).
(<a href="https://issues.jenkins-ci.org/browse/JENKINS-16606">issue 16606</a>)
+1 -1
View File
@@ -5,7 +5,7 @@
<parent>
<artifactId>pom</artifactId>
<groupId>org.jenkins-ci.main</groupId>
<version>1.510-SNAPSHOT</version>
<version>1.533-SNAPSHOT</version>
</parent>
<artifactId>cli</artifactId>
+20 -10
View File
@@ -157,7 +157,7 @@ public class CLI {
}
private Channel connectViaHttp(String url) throws IOException {
LOGGER.fine("Trying to connect to "+url+" via HTTP");
LOGGER.log(FINE, "Trying to connect to {0} via HTTP", url);
url+="cli";
URL jenkins = new URL(url);
@@ -176,7 +176,7 @@ public class CLI {
}
private Channel connectViaCliPort(URL jenkins, CliPort clip) throws IOException {
LOGGER.fine("Trying to connect directly via TCP/IP to "+clip.endpoint);
LOGGER.log(FINE, "Trying to connect directly via TCP/IP to {0}", clip.endpoint);
final Socket s;
OutputStream out;
@@ -188,12 +188,12 @@ public class CLI {
// read the response from the proxy
ByteArrayOutputStream rsp = new ByteArrayOutputStream();
while (!rsp.toString().endsWith("\r\n\r\n")) {
while (!rsp.toString("ISO-8859-1").endsWith("\r\n\r\n")) {
int ch = s.getInputStream().read();
if (ch<0) throw new IOException("Failed to read the HTTP proxy response: "+rsp);
rsp.write(ch);
}
String head = new BufferedReader(new StringReader(rsp.toString())).readLine();
String head = new BufferedReader(new StringReader(rsp.toString("ISO-8859-1"))).readLine();
if (!head.startsWith("HTTP/1.0 200 "))
throw new IOException("Failed to establish a connection through HTTP proxy: "+rsp);
@@ -300,14 +300,14 @@ public class CLI {
byte[] buf = new byte[1024];
try {
InputStream is = conn.getInputStream();
while (is.read(buf) > 0) {
while (is.read(buf) >= 0) {
// Ignore
}
is.close();
} catch (IOException e) {
try {
InputStream es = ((HttpURLConnection)conn).getErrorStream();
while (es.read(buf) > 0) {
while (es.read(buf) >= 0) {
// Ignore
}
es.close();
@@ -488,7 +488,7 @@ public class CLI {
LOGGER.log(FINE,e.getMessage(),e);
return -1;
}
System.err.println("Failed to authenticate with your SSH keys.");
System.err.println("[WARN] Failed to authenticate with your SSH keys. Proceeding as anonymous");
LOGGER.log(FINE,"Failed to authenticate with your SSH keys.",e);
}
}
@@ -506,8 +506,13 @@ public class CLI {
Properties props = new Properties();
try {
InputStream is = CLI.class.getResourceAsStream("/jenkins/cli/jenkins-cli-version.properties");
if(is!=null)
props.load(is);
if(is!=null) {
try {
props.load(is);
} finally {
is.close();
}
}
} catch (IOException e) {
e.printStackTrace(); // if the version properties is missing, that's OK.
}
@@ -526,11 +531,16 @@ public class CLI {
}
private static String readPemFile(File f) throws IOException{
DataInputStream dis = new DataInputStream(new FileInputStream(f));
FileInputStream is = new FileInputStream(f);
try {
DataInputStream dis = new DataInputStream(is);
byte[] bytes = new byte[(int) f.length()];
dis.readFully(bytes);
dis.close();
return new String(bytes);
} finally {
is.close();
}
}
/**
+43 -62
View File
@@ -29,7 +29,7 @@ THE SOFTWARE.
<parent>
<groupId>org.jenkins-ci.main</groupId>
<artifactId>pom</artifactId>
<version>1.510-SNAPSHOT</version>
<version>1.533-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
@@ -42,8 +42,9 @@ THE SOFTWARE.
<properties>
<staplerFork>true</staplerFork>
<stapler.version>1.207</stapler.version>
<stapler.version>1.218</stapler.version>
<spring.version>2.5.6.SEC03</spring.version>
<groovy.version>1.8.9</groovy.version>
</properties>
<dependencies>
@@ -87,36 +88,29 @@ THE SOFTWARE.
<optional>true</optional>
</dependency>
<!--
<dependency>
<groupId>com.google.inject</groupId>
<artifactId>guice</artifactId>
<version>3.0</version>
</dependency>
-->
<dependency>
<!-- we'd like to use stock Guice, but that breaks Maven embedder
because it uses a modified Guice called Sisu -->
<groupId>org.sonatype.sisu</groupId>
<artifactId>sisu-guice</artifactId>
<exclusions>
<!--
sisu-guice wants a patched version of guava, which is old.
but from what I can tell from changes, it's GWT related
and we don't care. So let's stick to the proper guava
-->
<exclusion>
<groupId>org.sonatype.sisu</groupId>
<artifactId>sisu-guava</artifactId>
</exclusion>
</exclusions>
<classifier>no_aop</classifier>
</dependency>
<dependency>
<dependency> <!-- for compatibility only; all new code should use JNR -->
<groupId>org.jruby.ext.posix</groupId>
<artifactId>jna-posix</artifactId>
<version>1.0.3</version>
</dependency>
<dependency>
<groupId>com.github.jnr</groupId>
<artifactId>jnr-posix</artifactId>
<version>3.0.0</version>
</dependency>
<!-- TODO JENKINS-19192 try when released to Central:
<dependency>
<groupId>com.github.jnr</groupId>
<artifactId>jnr-ffi</artifactId>
<version>1.0.6</version>
</dependency>
-->
<dependency>
<groupId>org.kohsuke</groupId>
<artifactId>trilead-putty-extension</artifactId>
@@ -125,7 +119,7 @@ THE SOFTWARE.
<dependency>
<groupId>org.jenkins-ci</groupId>
<artifactId>trilead-ssh2</artifactId>
<version>build214-jenkins-2</version>
<version>build214-jenkins-3</version>
</dependency>
<dependency>
<groupId>org.kohsuke.stapler</groupId>
@@ -164,7 +158,7 @@ THE SOFTWARE.
<dependency>
<groupId>org.kohsuke.stapler</groupId>
<artifactId>stapler-adjunct-zeroclipboard</artifactId>
<version>1.0.7-2</version>
<version>1.1.7-1</version>
</dependency>
<dependency>
<groupId>org.kohsuke.stapler</groupId>
@@ -187,19 +181,13 @@ THE SOFTWARE.
<dependency>
<groupId>com.infradna.tool</groupId>
<artifactId>bridge-method-annotation</artifactId>
<version>1.4</version>
<exclusions> <!-- https://github.com/infradna/bridge-method-injector/issues/1 -->
<exclusion>
<artifactId>annotation-indexer</artifactId>
<groupId>org.jvnet.hudson</groupId>
</exclusion>
</exclusions>
<version>1.8</version>
</dependency>
<dependency><!-- until we get this version through Stapler -->
<groupId>org.kohsuke.stapler</groupId>
<artifactId>json-lib</artifactId>
<version>2.4-jenkins-1</version>
<version>2.4-jenkins-2</version>
</dependency>
<dependency>
<groupId>commons-httpclient</groupId>
@@ -208,13 +196,18 @@ THE SOFTWARE.
<dependency>
<groupId>args4j</groupId>
<artifactId>args4j</artifactId>
<version>2.0.21</version>
<version>2.0.23</version>
</dependency>
<dependency>
<groupId>org.jenkins-ci</groupId>
<artifactId>annotation-indexer</artifactId>
<version>1.4</version>
</dependency>
<dependency>
<groupId>org.jenkins-ci</groupId>
<artifactId>bytecode-compatibility-transformer</artifactId>
<version>1.3</version>
</dependency>
<dependency>
<groupId>org.jvnet.hudson</groupId>
<artifactId>task-reactor</artifactId>
@@ -233,7 +226,7 @@ THE SOFTWARE.
<dependency>
<groupId>org.jvnet.hudson</groupId>
<artifactId>xstream</artifactId>
<version>1.4.4-jenkins-3</version>
<version>1.4.4-jenkins-4</version>
</dependency>
<dependency>
<groupId>jfree</groupId>
@@ -404,7 +397,7 @@ THE SOFTWARE.
<dependency>
<groupId>org.codehaus.groovy</groupId>
<artifactId>groovy-all</artifactId>
<version>1.8.5</version>
<version>${groovy.version}</version>
</dependency>
<dependency><!-- groovy shell uses this but it doesn't declare this dependency -->
<groupId>jline</groupId>
@@ -469,7 +462,6 @@ THE SOFTWARE.
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.1.1</version>
</dependency>
<dependency>
<groupId>com.sun.xml.txw2</groupId>
@@ -501,14 +493,6 @@ THE SOFTWARE.
<artifactId>jmdns</artifactId>
<version>3.4.0-jenkins-3</version>
</dependency>
<dependency>
<groupId>com.sun.winsw</groupId>
<artifactId>winsw</artifactId>
<version>1.10</version>
<classifier>bin</classifier>
<type>exe</type>
<scope>provided</scope><!-- this isn't really a dependency that Maven should care about, so putting 'provided' -->
</dependency>
<dependency>
<groupId>net.java.dev.jna</groupId>
<artifactId>jna</artifactId>
@@ -565,8 +549,7 @@ THE SOFTWARE.
<dependency><!-- Jenkins doesn't depend on it but some plugin wants the latest version. Bundling 1.6 until we figure out a mechanism to let plugins load its own -->
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
<!-- NOTE: 1.7 requires Java 6, so don't use it! -->
<version>1.6</version>
<version>1.8</version>
</dependency>
<dependency>
@@ -658,7 +641,7 @@ THE SOFTWARE.
<plugin>
<groupId>com.infradna.tool</groupId>
<artifactId>bridge-method-injector</artifactId>
<!-- version specified in grandparent pom -->
<version>1.8</version>
<executions>
<execution>
<goals>
@@ -746,7 +729,7 @@ THE SOFTWARE.
<artifactItem>
<groupId>com.sun.winsw</groupId>
<artifactId>winsw</artifactId>
<version>1.10</version>
<version>1.13</version>
<classifier>bin</classifier>
<type>exe</type>
<outputDirectory>${project.build.outputDirectory}/windows-service</outputDirectory>
@@ -757,6 +740,14 @@ THE SOFTWARE.
</execution>
</executions>
</plugin>
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<forkCount>0.5C</forkCount>
<reuseForks>true</reuseForks>
<argLine>-XX:MaxPermSize=128m</argLine>
</configuration>
</plugin>
<plugin><!-- set main class -->
<artifactId>maven-jar-plugin</artifactId>
<!-- version specified in grandparent pom -->
@@ -771,7 +762,7 @@ THE SOFTWARE.
</configuration>
</plugin>
<plugin><!-- run unit test in src/test/java -->
<groupId>org.kohsuke.gmaven</groupId>
<groupId>org.codehaus.gmaven</groupId>
<artifactId>gmaven-plugin</artifactId>
<!-- version specified in grandparent pom -->
<executions>
@@ -779,23 +770,13 @@ THE SOFTWARE.
<goals>
<goal>testCompile</goal>
</goals>
<configuration>
<sources>
<fileset>
<directory>${project.basedir}/src/test/java</directory>
<includes>
<include>**/*.groovy</include>
</includes>
</fileset>
</sources>
</configuration>
</execution>
</executions>
<dependencies>
<dependency>
<groupId>org.codehaus.groovy</groupId>
<artifactId>groovy-all</artifactId>
<version>1.8.5</version>
<version>${groovy.version}</version>
</dependency>
</dependencies>
</plugin>
@@ -898,7 +879,7 @@ THE SOFTWARE.
<build>
<plugins>
<plugin>
<groupId>org.kohsuke.gmaven</groupId>
<groupId>org.codehaus.gmaven</groupId>
<artifactId>gmaven-plugin</artifactId>
<!-- version specified in grandparent pom -->
<executions>
+5 -5
View File
@@ -93,7 +93,7 @@ term [int field]
returns [long bits=0]
throws ANTLRException
{
int d=1,s,e,t;
int d=NO_STEP,s,e,t;
}
: (token "-")=> s=token "-" e=token ( "/" d=token )?
{
@@ -108,13 +108,13 @@ throws ANTLRException
{
bits = doRange(d,field);
}
| ("H" "(")=> "H" "(" s=token "-" e=token ")"
| ("H" "(")=> "H" "(" s=token "-" e=token ")" ( "/" d=token )?
{
bits = doHash(s,e);
bits = doHash(s,e,d);
}
| "H"
| "H" ( "/" d=token )?
{
bits = doHash(field);
bits = doHash(d,field);
}
;
@@ -1,6 +1,9 @@
package hudson;
import hudson.model.ManagementLink;
import java.net.URL;
import org.kohsuke.accmod.Restricted;
import org.kohsuke.accmod.restrictions.NoExternalUse;
/**
* Show "About Jenkins" link.
@@ -27,4 +30,10 @@ public class AboutJenkins extends ManagementLink {
public String getDescription() {
return Messages.AboutJenkins_Description();
}
@Restricted(NoExternalUse.class)
public URL getLicensesURL() {
return AboutJenkins.class.getResource("/META-INF/licenses.xml");
}
}
@@ -23,7 +23,6 @@
*/
package hudson;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import hudson.Plugin.DummyImpl;
import hudson.PluginWrapper.Dependency;
@@ -36,11 +35,21 @@ import hudson.util.MaskingClassLoader;
import hudson.util.VersionNumber;
import jenkins.ClassLoaderReflectionToolkit;
import jenkins.ExtensionFilter;
import org.apache.commons.io.output.NullOutputStream;
import org.apache.tools.ant.AntClassLoader;
import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.Project;
import org.apache.tools.ant.taskdefs.Expand;
import org.apache.tools.ant.taskdefs.Zip;
import org.apache.tools.ant.types.FileSet;
import org.apache.tools.ant.types.PatternSet;
import org.apache.tools.ant.types.Resource;
import org.apache.tools.ant.types.ZipFileSet;
import org.apache.tools.ant.types.resources.MappedResourceCollection;
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 java.io.Closeable;
import java.io.File;
@@ -50,14 +59,12 @@ import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Vector;
import java.util.jar.Attributes;
@@ -215,19 +222,10 @@ public class ClassicPluginStrategy implements PluginStrategy {
return classLoader;
}
}
if(useAntClassLoader && !Closeable.class.isAssignableFrom(URLClassLoader.class)) {
// using AntClassLoader with Closeable so that we can predictably release jar files opened by URLClassLoader
AntClassLoader2 classLoader = new AntClassLoader2(parent);
classLoader.addPathFiles(paths);
return classLoader;
} else {
// Tom reported that AntClassLoader has a performance issue when Hudson keeps trying to load a class that doesn't exist,
// so providing a legacy URLClassLoader support, too
List<URL> urls = new ArrayList<URL>();
for (File path : paths)
urls.add(path.toURI().toURL());
return new URLClassLoader(urls.toArray(new URL[urls.size()]),parent);
}
AntClassLoader2 classLoader = new AntClassLoader2(parent);
classLoader.addPathFiles(paths);
return classLoader;
}
/**
@@ -413,11 +411,10 @@ public class ClassicPluginStrategy implements PluginStrategy {
* Explodes the plugin into a directory, if necessary.
*/
private static void explode(File archive, File destDir) throws IOException {
if(!destDir.exists())
destDir.mkdirs();
destDir.mkdirs();
// timestamp check
File explodeTime = new File(destDir,".timestamp");
File explodeTime = new File(destDir,".timestamp2");
if(explodeTime.exists() && explodeTime.lastModified()==archive.lastModified())
return; // no need to expand
@@ -425,12 +422,9 @@ public class ClassicPluginStrategy implements PluginStrategy {
Util.deleteRecursive(destDir);
try {
Expand e = new Expand();
e.setProject(new Project());
e.setTaskType("unzip");
e.setSrc(archive);
e.setDest(destDir);
e.execute();
Project prj = new Project();
unzipExceptClasses(archive, destDir, prj);
createClassJarFromWebInfClasses(archive, destDir, prj);
} catch (BuildException x) {
throw new IOException2("Failed to expand " + archive,x);
}
@@ -442,6 +436,66 @@ public class ClassicPluginStrategy implements PluginStrategy {
}
}
/**
* Repackage classes directory into a jar file to make it remoting friendly.
* The remoting layer can cache jar files but not class files.
*/
private static void createClassJarFromWebInfClasses(File archive, File destDir, Project prj) {
File classesJar = new File(destDir, "WEB-INF/lib/classes.jar");
ZipFileSet zfs = new ZipFileSet();
zfs.setProject(prj);
zfs.setSrc(archive);
zfs.setIncludes("WEB-INF/classes/");
MappedResourceCollection mapper = new MappedResourceCollection();
mapper.add(zfs);
GlobPatternMapper gm = new GlobPatternMapper();
gm.setFrom("WEB-INF/classes/*");
gm.setTo("*");
mapper.add(gm);
final long dirTime = archive.lastModified();
Zip z = new Zip() {
/**
* Forces the fixed timestamp for directories to make sure
* classes.jar always get a consistent checksum.
*/
protected void zipDir(Resource dir, ZipOutputStream zOut, String vPath,
int mode, ZipExtraField[] extra)
throws IOException {
ZipOutputStream wrapped = new ZipOutputStream(new NullOutputStream()) {
@Override
public void putNextEntry(ZipEntry ze) throws IOException {
ze.setTime(dirTime+1999); // roundup
super.putNextEntry(ze);
}
};
super.zipDir(dir,wrapped,vPath,mode,extra);
}
};
z.setProject(prj);
z.setTaskType("zip");
classesJar.getParentFile().mkdirs();
z.setDestFile(classesJar);
z.add(mapper);
z.execute();
}
private static void unzipExceptClasses(File archive, File destDir, Project prj) {
Expand e = new Expand();
e.setProject(prj);
e.setTaskType("unzip");
e.setSrc(archive);
e.setDest(destDir);
PatternSet p = new PatternSet();
p.setExcludes("WEB-INF/classes/");
e.addPatternset(p);
e.execute();
}
/**
* Used to load classes from dependency plugins.
*/
@@ -472,7 +526,7 @@ public class ClassicPluginStrategy implements PluginStrategy {
List<PluginWrapper> dep = new ArrayList<PluginWrapper>();
for (Dependency d : pw.getDependencies()) {
PluginWrapper p = pluginManager.getPlugin(d.shortName);
if (p!=null)
if (p!=null && p.isActive())
dep.add(p);
}
return dep;
@@ -482,7 +536,7 @@ public class ClassicPluginStrategy implements PluginStrategy {
try {
for (Dependency d : dependencies) {
PluginWrapper p = pluginManager.getPlugin(d.shortName);
if (p!=null)
if (p!=null && p.isActive())
cgd.run(Collections.singleton(p));
}
} catch (CycleDetectedException e) {
@@ -588,7 +642,7 @@ public class ClassicPluginStrategy implements PluginStrategy {
* {@link AntClassLoader} with a few methods exposed and {@link Closeable} support.
* Deprecated as of Java 7, retained only for Java 5/6.
*/
private static final class AntClassLoader2 extends AntClassLoader implements Closeable {
private final class AntClassLoader2 extends AntClassLoader implements Closeable {
private final Vector pathComponents;
private AntClassLoader2(ClassLoader parent) {
@@ -605,6 +659,7 @@ public class ClassicPluginStrategy implements PluginStrategy {
}
}
public void addPathFiles(Collection<File> paths) throws IOException {
for (File f : paths)
addPathFile(f);
@@ -635,6 +690,11 @@ public class ClassicPluginStrategy implements PluginStrategy {
return url;
}
@Override
protected Class defineClassFromData(File container, byte[] classData, String classname) throws IOException {
return super.defineClassFromData(container, pluginManager.getCompatibilityTransformer().transform(classname,classData), classname);
}
}
public static boolean useAntClassLoader = Boolean.getBoolean(ClassicPluginStrategy.class.getName()+".useAntClassLoader");
+24 -7
View File
@@ -413,7 +413,14 @@ public abstract class ExtensionFinder implements ExtensionPoint {
* This is necessary as a failure to load one plugin shouldn't fail the startup of the entire Jenkins.
* Instead, we should just drop the failing plugins.
*/
public static final Scope FAULT_TOLERANT_SCOPE = new Scope() {
public static final Scope FAULT_TOLERANT_SCOPE = new FaultTolerantScope(true);
private static final Scope QUIET_FAULT_TOLERANT_SCOPE = new FaultTolerantScope(false);
private static final class FaultTolerantScope implements Scope {
private final boolean verbose;
FaultTolerantScope(boolean verbose) {
this.verbose = verbose;
}
public <T> Provider<T> scope(final Key<T> key, final Provider<T> unscoped) {
final Provider<T> base = Scopes.SINGLETON.scope(key,unscoped);
return new Provider<T>() {
@@ -421,13 +428,21 @@ public abstract class ExtensionFinder implements ExtensionPoint {
try {
return base.get();
} catch (Exception e) {
LOGGER.log(Level.WARNING,"Failed to instantiate. Skipping this component",e);
error(key, e);
return null;
} catch (LinkageError e) {
LOGGER.log(Level.WARNING,"Failed to instantiate. Skipping this component",e);
error(key, e);
return null;
}
}
void error(Key<T> key, Throwable x) {
if (verbose) {
LOGGER.log(Level.WARNING, "Failed to instantiate " + key + "; skipping this component", x);
} else {
LOGGER.log(Level.WARNING, "Failed to instantiate optional component {0}; skipping", key.getTypeLiteral());
LOGGER.log(Level.FINE, key.toString(), x);
}
}
};
}
};
@@ -484,16 +499,18 @@ public abstract class ExtensionFinder implements ExtensionPoint {
for (final IndexItem<?,Object> item : index) {
id++;
boolean optional = isOptional(item.annotation());
try {
AnnotatedElement e = item.element();
Annotation a = item.annotation();
if (!isActive(a,e)) continue;
Scope scope = optional ? QUIET_FAULT_TOLERANT_SCOPE : FAULT_TOLERANT_SCOPE;
if (e instanceof Class) {
Key key = Key.get((Class)e);
resolve((Class)e);
annotations.put(key,a);
bind(key).in(FAULT_TOLERANT_SCOPE);
bind(key).in(scope);
} else {
Class extType;
if (e instanceof Field) {
@@ -513,15 +530,15 @@ public abstract class ExtensionFinder implements ExtensionPoint {
public Object get() {
return instantiate(item);
}
}).in(FAULT_TOLERANT_SCOPE);
}).in(scope);
}
} catch (LinkageError e) {
// sometimes the instantiation fails in an indirect classloading failure,
// which results in a LinkageError
LOGGER.log(isOptional(item.annotation()) ? Level.FINE : Level.WARNING,
LOGGER.log(optional ? Level.FINE : Level.WARNING,
"Failed to load "+item.className(), e);
} catch (InstantiationException e) {
LOGGER.log(isOptional(item.annotation()) ? Level.FINE : Level.WARNING,
LOGGER.log(optional ? Level.FINE : Level.WARNING,
"Failed to load "+item.className(), e);
}
}
@@ -142,7 +142,7 @@ public class ExtensionListView {
}
@Override
public T[] toArray(T[] array) {
public <T> T[] toArray(T[] array) {
return storage().toArray(array);
}
+200 -61
View File
@@ -53,10 +53,9 @@ import static hudson.FilePath.TarCompression.GZIP;
import hudson.org.apache.tools.tar.TarInputStream;
import hudson.util.io.Archiver;
import hudson.util.io.ArchiverFactory;
import org.apache.tools.ant.BuildException;
import jenkins.util.VirtualFile;
import org.apache.tools.ant.DirectoryScanner;
import org.apache.tools.ant.Project;
import org.apache.tools.ant.taskdefs.Copy;
import org.apache.tools.ant.types.FileSet;
import org.apache.tools.tar.TarEntry;
import org.apache.commons.io.input.CountingInputStream;
@@ -70,6 +69,7 @@ import java.io.FileOutputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.InterruptedIOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.OutputStream;
@@ -92,12 +92,15 @@ import java.util.regex.Pattern;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.zip.GZIPOutputStream;
import java.util.zip.GZIPInputStream;
import com.jcraft.jzlib.GZIPInputStream;
import com.jcraft.jzlib.GZIPOutputStream;
import com.sun.jna.Native;
import hudson.os.PosixException;
import hudson.util.FileVisitor;
import java.util.Enumeration;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.logging.Logger;
import org.apache.tools.ant.taskdefs.Chmod;
@@ -138,18 +141,21 @@ import org.apache.tools.zip.ZipEntry;
* The following code shows the example:
*
* <pre>
* FilePath file = ...;
*
* // make 'file' a fresh empty directory.
* file.act(new FileCallable&lt;Void>() {
* // if 'file' is on a different node, this FileCallable will
* // be transfered to that node and executed there.
* public Void invoke(File f,VirtualChannel channel) {
* // f and file represents the same thing
* f.deleteContents();
* f.mkdirs();
* }
* });
* void someMethod(FilePath file) {
* // make 'file' a fresh empty directory.
* file.act(new Freshen());
* }
* // if 'file' is on a different node, this FileCallable will
* // be transferred to that node and executed there.
* private static final class Freshen implements FileCallable&lt;Void> {
* private static final long serialVersionUID = 1;
* &#64;Override public Void invoke(File f, VirtualChannel channel) {
* // f and file represent the same thing
* f.deleteContents();
* f.mkdirs();
* return null;
* }
* }
* </pre>
*
* <p>
@@ -164,6 +170,7 @@ import org.apache.tools.zip.ZipEntry;
* {@link FilePath} that represents the local computer causes it to become "remote."
*
* @author Kohsuke Kawaguchi
* @see VirtualFile
*/
public final class FilePath implements Serializable {
/**
@@ -192,7 +199,7 @@ public final class FilePath implements Serializable {
* that's connected to that machine. If null, that means the local file path.
*/
public FilePath(VirtualChannel channel, String remote) {
this.channel = channel;
this.channel = channel == Jenkins.MasterComputer.localChannel ? null : channel;
this.remote = normalize(remote);
}
@@ -493,9 +500,10 @@ public final class FilePath implements Serializable {
});
}
private void unzip(File dir, InputStream in) throws IOException {
private static void unzip(File dir, InputStream in) throws IOException {
File tmpFile = File.createTempFile("tmpzip", null); // uses java.io.tmpdir
try {
// TODO why does this not simply use ZipInputStream?
IOUtils.copy(in, tmpFile);
unzip(dir,tmpFile);
}
@@ -504,7 +512,7 @@ public final class FilePath implements Serializable {
}
}
private void unzip(File dir, File zipFile) throws IOException {
static private void unzip(File dir, File zipFile) throws IOException {
dir = dir.getAbsoluteFile(); // without absolutization, getParentFile below seems to fail
ZipFile zip = new ZipFile(zipFile);
@SuppressWarnings("unchecked")
@@ -618,7 +626,7 @@ public final class FilePath implements Serializable {
public InputStream extract(InputStream _in) throws IOException {
HeadBufferingStream in = new HeadBufferingStream(_in,SIDE_BUFFER_SIZE);
try {
return new GZIPInputStream(in,8192);
return new GZIPInputStream(in, 8192, true);
} catch (IOException e) {
// various people reported "java.io.IOException: Not in GZIP format" here, so diagnose this problem better
in.fillSide();
@@ -626,7 +634,12 @@ public final class FilePath implements Serializable {
}
}
public OutputStream compress(OutputStream out) throws IOException {
return new GZIPOutputStream(new BufferedOutputStream(out));
return new GZIPOutputStream(new BufferedOutputStream(out),
// TODO JENKINS-19473 workaround; replace when jzlib fixed
new com.jcraft.jzlib.Deflater(6, 15+16, 9), // use 9 for memLevel
512,
true
);
}
};
@@ -724,6 +737,19 @@ public final class FilePath implements Serializable {
if(listener!=null)
listener.getLogger().println(message);
if (isRemote()) {
// First try to download from the slave machine.
try {
act(new Unpack(archive));
timestamp.touch(sourceTimestamp);
return true;
} catch (IOException x) {
if (listener != null) {
x.printStackTrace(listener.error("Failed to download " + archive + " from slave; will retry from master"));
}
}
}
// for HTTP downloads, enable automatic retry for added resilience
InputStream in = archive.getProtocol().startsWith("http") ? ProxyConfiguration.getInputStream(archive) : con.getInputStream();
CountingInputStream cis = new CountingInputStream(in);
@@ -743,6 +769,31 @@ public final class FilePath implements Serializable {
}
}
private static final class Unpack implements FileCallable<Void> {
private final URL archive;
Unpack(URL archive) {
this.archive = archive;
}
@Override public Void invoke(File dir, VirtualChannel channel) throws IOException, InterruptedException {
InputStream in = archive.openStream();
try {
CountingInputStream cis = new CountingInputStream(in);
try {
if (archive.toExternalForm().endsWith(".zip")) {
unzip(dir, cis);
} else {
readFromTar("input stream", dir, GZIP.extract(cis));
}
} catch (IOException x) {
throw new IOException2(String.format("Failed to unpack %s (%d bytes read)", archive, cis.getByteCount()), x);
}
} finally {
in.close();
}
return null;
}
}
/**
* Reads the URL on the current VM, and writes all the data to this {@link FilePath}
* (this is different from resolving URL remotely.)
@@ -811,7 +862,7 @@ public final class FilePath implements Serializable {
/**
* Code that gets executed on the machine where the {@link FilePath} is local.
* Used to act on {@link FilePath}.
*
* <strong>Warning:</code> implementations must be serializable, so prefer a static nested class to an inner class.
* @see FilePath#act(FileCallable)
*/
public interface FileCallable<T> extends Serializable {
@@ -958,6 +1009,27 @@ public final class FilePath implements Serializable {
}
}
/**
* Takes a {@link FilePath}+{@link FileCallable} pair and returns the equivalent {@link Callable}.
* When executing the resulting {@link Callable}, it executes {@link FileCallable#act(FileCallable)}
* on this {@link FilePath}.
*
* @since 1.522
*/
public <V> Callable<V,IOException> asCallableWith(final FileCallable<V> task) {
return new Callable<V,IOException>() {
@Override
public V call() throws IOException {
try {
return act(task);
} catch (InterruptedException e) {
throw (IOException)new InterruptedIOException().initCause(e);
}
}
private static final long serialVersionUID = 1L;
};
}
/**
* Converts this file to the URI, relative to the machine
* on which this file is available.
@@ -971,6 +1043,15 @@ public final class FilePath implements Serializable {
});
}
/**
* Gets the {@link VirtualFile} representation of this {@link FilePath}
*
* @since 1.532
*/
public VirtualFile toVirtualFile() {
return VirtualFile.forFilePath(this);
}
/**
* Creates this directory.
*/
@@ -1173,8 +1254,11 @@ public final class FilePath implements Serializable {
}
Writer w = new FileWriter(f);
w.write(contents);
w.close();
try {
w.write(contents);
} finally {
w.close();
}
return f.getAbsolutePath();
}
@@ -1619,12 +1703,13 @@ public final class FilePath implements Serializable {
/**
* Computes the MD5 digest of the file in hex string.
* @see Util#getDigestOf(File)
*/
public String digest() throws IOException, InterruptedException {
return act(new FileCallable<String>() {
private static final long serialVersionUID = 1L;
public String invoke(File f, VirtualChannel channel) throws IOException {
return Util.getDigestOf(new FileInputStream(f));
return Util.getDigestOf(f);
}
});
}
@@ -1806,6 +1891,18 @@ public final class FilePath implements Serializable {
* the number of files copied.
*/
public int copyRecursiveTo(final String fileMask, final String excludes, final FilePath target) throws IOException, InterruptedException {
return copyRecursiveTo(new DirScanner.Glob(fileMask, excludes), target, fileMask);
}
/**
* Copies files according to a specified scanner to a target node.
* @param scanner a way of enumerating some files (must be serializable for possible delivery to remote side)
* @param target the destination basedir
* @param description a description of the fileset, for logging purposes
* @return the number of files copied
* @since 1.532
*/
public int copyRecursiveTo(final DirScanner scanner, final FilePath target, final String description) throws IOException, InterruptedException {
if(this.channel==target.channel) {
// local to local copy.
return act(new FileCallable<Integer>() {
@@ -1813,37 +1910,30 @@ public final class FilePath implements Serializable {
public Integer invoke(File base, VirtualChannel channel) throws IOException {
if(!base.exists()) return 0;
assert target.channel==null;
try {
class CopyImpl extends Copy {
private int copySize;
public CopyImpl() {
setProject(new org.apache.tools.ant.Project());
}
@Override
protected void doFileOperations() {
copySize = super.fileCopyMap.size();
super.doFileOperations();
}
public int getNumCopied() {
return copySize;
final File dest = new File(target.remote);
final AtomicInteger count = new AtomicInteger();
scanner.scan(base, new FileVisitor() {
@Override public void visit(File f, String relativePath) throws IOException {
if (f.isFile()) {
File target = new File(dest, relativePath);
target.getParentFile().mkdirs();
Util.copyFile(f, target);
count.incrementAndGet();
}
}
CopyImpl copyTask = new CopyImpl();
copyTask.setTodir(new File(target.remote));
copyTask.addFileset(Util.createFileSet(base,fileMask,excludes));
copyTask.setOverwrite(true);
copyTask.setIncludeEmptyDirs(false);
copyTask.execute();
return copyTask.getNumCopied();
} catch (BuildException e) {
throw new IOException2("Failed to copy "+base+"/"+fileMask+" to "+target,e);
}
@Override public boolean understandsSymlink() {
return true;
}
@Override public void visitSymlink(File link, String target, String relativePath) throws IOException {
try {
Util.createSymlink(dest, target, relativePath, TaskListener.NULL);
} catch (InterruptedException x) {
throw (IOException) new IOException(x.toString()).initCause(x);
}
count.incrementAndGet();
}
});
return count.get();
}
});
} else
@@ -1855,20 +1945,26 @@ public final class FilePath implements Serializable {
private static final long serialVersionUID = 1L;
public Void invoke(File f, VirtualChannel channel) throws IOException {
try {
readFromTar(remote+'/'+fileMask, f,TarCompression.GZIP.extract(pipe.getIn()));
readFromTar(remote + '/' + description, f,TarCompression.GZIP.extract(pipe.getIn()));
return null;
} finally {
pipe.getIn().close();
}
}
});
int r = writeToTar(new File(remote),fileMask,excludes,TarCompression.GZIP.compress(pipe.getOut()));
Future<Integer> future2 = actAsync(new FileCallable<Integer>() {
private static final long serialVersionUID = 1L;
@Override public Integer invoke(File f, VirtualChannel channel) throws IOException, InterruptedException {
return writeToTar(new File(remote), scanner, TarCompression.GZIP.compress(pipe.getOut()));
}
});
try {
// JENKINS-9540 in case the reading side failed, report that error first
future.get();
return future2.get();
} catch (ExecutionException e) {
throw new IOException2(e);
}
return r;
} else {
// remote -> local copy
final Pipe pipe = Pipe.createRemoteToLocal();
@@ -1877,14 +1973,14 @@ public final class FilePath implements Serializable {
private static final long serialVersionUID = 1L;
public Integer invoke(File f, VirtualChannel channel) throws IOException {
try {
return writeToTar(f,fileMask,excludes,TarCompression.GZIP.compress(pipe.getOut()));
return writeToTar(f, scanner, TarCompression.GZIP.compress(pipe.getOut()));
} finally {
pipe.getOut().close();
}
}
});
try {
readFromTar(remote+'/'+fileMask,new File(target.remote),TarCompression.GZIP.extract(pipe.getIn()));
readFromTar(remote + '/' + description,new File(target.remote),TarCompression.GZIP.extract(pipe.getIn()));
} catch (IOException e) {// BuildException or IOException
try {
future.get(3,TimeUnit.SECONDS);
@@ -1933,10 +2029,10 @@ public final class FilePath implements Serializable {
* @return
* number of files/directories that are written.
*/
private static Integer writeToTar(File baseDir, String fileMask, String excludes, OutputStream out) throws IOException {
private static Integer writeToTar(File baseDir, DirScanner scanner, OutputStream out) throws IOException {
Archiver tw = ArchiverFactory.TAR.create(out);
try {
new DirScanner.Glob(fileMask,excludes).scan(baseDir,tw);
scanner.scan(baseDir,tw);
} finally {
tw.close();
}
@@ -2387,4 +2483,47 @@ public final class FilePath implements Serializable {
throw new AssertionError(e);
}
}
/**
* Gets the {@link FilePath} representation of the "~" directory
* (User's home directory in the Unix sense) of the given channel.
*/
public static FilePath getHomeDirectory(VirtualChannel ch) throws InterruptedException, IOException {
return ch.call(new Callable<FilePath,IOException>() {
public FilePath call() throws IOException {
return new FilePath(new File(System.getProperty("user.home")));
}
});
}
/**
* Helper class to make it easy to send an explicit list of files using {@link FilePath} methods.
* @since 1.532
*/
public static final class ExplicitlySpecifiedDirScanner extends DirScanner {
private static final long serialVersionUID = 1;
private final Map<String,String> files;
/**
* Create a “scanner” (it actually does no scanning).
* @param files a map from logical relative paths as per {@link FileVisitor#visit}, to actual relative paths within the scanned directory
*/
public ExplicitlySpecifiedDirScanner(Map<String,String> files) {
this.files = files;
}
@Override public void scan(File dir, FileVisitor visitor) throws IOException {
for (Map.Entry<String,String> entry : files.entrySet()) {
String archivedPath = entry.getKey();
assert archivedPath.indexOf('\\') == -1;
String workspacePath = entry.getValue();
assert workspacePath.indexOf('\\') == -1;
scanSingle(new File(dir, workspacePath), archivedPath, visitor);
}
}
}
}
@@ -126,7 +126,7 @@ public abstract class FileSystemProvisioner implements ExtensionPoint, Describab
*
* <p>
* This method can prepare the underlying file system in preparation
* for the later {@link #snapshot(AbstractBuild, FilePath, TaskListener)}.
* for the later {@link FileSystemProvisioner.Default#snapshot(AbstractBuild, FilePath, TaskListener)}.
*
* TODO : the method needs to be able to see the snapshot would
* be later needed. In fact, perhaps we should only call this method
+310 -49
View File
@@ -25,13 +25,34 @@
*/
package hudson;
import com.google.common.base.Predicate;
import com.google.common.base.Predicates;
import hudson.cli.CLICommand;
import hudson.console.ConsoleAnnotationDescriptor;
import hudson.console.ConsoleAnnotatorFactory;
import hudson.model.*;
import hudson.matrix.MatrixProject;
import hudson.model.AbstractProject;
import hudson.model.Action;
import hudson.model.Describable;
import hudson.model.Descriptor;
import hudson.model.DescriptorVisibilityFilter;
import hudson.model.Hudson;
import hudson.model.Item;
import hudson.model.ItemGroup;
import hudson.model.Items;
import hudson.model.JDK;
import hudson.model.Job;
import hudson.model.JobPropertyDescriptor;
import hudson.model.ModelObject;
import hudson.model.Node;
import hudson.model.PageDecorator;
import hudson.model.ParameterDefinition;
import hudson.model.ParameterDefinition.ParameterDescriptor;
import hudson.model.Project;
import hudson.model.Run;
import hudson.model.TopLevelItem;
import hudson.model.User;
import hudson.model.View;
import hudson.scm.SCM;
import hudson.scm.SCMDescriptor;
import hudson.search.SearchableModelObject;
import hudson.security.AccessControlled;
import hudson.security.AuthorizationStrategy;
@@ -52,37 +73,13 @@ import hudson.tasks.Builder;
import hudson.tasks.Publisher;
import hudson.tasks.UserAvatarResolver;
import hudson.util.Area;
import hudson.util.FormValidation.CheckMethod;
import hudson.util.Iterators;
import hudson.scm.SCM;
import hudson.scm.SCMDescriptor;
import hudson.util.Secret;
import hudson.views.MyViewsTabBar;
import hudson.views.ViewsTabBar;
import hudson.widgets.RenderOnDemandClosure;
import jenkins.model.GlobalConfiguration;
import jenkins.model.GlobalConfigurationCategory;
import jenkins.model.GlobalConfigurationCategory.Unclassified;
import jenkins.model.Jenkins;
import jenkins.model.ModelObjectWithContextMenu;
import org.acegisecurity.providers.anonymous.AnonymousAuthenticationToken;
import org.apache.commons.jelly.JellyContext;
import org.apache.commons.jelly.JellyTagException;
import org.apache.commons.jelly.Script;
import org.apache.commons.jelly.XMLOutput;
import org.apache.commons.jexl.parser.ASTSizeFunction;
import org.apache.commons.jexl.util.Introspector;
import org.codehaus.mojo.animal_sniffer.IgnoreJRERequirement;
import org.jvnet.tiger_types.Types;
import org.kohsuke.stapler.Ancestor;
import org.kohsuke.stapler.Stapler;
import org.kohsuke.stapler.StaplerRequest;
import org.kohsuke.stapler.StaplerResponse;
import org.kohsuke.stapler.jelly.InternationalizedStringExpression.RawHtmlArgument;
import javax.servlet.ServletException;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException;
import java.io.PrintWriter;
@@ -93,8 +90,8 @@ import java.lang.management.ManagementFactory;
import java.lang.management.MonitorInfo;
import java.lang.management.ThreadInfo;
import java.lang.management.ThreadMXBean;
import java.lang.reflect.Type;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
@@ -108,6 +105,7 @@ import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.ConcurrentModificationException;
import java.util.Date;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
@@ -115,16 +113,45 @@ import java.util.Locale;
import java.util.Map;
import java.util.SortedMap;
import java.util.TreeMap;
import java.util.Date;
import java.util.logging.Level;
import java.util.logging.LogManager;
import java.util.logging.LogRecord;
import java.util.logging.Logger;
import java.util.logging.SimpleFormatter;
import java.util.regex.Pattern;
import javax.servlet.ServletException;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import jenkins.model.GlobalConfiguration;
import jenkins.model.GlobalConfigurationCategory;
import jenkins.model.GlobalConfigurationCategory.Unclassified;
import jenkins.model.Jenkins;
import jenkins.model.ModelObjectWithChildren;
import jenkins.model.ModelObjectWithContextMenu;
import org.acegisecurity.providers.anonymous.AnonymousAuthenticationToken;
import org.apache.commons.jelly.JellyContext;
import org.apache.commons.jelly.JellyTagException;
import org.apache.commons.jelly.Script;
import org.apache.commons.jelly.XMLOutput;
import org.apache.commons.jexl.parser.ASTSizeFunction;
import org.apache.commons.jexl.util.Introspector;
import org.apache.commons.lang.StringUtils;
import org.codehaus.mojo.animal_sniffer.IgnoreJRERequirement;
import org.jvnet.tiger_types.Types;
import org.kohsuke.stapler.Ancestor;
import org.kohsuke.stapler.Stapler;
import org.kohsuke.stapler.StaplerRequest;
import org.kohsuke.stapler.StaplerResponse;
import org.kohsuke.stapler.jelly.InternationalizedStringExpression.RawHtmlArgument;
import com.google.common.base.Predicate;
import com.google.common.base.Predicates;
import org.kohsuke.accmod.Restricted;
import org.kohsuke.accmod.restrictions.NoExternalUse;
import org.kohsuke.accmod.restrictions.DoNotUse;
/**
* Utility functions used in views.
@@ -135,6 +162,7 @@ import org.kohsuke.accmod.restrictions.NoExternalUse;
*
* @author Kohsuke Kawaguchi
*/
@SuppressWarnings("rawtypes")
public class Functions {
private static volatile int globalIota = 0;
private int iota;
@@ -161,6 +189,17 @@ public class Functions {
return o instanceof ModelObjectWithContextMenu;
}
public static boolean isModelWithChildren(Object o) {
return o instanceof ModelObjectWithChildren;
}
/**
* @since 1.524
*/
public static boolean isMatrixProject(Object o) {
return o instanceof MatrixProject;
}
public static String xsDate(Calendar cal) {
return Util.XS_DATETIME_FORMATTER.format(cal.getTime());
}
@@ -410,6 +449,51 @@ public class Functions {
return formatter.format(r);
}
@Restricted(DoNotUse.class)
public static String[] printLogRecordHtml(LogRecord r, LogRecord prior) {
String[] oldParts = prior == null ? new String[4] : logRecordPreformat(prior);
String[] newParts = logRecordPreformat(r);
for (int i = 0; i < /* not 4 */3; i++) {
newParts[i] = "<span class='" + (newParts[i].equals(oldParts[i]) ? "logrecord-metadata-old" : "logrecord-metadata-new") + "'>" + newParts[i] + "</span>";
}
return newParts;
}
/**
* Partially formats a log record.
* @return date, source, level, message+thrown
* @see SimpleFormatter#format(LogRecord)
*/
private static String[] logRecordPreformat(LogRecord r) {
String source;
if (r.getSourceClassName() == null) {
source = r.getLoggerName();
} else {
if (r.getSourceMethodName() == null) {
source = r.getSourceClassName();
} else {
source = r.getSourceClassName() + " " + r.getSourceMethodName();
}
}
String message = new SimpleFormatter().formatMessage(r) + "\n";
Throwable x = r.getThrown();
return new String[] {
String.format("%1$tb %1$td, %1$tY %1$tl:%1$tM:%1$tS %1$Tp", new Date(r.getMillis())),
source,
r.getLevel().getLocalizedName(),
x == null ? message : message + printThrowable(x) + "\n"
};
}
/**
* Reverses a collection so that it can be easily walked in reverse order.
* @since 1.525
*/
public static <T> Iterable<T> reverse(Collection<T> collection) {
List<T> list = new ArrayList<T>(collection);
Collections.reverse(list);
return list;
}
public static Cookie getCookie(HttpServletRequest req,String name) {
Cookie[] cookies = req.getCookies();
if(cookies!=null) {
@@ -778,6 +862,22 @@ public class Functions {
return result;
}
/**
* Returns those node properties which can be configured as global node properties.
*
* @since 1.520
*/
public static List<NodePropertyDescriptor> getGlobalNodePropertyDescriptors() {
List<NodePropertyDescriptor> result = new ArrayList<NodePropertyDescriptor>();
Collection<NodePropertyDescriptor> list = (Collection) Jenkins.getInstance().getDescriptorList(NodeProperty.class);
for (NodePropertyDescriptor npd : list) {
if (npd.isApplicableAsGlobal()) {
result.add(npd);
}
}
return result;
}
/**
* Gets all the descriptors sorted by their inheritance tree of {@link Describable}
* so that descriptors of similar types come nearby.
@@ -906,32 +1006,131 @@ public class Functions {
}
String path = ancestors.get(p);
if(path!=null) return path;
if(path!=null) {
return normalizeURI(path + '/');
}
Item i=p;
String url = "";
Collection<TopLevelItem> viewItems;
if (view != null) {
viewItems = view.getItems();
} else {
viewItems = Collections.emptyList();
}
while(true) {
ItemGroup ig = i.getParent();
url = i.getShortUrl()+url;
if(ig== Jenkins.getInstance()) {
if(ig== Jenkins.getInstance() || (view != null && ig == view.getOwnerItemGroup())) {
assert i instanceof TopLevelItem;
if(view!=null && view.contains((TopLevelItem)i)) {
if(viewItems.contains((TopLevelItem)i)) {
// if p and the current page belongs to the same view, then return a relative path
return ancestors.get(view)+'/'+url;
return normalizeURI(ancestors.get(view)+'/'+url);
} else {
// otherwise return a path from the root Hudson
return request.getContextPath()+'/'+p.getUrl();
return normalizeURI(request.getContextPath()+'/'+p.getUrl());
}
}
path = ancestors.get(ig);
if(path!=null) return path+'/'+url;
if(path!=null) {
return normalizeURI(path+'/'+url);
}
assert ig instanceof Item; // if not, ig must have been the Hudson instance
i = (Item) ig;
}
}
private static String normalizeURI(String uri) {
return URI.create(uri).normalize().toString();
}
/**
* Gets all the {@link TopLevelItem}s recursively in the {@link ItemGroup} tree.
*
* @since 1.512
*/
public static List<TopLevelItem> getAllTopLevelItems(ItemGroup root) {
return Items.getAllItems(root, TopLevelItem.class);
}
/**
* Gets the relative name or display name to the given item from the specified group.
*
* @since 1.515
* @param p the Item we want the relative display name
* @param g the ItemGroup used as point of reference for the item
* @param useDisplayName if true, returns a display name, otherwise returns a name
* @return
* String like "foo » bar"
*/
public static String getRelativeNameFrom(Item p, ItemGroup g, boolean useDisplayName) {
if (p == null) return null;
if (g == null) return useDisplayName ? p.getFullDisplayName() : p.getFullName();
String separationString = useDisplayName ? " » " : "/";
// first list up all the parents
Map<ItemGroup,Integer> parents = new HashMap<ItemGroup,Integer>();
int depth=0;
while (g!=null) {
parents.put(g, depth++);
if (g instanceof Item)
g = ((Item)g).getParent();
else
g = null;
}
StringBuilder buf = new StringBuilder();
Item i=p;
while (true) {
if (buf.length()>0) buf.insert(0,separationString);
buf.insert(0,useDisplayName ? i.getDisplayName() : i.getName());
ItemGroup gr = i.getParent();
Integer d = parents.get(gr);
if (d!=null) {
for (int j=d; j>0; j--) {
buf.insert(0,separationString);
buf.insert(0,"..");
}
return buf.toString();
}
if (gr instanceof Item)
i = (Item)gr;
else
return null;
}
}
/**
* Gets the name to the given item relative to given group.
*
* @since 1.515
* @param p the Item we want the relative display name
* @param g the ItemGroup used as point of reference for the item
* @return
* String like "foo » bar"
*/
public static String getRelativeNameFrom(Item p, ItemGroup g) {
return getRelativeNameFrom(p, g, false);
}
/**
* Gets the relative display name to the given item from the specified group.
*
* @since 1.512
* @param p the Item we want the relative display name
* @param g the ItemGroup used as point of reference for the item
* @return
* String like "foo » bar"
*/
public static String getRelativeDisplayNameFrom(Item p, ItemGroup g) {
return getRelativeNameFrom(p, g, true);
}
public static Map<Thread,StackTraceElement[]> dumpAllThreads() {
Map<Thread,StackTraceElement[]> sorted = new TreeMap<Thread,StackTraceElement[]>(new ThreadSorter());
@@ -1038,21 +1237,21 @@ public class Functions {
StackTraceElement[] stackTrace = ti.getStackTrace();
for (int i=0; i < stackTrace.length; i++) {
StackTraceElement ste = stackTrace[i];
sb.append("\tat " + ste.toString());
sb.append("\tat ").append(ste);
sb.append('\n');
if (i == 0 && ti.getLockInfo() != null) {
Thread.State ts = ti.getThreadState();
switch (ts) {
case BLOCKED:
sb.append("\t- blocked on " + ti.getLockInfo());
sb.append("\t- blocked on ").append(ti.getLockInfo());
sb.append('\n');
break;
case WAITING:
sb.append("\t- waiting on " + ti.getLockInfo());
sb.append("\t- waiting on ").append(ti.getLockInfo());
sb.append('\n');
break;
case TIMED_WAITING:
sb.append("\t- waiting on " + ti.getLockInfo());
sb.append("\t- waiting on ").append(ti.getLockInfo());
sb.append('\n');
break;
default:
@@ -1061,7 +1260,7 @@ public class Functions {
for (MonitorInfo mi : ti.getLockedMonitors()) {
if (mi.getLockedStackDepth() == i) {
sb.append("\t- locked " + mi);
sb.append("\t- locked ").append(mi);
sb.append('\n');
}
}
@@ -1072,7 +1271,7 @@ public class Functions {
sb.append("\n\tNumber of locked synchronizers = " + locks.length);
sb.append('\n');
for (LockInfo li : locks) {
sb.append("\t- " + li);
sb.append("\t- ").append(li);
sb.append('\n');
}
}
@@ -1188,15 +1387,23 @@ public class Functions {
public static String toCCStatus(Item i) {
if (i instanceof Job) {
Job j = (Job) i;
switch (j.getIconColor().noAnime()) {
switch (j.getIconColor()) {
case ABORTED:
case ABORTED_ANIME:
case RED:
case RED_ANIME:
case YELLOW:
case YELLOW_ANIME:
return "Failure";
case BLUE:
case BLUE_ANIME:
return "Success";
case DISABLED:
case DISABLED_ANIME:
case GREY:
case GREY_ANIME:
case NOTBUILT:
case NOTBUILT_ANIME:
return "Unknown";
}
}
@@ -1335,6 +1542,9 @@ public class Functions {
/**
* Determines the form validation check URL. See textbox.jelly
*
* @deprecated
* Use {@link #calcCheckUrl}
*/
public String getCheckUrl(String userDefined, Object descriptor, String field) {
if(userDefined!=null || field==null) return userDefined;
@@ -1345,6 +1555,21 @@ public class Functions {
return null;
}
/**
* Determines the parameters that client-side needs for a form validation check. See prepareDatabinding.jelly
* @since 1.528
*/
public void calcCheckUrl(Map attributes, String userDefined, Object descriptor, String field) {
if(userDefined!=null || field==null) return;
if (descriptor instanceof Descriptor) {
Descriptor d = (Descriptor) descriptor;
CheckMethod m = d.getCheckMethod(field);
attributes.put("checkUrl",m.toStemUrl());
attributes.put("checkDependsOn",m.getDependsOn());
}
}
/**
* If the given href link is matching the current page, return true.
*
@@ -1427,16 +1652,16 @@ public class Functions {
for (ConsoleAnnotatorFactory f : ConsoleAnnotatorFactory.all()) {
String path = cp + "/extensionList/" + ConsoleAnnotatorFactory.class.getName() + "/" + f.getClass().getName();
if (f.hasScript())
buf.append("<script src='"+path+"/script.js'></script>");
buf.append("<script src='").append(path).append("/script.js'></script>");
if (f.hasStylesheet())
buf.append("<link rel='stylesheet' type='text/css' href='"+path+"/style.css' />");
buf.append("<link rel='stylesheet' type='text/css' href='").append(path).append("/style.css' />");
}
for (ConsoleAnnotationDescriptor d : ConsoleAnnotationDescriptor.all()) {
String path = cp+"/descriptor/"+d.clazz.getName();
if (d.hasScript())
buf.append("<script src='"+path+"/script.js'></script>");
buf.append("<script src='").append(path).append("/script.js'></script>");
if (d.hasStylesheet())
buf.append("<link rel='stylesheet' type='text/css' href='"+path+"/style.css' />");
buf.append("<link rel='stylesheet' type='text/css' href='").append(path).append("/style.css' />");
}
return buf.toString();
}
@@ -1579,7 +1804,7 @@ public class Functions {
/**
* Returns human readable information about file size
*
* @param file size in bytes
* @param size file size in bytes
* @return file size in appropriate unit
*/
public static String humanReadableByteSize(long size){
@@ -1603,4 +1828,40 @@ public class Functions {
DecimalFormat format = new DecimalFormat("#0.00");
return format.format(number) + " " + measure;
}
/**
* Get a string that can be safely broken to several lines when necessary.
*
* This implementation inserts &lt;wbr> tags into string. It allows browsers
* to wrap line before any sequence of punctuation characters or anywhere
* in the middle of prolonged sequences of word characters.
*
* @since 1.517
*/
public static String breakableString(final String plain) {
return plain.replaceAll("(\\p{Punct}+\\w)", "<wbr>$1")
.replaceAll("(\\w{10})(?=\\w{3})", "$1<wbr>")
;
}
/**
* Advertises the minimum set of HTTP headers that assist programmatic
* discovery of Jenkins.
*/
public static void advertiseHeaders(HttpServletResponse rsp) {
Jenkins j = Jenkins.getInstance();
rsp.setHeader("X-Hudson","1.395");
rsp.setHeader("X-Jenkins", Jenkins.VERSION);
rsp.setHeader("X-Jenkins-Session", Jenkins.SESSION_HASH);
TcpSlaveAgentListener tal = j.tcpSlaveAgentListener;
if (tal !=null) {
rsp.setIntHeader("X-Hudson-CLI-Port", tal.getPort());
rsp.setIntHeader("X-Jenkins-CLI-Port", tal.getPort());
rsp.setIntHeader("X-Jenkins-CLI2-Port", tal.getPort());
rsp.setHeader("X-Jenkins-CLI-Host", TcpSlaveAgentListener.CLI_HOST_NAME);
}
}
}
+10
View File
@@ -664,6 +664,11 @@ public abstract class Launcher {
public final Launcher decorateByPrefix(final String... prefix) {
final Launcher outer = this;
return new Launcher(outer) {
@Override
public boolean isUnix() {
return outer.isUnix();
}
@Override
public Proc launch(ProcStarter starter) throws IOException {
starter.commands.addAll(0,Arrays.asList(prefix));
@@ -711,6 +716,11 @@ public abstract class Launcher {
final EnvVars env = new EnvVars(_env);
final Launcher outer = this;
return new Launcher(outer) {
@Override
public boolean isUnix() {
return outer.isUnix();
}
@Override
public Proc launch(ProcStarter starter) throws IOException {
EnvVars e = new EnvVars(env);
+52 -9
View File
@@ -59,6 +59,7 @@ import org.apache.commons.fileupload.servlet.ServletFileUpload;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.logging.LogFactory;
import org.jenkinsci.bytecode.Transformer;
import org.jvnet.hudson.reactor.Executable;
import org.jvnet.hudson.reactor.Reactor;
import org.jvnet.hudson.reactor.ReactorException;
@@ -101,6 +102,7 @@ import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.Future;
import java.util.jar.JarFile;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.xml.sax.Attributes;
@@ -150,6 +152,8 @@ public abstract class PluginManager extends AbstractModelObject implements OnMas
// and load plugin-contributed classes.
public final ClassLoader uberClassLoader = new UberClassLoader();
private final Transformer compatibilityTransformer = new Transformer();
/**
* Once plugin is uploaded, this flag becomes true.
* This is used to report a message that Jenkins needs to be restarted
@@ -178,6 +182,17 @@ public abstract class PluginManager extends AbstractModelObject implements OnMas
rootDir.mkdirs();
strategy = createPluginStrategy();
// load up rules for the core first
try {
compatibilityTransformer.loadRules(getClass().getClassLoader());
} catch (IOException e) {
LOGGER.log(Level.WARNING, "Failed to load compatibility rewrite rules",e);
}
}
public Transformer getCompatibilityTransformer() {
return compatibilityTransformer;
}
public Api getApi() {
@@ -300,6 +315,13 @@ public abstract class PluginManager extends AbstractModelObject implements OnMas
}
});
// Let's see for a while until we open this functionality up to plugins
// g.followedBy().attains(PLUGINS_LISTED).add("Load compatibility rules", new Executable() {
// public void run(Reactor reactor) throws Exception {
// compatibilityTransformer.loadRules(uberClassLoader);
// }
// });
session.addAll(g.discoverTasks(session));
pluginListed = true; // technically speaking this is still too early, as at this point tasks are merely scheduled, not necessarily executed.
@@ -729,19 +751,25 @@ public abstract class PluginManager extends AbstractModelObject implements OnMas
if(!fileName.endsWith(".jpi") && !fileName.endsWith(".hpi")){
throw new Failure(hudson.model.Messages.Hudson_NotAPlugin(fileName));
}
final String baseName = FilenameUtils.getBaseName(fileName);
new File(rootDir, baseName + ".hpi").delete(); // don't keep confusing legacy *.hpi
fileItem.write(new File(rootDir, baseName + ".jpi")); // rename all new plugins to *.jpi
// first copy into a temporary file name
File t = File.createTempFile("uploaded", ".jpi");
t.deleteOnExit();
fileItem.write(t);
fileItem.delete();
PluginWrapper existing = getPlugin(baseName);
if (existing!=null && existing.isBundled){
existing.doPin();
}
final String baseName = identifyPluginShortName(t);
pluginUploaded = true;
return new HttpRedirect(".");
// Now create a dummy plugin that we can dynamically load (the InstallationJob will force a restart if one is needed):
JSONObject cfg = new JSONObject().
element("name", baseName).
element("version", "0"). // unused but mandatory
element("url", t.toURI().toString()).
element("dependencies", new JSONArray());
new UpdateSite(UpdateCenter.ID_UPLOAD, null).new Plugin(UpdateCenter.ID_UPLOAD, cfg).deploy(true);
return new HttpRedirect("../updateCenter");
} catch (IOException e) {
throw e;
} catch (Exception e) {// grrr. fileItem.write throws this
@@ -749,6 +777,21 @@ public abstract class PluginManager extends AbstractModelObject implements OnMas
}
}
protected String identifyPluginShortName(File t) {
try {
JarFile j = new JarFile(t);
try {
String name = j.getManifest().getMainAttributes().getValue("Short-Name");
if (name!=null) return name;
} finally {
j.close();
}
} catch (IOException e) {
LOGGER.log(WARNING, "Failed to identify the short name from "+t,e);
}
return FilenameUtils.getBaseName(t.getName()); // fall back to the base name of what's uploaded
}
public Descriptor<ProxyConfiguration> getProxyDescriptor() {
return Jenkins.getInstance().getDescriptor(ProxyConfiguration.class);
}
@@ -775,7 +818,7 @@ public abstract class PluginManager extends AbstractModelObject implements OnMas
Jenkins.getInstance().checkPermission(Jenkins.ADMINISTER);
List<Future<UpdateCenter.UpdateCenterJob>> jobs = new ArrayList<Future<UpdateCenter.UpdateCenterJob>>();
UpdateCenter uc = Jenkins.getInstance().getUpdateCenter();
// XXX call uc.updateAllSites() when available? perhaps not, since we should not block on network here
// TODO call uc.updateAllSites() when available? perhaps not, since we should not block on network here
for (Map.Entry<String,VersionNumber> requestedPlugin : parseRequestedPlugins(configXml).entrySet()) {
PluginWrapper pw = getPlugin(requestedPlugin.getKey());
if (pw == null) { // install new
+7 -3
View File
@@ -487,7 +487,7 @@ public class PluginWrapper implements Comparable<PluginWrapper>, ModelObject {
/**
* If the plugin has {@link #getUpdateInfo() an update},
* returns the {@link UpdateSite.Plugin} object.
* returns the {@link hudson.model.UpdateSite.Plugin} object.
*
* @return
* This method may return null &mdash; for example,
@@ -501,7 +501,7 @@ public class PluginWrapper implements Comparable<PluginWrapper>, ModelObject {
}
/**
* returns the {@link UpdateSite.Plugin} object, or null.
* returns the {@link hudson.model.UpdateSite.Plugin} object, or null.
*/
public UpdateSite.Plugin getInfo() {
UpdateCenter uc = Jenkins.getInstance().getUpdateCenter();
@@ -567,7 +567,11 @@ public class PluginWrapper implements Comparable<PluginWrapper>, ModelObject {
if (backup.exists()) {
try {
JarFile backupPlugin = new JarFile(backup);
return backupPlugin.getManifest().getMainAttributes().getValue("Plugin-Version");
try {
return backupPlugin.getManifest().getMainAttributes().getValue("Plugin-Version");
} finally {
backupPlugin.close();
}
} catch (IOException e) {
LOGGER.log(WARNING, "Failed to get backup version ", e);
return null;
+1 -1
View File
@@ -399,7 +399,7 @@ public abstract class Proc {
try {
byte[] buf = new byte[8192];
int len;
while ((len = in.read(buf)) > 0) {
while ((len = in.read(buf)) >= 0) {
out.write(buf, 0, len);
out.flush();
}
@@ -26,8 +26,10 @@ package hudson;
import hudson.slaves.OfflineCause;
import jenkins.AgentProtocol;
import java.io.BufferedWriter;
import java.io.DataInputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.net.BindException;
import java.net.ServerSocket;
@@ -139,7 +141,9 @@ public final class TcpSlaveAgentListener extends Thread {
LOGGER.info("Accepted connection #"+id+" from "+s.getRemoteSocketAddress());
DataInputStream in = new DataInputStream(s.getInputStream());
PrintWriter out = new PrintWriter(s.getOutputStream(),true); // DEPRECATED: newer protocol shouldn't use PrintWriter but should use DataOutputStream
PrintWriter out = new PrintWriter(
new BufferedWriter(new OutputStreamWriter(s.getOutputStream(),"UTF-8")),
true); // DEPRECATED: newer protocol shouldn't use PrintWriter but should use DataOutputStream
String s = in.readUTF();
+51 -20
View File
@@ -34,7 +34,6 @@ import hudson.util.IOException2;
import hudson.util.QuotedStringTokenizer;
import hudson.util.VariableResolver;
import hudson.util.jna.WinIOException;
import jenkins.model.Jenkins;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.time.FastDateFormat;
import org.apache.tools.ant.BuildException;
@@ -42,10 +41,9 @@ import org.apache.tools.ant.Project;
import org.apache.tools.ant.taskdefs.Chmod;
import org.apache.tools.ant.taskdefs.Copy;
import org.apache.tools.ant.types.FileSet;
import org.jruby.ext.posix.FileStat;
import org.jruby.ext.posix.POSIX;
import jnr.posix.FileStat;
import jnr.posix.POSIX;
import org.codehaus.mojo.animal_sniffer.IgnoreJRERequirement;
import org.kohsuke.stapler.Stapler;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
@@ -61,7 +59,6 @@ import java.nio.CharBuffer;
import java.nio.charset.CharacterCodingException;
import java.nio.charset.Charset;
import java.nio.charset.CharsetEncoder;
import java.security.DigestInputStream;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.text.NumberFormat;
@@ -76,6 +73,8 @@ import java.util.regex.Pattern;
import hudson.util.jna.Kernel32Utils;
import static hudson.util.jna.GNUCLibrary.LIBC;
import java.security.DigestInputStream;
import org.apache.commons.codec.digest.DigestUtils;
/**
* Various utility methods that don't have more proper home.
@@ -113,9 +112,9 @@ public class Util {
}
/**
* Pattern for capturing variables. Either $xyz or ${xyz}, while ignoring "$$"
* Pattern for capturing variables. Either $xyz, ${xyz} or ${a.b} but not $a.b, while ignoring "$$"
*/
private static final Pattern VARIABLE = Pattern.compile("\\$([A-Za-z0-9_]+|\\{[A-Za-z0-9_]+\\}|\\$)");
private static final Pattern VARIABLE = Pattern.compile("\\$([A-Za-z0-9_]+|\\{[A-Za-z0-9_.]+\\}|\\$)");
/**
* Replaces the occurrence of '$key' by <tt>properties.get('key')</tt>.
@@ -284,7 +283,7 @@ public class Util {
}
try {// try libc chmod
POSIX posix = PosixAPI.get();
POSIX posix = PosixAPI.jnr();
String path = f.getAbsolutePath();
FileStat stat = posix.stat(path);
posix.chmod(path, stat.mode()|0200); // u+w
@@ -451,7 +450,7 @@ public class Util {
public static void copyStream(InputStream in,OutputStream out) throws IOException {
byte[] buf = new byte[8192];
int len;
while((len=in.read(buf))>0)
while((len=in.read(buf))>=0)
out.write(buf,0,len);
}
@@ -554,6 +553,7 @@ public class Util {
* The stream will be closed by this method at the end of this method.
* @return
* 32-char wide string
* @see DigestUtils#md5Hex(InputStream)
*/
public static String getDigestOf(InputStream source) throws IOException {
try {
@@ -562,7 +562,7 @@ public class Util {
byte[] buffer = new byte[1024];
DigestInputStream in =new DigestInputStream(source,md5);
try {
while(in.read(buffer)>0)
while(in.read(buffer)>=0)
; // simply discard the input
} finally {
in.close();
@@ -571,6 +571,13 @@ public class Util {
} catch (NoSuchAlgorithmException e) {
throw new IOException2("MD5 not installed",e); // impossible
}
/* JENKINS-18178: confuses Maven 2 runner
try {
return DigestUtils.md5Hex(source);
} finally {
source.close();
}
*/
}
public static String getDigestOf(String text) {
@@ -581,6 +588,22 @@ public class Util {
}
}
/**
* Computes the MD5 digest of a file.
* @param file a file
* @return a 32-character string
* @throws IOException in case reading fails
* @since 1.525
*/
public static String getDigestOf(File file) throws IOException {
InputStream is = new FileInputStream(file);
try {
return getDigestOf(new BufferedInputStream(is));
} finally {
is.close();
}
}
/**
* Converts a string into 128-bit AES key.
* @since 1.308
@@ -782,10 +805,10 @@ public class Util {
/**
* Encode a single path component for use in an HTTP URL.
* Escapes all non-ASCII, general unsafe (space and "#%<>[\]^`{|}~)
* and HTTP special characters (/;:?) as specified in RFC1738.
* (so alphanumeric and !@$&*()-_=+',. are not encoded)
* Note that slash(/) is encoded, so the given string should be a
* Escapes all non-ASCII, general unsafe (space and {@code "#%<>[\]^`{|}~})
* and HTTP special characters ({@code /;:?}) as specified in RFC1738.
* (so alphanumeric and {@code !@$&*()-_=+',.} are not encoded)
* Note that slash ({@code /}) is encoded, so the given string should be a
* single path component used in constructing a URL.
* Method name inspired by PHP's rawurlencode.
*/
@@ -1065,6 +1088,9 @@ public class Util {
return;
}
throw e;
} catch (UnsatisfiedLinkError e) {
// not available on this Windows
return;
}
} else {
String errmsg = "";
@@ -1086,13 +1112,16 @@ public class Util {
} catch (LinkageError e) {
// if JNA is unavailable, fall back.
// we still prefer to try JNA first as PosixAPI supports even smaller platforms.
if (PosixAPI.supportsNative()) {
r = PosixAPI.get().symlink(targetPath,symlinkFile.getAbsolutePath());
POSIX posix = PosixAPI.jnr();
if (posix.isNative()) {
// TODO should we rethrow PosixException as IOException here?
r = posix.symlink(targetPath,symlinkFile.getAbsolutePath());
}
}
}
if (r==null) {
// if all else fail, fall back to the most expensive approach of forking a process
// TODO is this really necessary? JavaPOSIX should do this automatically
r = new LocalProc(new String[]{
"ln","-s", targetPath, symlinkPath},
new String[0],listener.getLogger(), baseDir).join();
@@ -1197,6 +1226,10 @@ public class Util {
} catch (ClassNotFoundException x3) {
assert false : x3; // should be Java 7+ here
}
if (x2.getClass().getName().equals("java.nio.file.FileSystemException")) {
// Thrown ("Incorrect function.") on JDK 7u21 in Windows 2012 when called on a non-symlink, rather than NotLinkException, contrary to documentation. Maybe only when not on NTFS?
return null;
}
if (x2 instanceof IOException) {
throw (IOException) x2;
}
@@ -1230,7 +1263,7 @@ public class Util {
} catch (LinkageError e) {
// if JNA is unavailable, fall back.
// we still prefer to try JNA first as PosixAPI supports even smaller platforms.
return PosixAPI.get().readlink(filename);
return PosixAPI.jnr().readlink(filename);
}
}
@@ -1261,9 +1294,7 @@ public class Util {
* @since 1.173
*/
public static String wrapToErrorSpan(String s) {
s = "<span class=error><img src='"+
Stapler.getCurrentRequest().getContextPath()+ Jenkins.RESOURCE_PATH+
"/images/none.gif' height=16 width=1>"+s+"</span>";
s = "<span class=error style='display:inline-block'>"+s+"</span>";
return s;
}
+10 -4
View File
@@ -59,6 +59,7 @@ import java.util.Locale;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.security.Security;
import java.util.logging.LogRecord;
/**
* Entry point when Hudson is used as a webapp.
@@ -66,7 +67,13 @@ import java.security.Security;
* @author Kohsuke Kawaguchi
*/
public final class WebAppMain implements ServletContextListener {
private final RingBufferLogHandler handler = new RingBufferLogHandler();
private final RingBufferLogHandler handler = new RingBufferLogHandler() {
@Override public synchronized void publish(LogRecord record) {
if (record.getLevel().intValue() >= Level.INFO.intValue()) {
super.publish(record);
}
}
};
private static final String APP = "app";
private boolean terminated;
private Thread initThread;
@@ -206,7 +213,7 @@ public final class WebAppMain implements ServletContextListener {
context.setAttribute(APP,new HudsonIsLoading());
initThread = new Thread("hudson initialization thread") {
initThread = new Thread("Jenkins initialization thread") {
@Override
public void run() {
boolean success = false;
@@ -341,8 +348,7 @@ public final class WebAppMain implements ServletContextListener {
// Logger is in the system classloader, so if we don't do this
// the whole web app will never be undepoyed.
Logger.getLogger("hudson").removeHandler(handler);
Logger.getLogger("jenkins").removeHandler(handler);
Logger.getLogger("").removeHandler(handler);
}
private static final Logger LOGGER = Logger.getLogger(WebAppMain.class.getName());
@@ -131,8 +131,19 @@ public class BuildCommand extends CLICommand {
}
}
QueueTaskFuture<? extends AbstractBuild> f = job.scheduleBuild2(0, new CLICause(Jenkins.getAuthentication().getName()), a);
if (!job.isBuildable()) {
String msg = Messages.BuildCommand_CLICause_CannotBuildUnknownReasons(job.getFullDisplayName());
if (job.isDisabled()) {
msg = Messages.BuildCommand_CLICause_CannotBuildDisabled(job.getFullDisplayName());
} else if (job.isHoldOffBuildUntilSave()){
msg = Messages.BuildCommand_CLICause_CannotBuildConfigNotSaved(job.getFullDisplayName());
}
stderr.println(msg);
return -1;
}
QueueTaskFuture<? extends AbstractBuild> f = job.scheduleBuild2(0, new CLICause(Jenkins.getAuthentication().getName()), a);
if (wait || sync) {
AbstractBuild b = f.waitForStart(); // wait for the start
stdout.println("Started "+b.getFullDisplayName());
@@ -9,7 +9,9 @@ import jenkins.model.Jenkins;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.net.Socket;
@@ -39,7 +41,7 @@ public class CliProtocol extends AgentProtocol {
}
public void run() throws IOException, InterruptedException {
PrintWriter out = new PrintWriter(socket.getOutputStream(),true);
PrintWriter out = new PrintWriter(new BufferedWriter(new OutputStreamWriter(socket.getOutputStream(),"UTF-8")),true);
out.println("Welcome");
runCli(new Connection(socket));
}
@@ -1,13 +1,10 @@
package hudson.cli;
import hudson.FilePath;
import hudson.FilePath.FileCallable;
import jenkins.model.Jenkins;
import jenkins.model.Jenkins.MasterComputer;
import hudson.os.PosixAPI;
import hudson.remoting.Callable;
import hudson.remoting.Channel;
import hudson.remoting.VirtualChannel;
import hudson.util.Secret;
import org.acegisecurity.Authentication;
import org.acegisecurity.AuthenticationException;
@@ -16,7 +13,6 @@ import org.acegisecurity.userdetails.UserDetails;
import org.springframework.dao.DataAccessException;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.Serializable;
@@ -106,21 +102,13 @@ public class ClientAuthenticationCache implements Serializable {
}
private void save() throws IOException, InterruptedException {
store.act(new FileCallable<Void>() {
public Void invoke(File f, VirtualChannel channel) throws IOException, InterruptedException {
f.getParentFile().mkdirs();
OutputStream os = new FileOutputStream(f);
try {
props.store(os,"Credential store");
} finally {
os.close();
}
// try to protect this file from other users, if we can.
PosixAPI.get().chmod(f.getAbsolutePath(),0600);
return null;
}
});
OutputStream os = store.write();
try {
props.store(os,"Credential store");
} finally {
os.close();
}
// try to protect this file from other users, if we can.
store.chmod(0600);
}
}
@@ -39,17 +39,27 @@ import java.io.IOException;
*/
public abstract class CommandDuringBuild extends CLICommand {
/**
* This method makes sense only when called from within the build kicked by Hudson.
* We use the environment variables that Hudson sets to determine the build that is being run.
* This method makes sense only when called from within the build kicked by Jenkins.
* We use the environment variables that Jenkins sets to determine the build that is being run.
*/
protected Run getCurrentlyBuilding() throws CmdLineException {
Run r = optCurrentlyBuilding();
if (r==null)
throw new CmdLineException("This CLI command works only when invoked from inside a build");
return r;
}
/**
* If the command is currently running inside a build, return it. Otherwise null.
*/
protected Run optCurrentlyBuilding() throws CmdLineException {
try {
CLICommand c = CLICommand.getCurrent();
if (c==null) throw new IllegalStateException("Not executing a CLI command");
String[] envs = c.checkChannel().call(new GetCharacteristicEnvironmentVariables());
if (envs[0]==null || envs[1]==null)
throw new CmdLineException("This CLI command works only when invoked from inside a build");
return null;
Job j = Jenkins.getInstance().getItemByFullName(envs[0],Job.class);
if (j==null) throw new CmdLineException("No such job: "+envs[0]);
@@ -0,0 +1,84 @@
/*
* The MIT License
*
* Copyright 2013 Red Hat, Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package hudson.cli;
import hudson.Extension;
import hudson.model.Computer;
import hudson.model.Node;
import hudson.model.Slave;
import hudson.model.User;
import jenkins.model.Jenkins;
import org.kohsuke.args4j.Argument;
import org.kohsuke.args4j.CmdLineException;
/**
* @author ogondza
* @since 1.526
*/
@Extension
public class CreateNodeCommand extends CLICommand {
@Argument(metaVar="NODE", usage="Name of the node")
public String nodeName;
@Override
public String getShortDescription() {
return Messages.CreateNodeCommand_ShortDescription();
}
@Override
protected int run() throws Exception {
final Jenkins jenkins = Jenkins.getInstance();
jenkins.checkPermission(Computer.CREATE);
final Node newNode = (Node) Jenkins.XSTREAM2.fromXML(stdin);
if (nodeName != null) {
// Using deprecated method but it's contract is preserved
newNode.setNodeName(nodeName);
}
if(newNode instanceof Slave) { //change userId too
User user = User.current();
((Slave) newNode).setUserId(user==null ? "anonymous" : user.getId());
}
if (jenkins.getNode(newNode.getNodeName()) != null) {
throw new CmdLineException(
null, "Node '" + newNode.getNodeName() + "' already exists"
);
}
jenkins.addNode(newNode);
return 0;
}
}
@@ -0,0 +1,61 @@
/*
* The MIT License
*
* Copyright (c) 2013, Red Hat, Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package hudson.cli;
import hudson.Extension;
import hudson.model.Computer;
import hudson.model.Node;
import java.io.IOException;
import jenkins.model.Jenkins;
import org.kohsuke.args4j.Argument;
/**
* @author ogondza
* @since 1.526
*/
@Extension
public class GetNodeCommand extends CLICommand {
@Argument(metaVar="NODE", usage="Name of the node", required=true)
public Node node;
@Override
public String getShortDescription() {
return Messages.GetNodeCommand_ShortDescription();
}
@Override
protected int run() throws IOException {
node.checkPermission(Computer.EXTENDED_READ);
Jenkins.XSTREAM2.toXMLUTF8(node, stdout);
return 0;
}
}
@@ -24,10 +24,14 @@
package hudson.cli;
import java.util.Collection;
import java.util.LinkedHashSet;
import java.util.Collections;
import hudson.model.Item;
import hudson.model.ItemGroup;
import hudson.model.TopLevelItem;
import hudson.model.ViewGroup;
import hudson.model.View;
import hudson.Extension;
import jenkins.model.Jenkins;
@@ -57,7 +61,7 @@ public class ListJobsCommand extends CLICommand {
View view = h.getView(name);
if (view != null) {
jobs = view.getItems();
jobs = view.getAllItems();
}
// If no view was found, try with an item group.
else {
@@ -71,7 +75,7 @@ public class ListJobsCommand extends CLICommand {
// No view and no item group with the given name found.
else {
stderr.println("No view or item group with the given name found");
jobs = Collections.emptyList();
return -1;
}
}
}
@@ -0,0 +1,49 @@
package hudson.cli;
import hudson.Extension;
import hudson.model.ParametersAction;
import hudson.model.Run;
import hudson.model.StringParameterValue;
import org.kohsuke.args4j.Argument;
import java.util.Collections;
/**
* Used from the build to update the build variable.
*
* This allows one build step to affect the environment variables seen by later build steps.
*
* @author Kohsuke Kawaguchi
* @since 1.514
*/
@Extension
public class SetBuildParameterCommand extends CommandDuringBuild {
@Argument(index=0, required=true, usage="Name of the build variable")
public String name;
@Argument(index=1,required=true, usage="Value of the build variable")
public String value;
@Override
public String getShortDescription() {
return Messages.SetBuildParameterCommand_ShortDescription();
}
@Override
protected int run() throws Exception {
Run r = getCurrentlyBuilding();
StringParameterValue p = new StringParameterValue(name, value);
ParametersAction a = r.getAction(ParametersAction.class);
if (a!=null) {
ParametersAction b = a.createUpdated(Collections.singleton(p));
r.addAction(b);
r.getActions().remove(a);
} else {
r.addAction(new ParametersAction(p));
}
return 0;
}
}
@@ -0,0 +1,59 @@
/*
* The MIT License
*
* Copyright 2013 Red Hat, Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package hudson.cli;
import java.io.IOException;
import javax.servlet.ServletException;
import hudson.Extension;
import hudson.model.Node;
import org.kohsuke.args4j.Argument;
/**
* @author ogondza
* @since 1.526
*/
@Extension
public class UpdateNodeCommand extends CLICommand {
@Argument(metaVar="NODE", usage="Name of the node", required=true)
public Node node;
@Override
public String getShortDescription() {
return Messages.UpdateNodeCommand_ShortDescription();
}
@Override
protected int run() throws IOException, ServletException {
node.toComputer().updateByXml(stdin);
return 0;
}
}
@@ -0,0 +1,68 @@
/*
* The MIT License
*
* Copyright (c) 2013, Red Hat, Inc.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package hudson.cli.handlers;
import hudson.model.Node;
import jenkins.model.Jenkins;
import org.kohsuke.MetaInfServices;
import org.kohsuke.args4j.CmdLineException;
import org.kohsuke.args4j.CmdLineParser;
import org.kohsuke.args4j.OptionDef;
import org.kohsuke.args4j.spi.OptionHandler;
import org.kohsuke.args4j.spi.Parameters;
import org.kohsuke.args4j.spi.Setter;
/**
* Refers to {@link Node} by its name.
*
* @author ogondza
* @since 1.526
*/
@MetaInfServices
public class NodeOptionHandler extends OptionHandler<Node> {
public NodeOptionHandler(CmdLineParser parser, OptionDef option, Setter<Node> setter) {
super(parser, option, setter);
}
@Override
public int parseArguments(Parameters params) throws CmdLineException {
String nodeName = params.getParameter(0);
final Node node = Jenkins.getInstance().getNode(nodeName);
if (node == null) throw new CmdLineException(owner, "No such node '" + nodeName + "'");
setter.addValue(node);
return 1;
}
@Override
public String getDefaultMetaVariable() {
return "NODE";
}
}
@@ -1,5 +1,5 @@
/**
* {@link OptionHandler} implementations for Hudson.
* {@link org.kohsuke.args4j.spi.OptionHandler} implementations for Hudson.
*/
package hudson.cli.handlers;
@@ -51,8 +51,8 @@ import java.io.OutputStream;
import java.io.Writer;
import java.nio.charset.Charset;
import java.security.GeneralSecurityException;
import java.util.zip.GZIPInputStream;
import java.util.zip.GZIPOutputStream;
import com.jcraft.jzlib.GZIPInputStream;
import com.jcraft.jzlib.GZIPOutputStream;
import static java.lang.Math.abs;
@@ -35,7 +35,7 @@ import java.util.ListIterator;
* Annotates one line of console output.
*
* <p>
* In Hudson, console output annotation is done line by line, and
* In Jenkins, console output annotation is done line by line, and
* we model this as a state machine &mdash;
* the code encapsulates some state, and it uses that to annotate one line (and possibly update the state.)
*
@@ -54,7 +54,7 @@ import java.util.ListIterator;
* </pre>
*
* <p>
* Because of a browser can request console output incrementally, in addition to above a console annotator
* Because a browser can request console output incrementally, in addition to above a console annotator
* can be serialized at any point and deserialized back later to continue annotation where it left off.
*
* <p>
@@ -50,8 +50,8 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.zip.GZIPInputStream;
import java.util.zip.GZIPOutputStream;
import com.jcraft.jzlib.GZIPInputStream;
import com.jcraft.jzlib.GZIPOutputStream;
/**
* Data that hangs off from a console output.
@@ -89,7 +89,7 @@ public class WorkspaceSnapshotSCM extends SCM {
}
/**
* Obtains the {@link WorkspaceSnapshot} object that this {@link SCM} points to,
* or throws {@link ResolvedFailedException} upon failing.
* or throws {@link hudson.fsp.WorkspaceSnapshotSCM.ResolvedFailedException} upon failing.
*
* @return never null.
*/
@@ -15,7 +15,6 @@ import org.kohsuke.MetaInfServices;
* To register, put {@link MetaInfServices} on your implementation.
*
* @author Kohsuke Kawaguchi
* @see jenkins.model.Jenkins#buildReactorListener()
*/
public interface InitReactorListener extends ReactorListener {
}
@@ -51,10 +51,10 @@ public class InitStrategy {
getBundledPluginsFromProperty(r);
// similarly, we prefer *.jpi over *.hpi
listPluginFiles(pm, ".hpi", r); // plugin jar file (for backward compatibility)
listPluginFiles(pm, ".jpi", r); // plugin jar file
listPluginFiles(pm, ".hpl", r); // linked plugin. for debugging. (for backward compatibility)
listPluginFiles(pm, ".jpl", r); // linked plugin. for debugging.
listPluginFiles(pm, ".hpl", r); // linked plugin. for debugging. (for backward compatibility)
listPluginFiles(pm, ".jpi", r); // plugin jar file
listPluginFiles(pm, ".hpi", r); // plugin jar file (for backward compatibility)
return r;
}
@@ -31,12 +31,12 @@ import java.io.FileFilter;
import java.io.IOException;
import java.net.URL;
import java.util.Arrays;
import java.util.Collections;
import java.util.logging.Logger;
import jenkins.model.Jenkins;
import static hudson.init.InitMilestone.JOB_LOADED;
import hudson.init.Initializer;
import java.util.logging.Level;
/**
* Run the initialization script, if it exists.
@@ -78,9 +78,13 @@ public class GroovyInitScript {
execute(new GroovyCodeSource(initScript));
}
private static void execute(GroovyCodeSource initScript) throws IOException {
private static void execute(GroovyCodeSource initScript) {
GroovyShell shell = new GroovyShell(Jenkins.getInstance().getPluginManager().uberClassLoader);
shell.evaluate(initScript);
try {
shell.evaluate(initScript);
} catch (RuntimeException x) {
LOGGER.log(Level.WARNING, "Failed to run script " + initScript.getName(), x);
}
}
private static final Logger LOGGER = Logger.getLogger(GroovyInitScript.class.getName());
@@ -0,0 +1,30 @@
package hudson.init.impl;
import hudson.init.Initializer;
import jenkins.model.Jenkins;
import org.kohsuke.stapler.WebApp;
import org.kohsuke.stapler.compression.CompressionFilter;
import org.kohsuke.stapler.compression.UncaughtExceptionHandler;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* @author Kohsuke Kawaguchi
*/
public class InstallUncaughtExceptionHandler {
@Initializer
public static void init(final Jenkins j) throws IOException {
CompressionFilter.setUncaughtExceptionHandler(j.servletContext, new UncaughtExceptionHandler() {
@Override
public void reportException(Throwable e, ServletContext context, HttpServletRequest req, HttpServletResponse rsp) throws ServletException, IOException {
req.setAttribute("javax.servlet.error.exception",e);
WebApp.get(j.servletContext).getSomeStapler()
.invoke(req,rsp, Jenkins.getInstance(), "/oops");
}
});
}
}
@@ -27,13 +27,13 @@
*
* <p>
* Hudson's start up is based on the same idea as the modern Unix init mechanism like initng/upstart/SMF.
* It first builds a set of {@link Task}s that are units of the initialization work, and have them declare
* It first builds a set of {@link org.jvnet.hudson.reactor.Task}s that are units of the initialization work, and have them declare
* dependencies among themselves. For example, jobs are only loaded after all the plugins are initialized,
* and restoring the build queue requires all the jobs to be loaded.
*
* <p>
* Such micro-scopic dependencies are organized into a bigger directed acyclic graph, which is then executed
* via {@link Session}. During execution of the reactor, additional tasks can be discovred and added to
* via <tt>Session</tt>. During execution of the reactor, additional tasks can be discovred and added to
* the DAG. We use this additional indirection to:
*
* <ol>
@@ -25,12 +25,20 @@ package hudson.logging;
import com.thoughtworks.xstream.XStream;
import hudson.BulkChange;
import hudson.Extension;
import hudson.FilePath;
import hudson.Util;
import hudson.XmlFile;
import hudson.model.AbstractModelObject;
import hudson.model.Computer;
import jenkins.model.Jenkins;
import hudson.model.Saveable;
import hudson.model.TaskListener;
import hudson.model.listeners.SaveableListener;
import hudson.remoting.Callable;
import hudson.remoting.Channel;
import hudson.remoting.VirtualChannel;
import hudson.slaves.ComputerListener;
import hudson.util.CopyOnWriteList;
import hudson.util.RingBufferLogHandler;
import hudson.util.XStream2;
@@ -43,12 +51,20 @@ import org.kohsuke.stapler.interceptor.RequirePOST;
import javax.servlet.ServletException;
import java.io.File;
import java.io.IOException;
import java.text.Collator;
import java.util.ArrayList;
import java.util.List;
import java.util.Arrays;
import java.util.Locale;
import java.util.Comparator;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import java.util.logging.Level;
import java.util.logging.LogRecord;
import java.util.logging.Logger;
import org.kohsuke.accmod.Restricted;
import org.kohsuke.accmod.restrictions.NoExternalUse;
/**
* Records a selected set of logs so that the system administrator
@@ -100,7 +116,7 @@ public class LogRecorder extends AbstractModelObject implements Saveable {
@DataBoundConstructor
public Target(String name, String level) {
this(name,Level.parse(level.toUpperCase(Locale.ENGLISH)));
this(name,Level.parse(level));
}
public Level getLevel() {
@@ -110,11 +126,13 @@ public class LogRecorder extends AbstractModelObject implements Saveable {
public boolean includes(LogRecord r) {
if(r.getLevel().intValue() < level)
return false; // below the threshold
if (name.length() == 0) {
return true; // like root logger, includes everything
}
String logName = r.getLoggerName();
if(logName==null || !logName.startsWith(name))
return false; // not within this logger
String rest = r.getLoggerName().substring(name.length());
String rest = logName.substring(name.length());
return rest.startsWith(".") || rest.length()==0;
}
@@ -132,6 +150,54 @@ public class LogRecorder extends AbstractModelObject implements Saveable {
Logger l = getLogger();
if(!l.isLoggable(getLevel()))
l.setLevel(getLevel());
new SetLevel(name, getLevel()).broadcast();
}
public void disable() {
getLogger().setLevel(null);
new SetLevel(name, null).broadcast();
}
}
private static final class SetLevel implements Callable<Void,Error> {
/** known loggers (kept per slave), to avoid GC */
@SuppressWarnings("MismatchedQueryAndUpdateOfCollection") private static final Set<Logger> loggers = new HashSet<Logger>();
private final String name;
private final Level level;
SetLevel(String name, Level level) {
this.name = name;
this.level = level;
}
@Override public Void call() throws Error {
Logger logger = Logger.getLogger(name);
loggers.add(logger);
logger.setLevel(level);
return null;
}
void broadcast() {
for (Computer c : Jenkins.getInstance().getComputers()) {
if (c.getName().length() > 0) { // i.e. not master
VirtualChannel ch = c.getChannel();
if (ch != null) {
try {
ch.call(this);
} catch (Exception x) {
Logger.getLogger(LogRecorder.class.getName()).log(Level.WARNING, "could not set up logging on " + c, x);
}
}
}
}
}
}
@Extension @Restricted(NoExternalUse.class) public static final class ComputerLogInitializer extends ComputerListener {
@Override public void preOnline(Computer c, Channel channel, FilePath root, TaskListener listener) throws IOException, InterruptedException {
for (LogRecorder recorder : Jenkins.getInstance().getLog().logRecorders.values()) {
for (Target t : recorder.targets) {
channel.call(new SetLevel(t.name, t.getLevel()));
}
}
}
}
@@ -147,7 +213,7 @@ public class LogRecorder extends AbstractModelObject implements Saveable {
}
public String getSearchUrl() {
return name;
return Util.rawEncode(name);
}
public String getName() {
@@ -215,7 +281,7 @@ public class LogRecorder extends AbstractModelObject implements Saveable {
// Disable logging for all our targets,
// then reenable all other loggers in case any also log the same targets
for (Target t : targets)
t.getLogger().setLevel(null);
t.disable();
for (LogRecorder log : getParent().logRecorders.values())
for (Target t : log.targets)
t.enable();
@@ -243,6 +309,44 @@ public class LogRecorder extends AbstractModelObject implements Saveable {
return handler.getView();
}
/**
* Gets a view of log records per slave matching this recorder.
* @return a map (sorted by display name) from computer to (nonempty) list of log records
* @since 1.519
*/
public Map<Computer,List<LogRecord>> getSlaveLogRecords() {
Map<Computer,List<LogRecord>> result = new TreeMap<Computer,List<LogRecord>>(new Comparator<Computer>() {
final Collator COLL = Collator.getInstance();
public int compare(Computer c1, Computer c2) {
return COLL.compare(c1.getDisplayName(), c2.getDisplayName());
}
});
for (Computer c : Jenkins.getInstance().getComputers()) {
if (c.getName().length() == 0) {
continue; // master
}
List<LogRecord> recs = new ArrayList<LogRecord>();
try {
for (LogRecord rec : c.getLogRecords()) {
for (Target t : targets) {
if (t.includes(rec)) {
recs.add(rec);
break;
}
}
}
} catch (IOException x) {
continue;
} catch (InterruptedException x) {
continue;
}
if (!recs.isEmpty()) {
result.put(c, recs);
}
}
return result;
}
/**
* Thread-safe reusable {@link XStream}.
*/
@@ -257,6 +361,5 @@ public class LogRecorder extends AbstractModelObject implements Saveable {
* Log levels that can be configured for {@link Target}.
*/
public static List<Level> LEVELS =
Arrays.asList(Level.SEVERE, Level.WARNING, Level.INFO, Level.CONFIG,
Level.FINE, Level.FINER, Level.FINEST, Level.ALL);
Arrays.asList(Level.ALL, Level.FINEST, Level.FINER, Level.FINE, Level.CONFIG, Level.INFO, Level.WARNING, Level.SEVERE);
}
@@ -32,6 +32,9 @@ import jenkins.model.Jenkins;
import hudson.model.RSS;
import hudson.util.CopyOnWriteMap;
import jenkins.model.JenkinsLocationConfiguration;
import jenkins.model.ModelObjectWithChildren;
import jenkins.model.ModelObjectWithContextMenu;
import jenkins.model.ModelObjectWithContextMenu.ContextMenu;
import org.apache.commons.io.filefilter.WildcardFileFilter;
import org.kohsuke.stapler.QueryParameter;
import org.kohsuke.stapler.StaplerRequest;
@@ -49,6 +52,7 @@ import java.util.GregorianCalendar;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Map.Entry;
import java.util.logging.Level;
import java.util.logging.LogRecord;
import java.util.logging.Logger;
@@ -58,9 +62,9 @@ import java.util.logging.Logger;
*
* @author Kohsuke Kawaguchi
*/
public class LogRecorderManager extends AbstractModelObject {
public class LogRecorderManager extends AbstractModelObject implements ModelObjectWithChildren {
/**
* {@link LogRecorder}s.
* {@link LogRecorder}s keyed by their {@linkplain LogRecorder#name name}.
*/
public transient final Map<String,LogRecorder> logRecorders = new CopyOnWriteMap.Tree<String,LogRecorder>();
@@ -109,6 +113,15 @@ public class LogRecorderManager extends AbstractModelObject {
return new HttpRedirect(name+"/configure");
}
public ContextMenu doChildrenContextMenu(StaplerRequest request, StaplerResponse response) throws Exception {
ContextMenu menu = new ContextMenu();
menu.add("all","All Jenkins Logs");
for (LogRecorder lr : logRecorders.values()) {
menu.add(lr.getSearchUrl(), lr.getDisplayName());
}
return menu;
}
/**
* Configure the logging level.
*/
@@ -66,7 +66,7 @@ public class MyspacePolicy {
tag("span,div");
tag("img", "src",ONSITE_OR_OFFSITE_URL,
"hspace","vspace");
tag("link", "type","rel");
tag("iframe", "src");
tag("ul,ol,li,dd,dl,dt,thead,tbody,tfoot");
tag("table", "noresize");
tag("td,th,tr");
+3 -2
View File
@@ -76,10 +76,11 @@ public class Axis extends AbstractDescribableImpl<Axis> implements Comparable<Ax
public final List<String> values;
public Axis(String name, List<String> values) {
if (values == null || values.isEmpty()) {
values = Collections.emptyList();
}
this.name = name;
this.values = new ArrayList<String>(values);
if(values.isEmpty())
throw new IllegalArgumentException(); // bug in the code
}
public Axis(String name, String... values) {
@@ -88,7 +88,7 @@ public class AxisList extends ArrayList<Axis> {
}
/**
* {@link Converter} implementation for XStream.
* {@link com.thoughtworks.xstream.converters.Converter} implementation for XStream.
*/
public static final class ConverterImpl extends RobustCollectionConverter {
public ConverterImpl(XStream xs) {
@@ -111,10 +111,18 @@ public final class Combination extends TreeMap<String,String> implements Compara
* true.
*/
public boolean evalGroovyExpression(AxisList axes, String expression) {
return evalGroovyExpression(axes, expression, new Binding());
}
/**
* @see #evalGroovyExpression(AxisList, String)
* @since 1.515
*/
public boolean evalGroovyExpression(AxisList axes, String expression, Binding binding) {
if(Util.fixEmptyAndTrim(expression)==null)
return true;
Binding binding = new Binding();
for (Map.Entry<String, String> e : entrySet())
binding.setVariable(e.getKey(),e.getValue());
@@ -1,5 +1,7 @@
package hudson.matrix;
import groovy.lang.Binding;
import groovy.lang.GroovyRuntimeException;
import hudson.AbortException;
import hudson.Extension;
import hudson.Util;
@@ -8,6 +10,7 @@ import hudson.matrix.MatrixBuild.MatrixBuildExecution;
import hudson.matrix.listeners.MatrixBuildListener;
import hudson.model.BuildListener;
import hudson.model.Cause.UpstreamCause;
import hudson.model.ParameterValue;
import hudson.model.ParametersAction;
import hudson.model.Queue;
import hudson.model.ResourceController;
@@ -113,27 +116,21 @@ public class DefaultMatrixExecutionStrategyImpl extends MatrixExecutionStrategy
@Override
public Result run(MatrixBuildExecution execution) throws InterruptedException, IOException {
MatrixBuild build = execution.getBuild();
MatrixProject p = execution.getProject();
PrintStream logger = execution.getListener().getLogger();
Collection<MatrixConfiguration> touchStoneConfigurations = new HashSet<MatrixConfiguration>();
Collection<MatrixConfiguration> delayedConfigurations = new HashSet<MatrixConfiguration>();
for (MatrixConfiguration c: execution.getActiveConfigurations()) {
if (!MatrixBuildListener.buildConfiguration(build, c))
continue; // skip rebuild
if (touchStoneCombinationFilter != null && c.getCombination().evalGroovyExpression(p.getAxes(), getTouchStoneCombinationFilter())) {
touchStoneConfigurations.add(c);
} else {
delayedConfigurations.add(c);
}
}
filterConfigurations(
execution,
touchStoneConfigurations,
delayedConfigurations
);
if (notifyStartBuild(execution.getAggregators())) return Result.FAILURE;
if (sorter != null) {
touchStoneConfigurations = createTreeSet(touchStoneConfigurations, sorter);
delayedConfigurations = createTreeSet(delayedConfigurations,sorter);
delayedConfigurations = createTreeSet(delayedConfigurations, sorter);
}
if(!runSequentially)
@@ -148,7 +145,9 @@ public class DefaultMatrixExecutionStrategyImpl extends MatrixExecutionStrategy
notifyEndBuild(run,execution.getAggregators());
r = r.combine(getResult(run));
}
PrintStream logger = execution.getListener().getLogger();
if (touchStoneResultCondition != null && r.isWorseThan(touchStoneResultCondition)) {
logger.printf("Touchstone configurations resulted in %s, so aborting...%n", r);
return r;
@@ -170,6 +169,71 @@ public class DefaultMatrixExecutionStrategyImpl extends MatrixExecutionStrategy
return r;
}
private void filterConfigurations(
final MatrixBuildExecution execution,
final Collection<MatrixConfiguration> touchStoneConfigurations,
final Collection<MatrixConfiguration> delayedConfigurations
) throws AbortException {
final MatrixBuild build = execution.getBuild();
final String combinationFilter = execution.getProject().getCombinationFilter();
final String touchStoneFilter = getTouchStoneCombinationFilter();
try {
for (MatrixConfiguration c: execution.getActiveConfigurations()) {
if (!MatrixBuildListener.buildConfiguration(build, c)) continue; // skip rebuild
final Combination combination = c.getCombination();
if (touchStoneFilter != null && satisfies(execution, combination, touchStoneFilter)) {
touchStoneConfigurations.add(c);
} else if (satisfies(execution, combination, combinationFilter)) {
delayedConfigurations.add(c);
}
}
} catch (GroovyRuntimeException ex) {
PrintStream logger = execution.getListener().getLogger();
logger.println(ex.getMessage());
ex.printStackTrace(logger);
throw new AbortException("Failed executing combination filter");
}
}
private boolean satisfies(
final MatrixBuildExecution execution,
final Combination combination,
final String filter
) {
return combination.evalGroovyExpression(
execution.getProject().getAxes(),
filter,
getConfiguredBinding(execution)
);
}
private Binding getConfiguredBinding(final MatrixBuildExecution execution) {
final Binding binding = new Binding();
final ParametersAction parameters = execution.getBuild().getAction(ParametersAction.class);
if (parameters == null) return binding;
for (final ParameterValue pv: parameters) {
if (pv == null) continue;
final String name = pv.getName();
final String value = pv.createVariableResolver(null).resolve(name);;
binding.setVariable(name, value);
}
return binding;
}
private Result getResult(@Nullable MatrixRun run) {
// null indicates that the run was cancelled before it even gets going
return run!=null ? run.getResult() : Result.ABORTED;
@@ -250,7 +314,7 @@ public class DefaultMatrixExecutionStrategyImpl extends MatrixExecutionStrategy
if(qi!=null) {
// if the build seems to be stuck in the queue, display why
String why = qi.getWhy();
if(!why.equals(whyInQueue) && System.currentTimeMillis()-startTime>5000) {
if(why != null && !why.equals(whyInQueue) && System.currentTimeMillis()-startTime>5000) {
listener.getLogger().print("Configuration " + ModelHyperlinkNote.encodeTo(c)+" is still in the queue: ");
qi.getCauseOfBlockage().print(listener); //this is still shown on the same line
whyInQueue = why;
@@ -201,8 +201,11 @@ public abstract class Layouter<T> {
buildMap(xp,x);
buildMap(yp,y);
buildMap(zp,z);
for (Axis a : trivial)
m.put(a.name,a.value(0));
for (Axis a : trivial) {
if (a.size() > 0) {
m.put(a.name, a.value(0));
}
}
return getT(new Combination(m));
}
@@ -74,7 +74,7 @@ public abstract class MatrixAggregator implements ExtensionPoint {
* @return
* true if the build can continue, false if there was an error
* and the build needs to be aborted.
* @see BuildStep#prebuild(Build,BuildListener)
* @see BuildStep#prebuild(AbstractBuild,BuildListener)
*/
public boolean startBuild() throws InterruptedException, IOException {
return true;
@@ -247,7 +247,7 @@ public class MatrixBuild extends AbstractBuild<MatrixProject,MatrixBuild> {
public String getWhyKeepLog() {
MatrixBuild b = getNextBuild();
if (isLinkedBy(b))
return b.getDisplayName()+" depends on this";
return Messages.MatrixBuild_depends_on_this(b.getDisplayName());
return super.getWhyKeepLog();
}
@@ -368,7 +368,8 @@ public class MatrixBuild extends AbstractBuild<MatrixProject,MatrixBuild> {
final int n = getNumber();
for (MatrixConfiguration c : activeConfigurations) {
for (Item i : q.getItems(c)) {
if (i.getAction(ParentBuildAction.class).parent==getBuild()) {
ParentBuildAction a = i.getAction(ParentBuildAction.class);
if (a!=null && a.parent==getBuild()) {
q.cancel(i);
logger.println(Messages.MatrixBuild_Cancelled(ModelHyperlinkNote.encodeTo(c)));
}
@@ -232,10 +232,17 @@ public class MatrixConfiguration extends Project<MatrixConfiguration,MatrixRun>
MatrixBuild lb = getParent().getLastBuild();
for (Action a : actions) {
if (a instanceof ParentBuildAction) {
lb = ((ParentBuildAction) a).parent;
MatrixBuild _lb = ((ParentBuildAction) a).parent;
if (_lb != null) {
lb = _lb;
}
}
}
if (lb == null) {
throw new IOException("cannot start a build of " + getFullName() + " since its parent has no builds at all");
}
// for every MatrixRun there should be a parent MatrixBuild
MatrixRun lastBuild = new MatrixRun(this, lb.getTimestamp());
@@ -390,17 +397,16 @@ public class MatrixConfiguration extends Project<MatrixConfiguration,MatrixRun>
* @param parameters
* Can be null.
* @deprecated
* Use {@link #scheduleBuild(List<? extends Action>, Cause)}. Since 1.480
* Use {@link #scheduleBuild(List, Cause)}. Since 1.480
*/
public boolean scheduleBuild(ParametersAction parameters, Cause c) {
return scheduleBuild(Collections.singletonList(parameters), c);
}
/**
* Starts the build with the actions that are passed in.
*
* @param actions Can be null.
* @param cause Reason for starting the build
* @param c Reason for starting the build
*/
public boolean scheduleBuild(List<? extends Action> actions, Cause c) {
List<Action> allActions = new ArrayList<Action>();
@@ -411,7 +417,7 @@ public class MatrixConfiguration extends Project<MatrixConfiguration,MatrixRun>
allActions.add(new ParentBuildAction());
allActions.add(new CauseAction(c));
return Jenkins.getInstance().getQueue().schedule(this, getQuietPeriod(), allActions )!=null;
return Jenkins.getInstance().getQueue().schedule2(this, getQuietPeriod(), allActions ).isAccepted();
}
/**
@@ -38,7 +38,7 @@ import java.util.List;
* some fails, etc.
*
* @author Kohsuke Kawaguchi
* @sicne 1.456
* @since 1.456
*/
public abstract class MatrixExecutionStrategy extends AbstractDescribableImpl<MatrixExecutionStrategy> implements ExtensionPoint {
public Result run(MatrixBuildExecution execution) throws InterruptedException, IOException {
@@ -47,7 +47,7 @@ public abstract class MatrixExecutionStrategy extends AbstractDescribableImpl<Ma
/**
* @deprecated
* Override {@link #run(MatrixBuildExecution)}
* Override {@link #run(MatrixBuild.MatrixBuildExecution)}
*/
public Result run(MatrixBuild build, List<MatrixAggregator> aggregators, BuildListener listener) throws InterruptedException, IOException {
throw new UnsupportedOperationException(getClass()+" needs to override run(MatrixBuildExecution)");
@@ -41,6 +41,7 @@ import hudson.model.Descriptor;
import hudson.model.Descriptor.FormException;
import hudson.model.Node;
import hudson.slaves.WorkspaceList;
import hudson.util.AlternativeUiTextProvider;
import jenkins.model.Jenkins;
import hudson.model.Item;
import hudson.model.ItemGroup;
@@ -48,6 +49,8 @@ import hudson.model.Items;
import hudson.model.JDK;
import hudson.model.Job;
import hudson.model.Label;
import hudson.model.ParameterDefinition;
import hudson.model.ParametersDefinitionProperty;
import hudson.model.Queue.FlyweightTask;
import hudson.model.Result;
import hudson.model.SCMedItem;
@@ -65,6 +68,8 @@ import hudson.util.FormValidation;
import hudson.util.FormValidation.Kind;
import jenkins.scm.SCMCheckoutStrategyDescriptor;
import net.sf.json.JSONObject;
import org.kohsuke.accmod.Restricted;
import org.kohsuke.accmod.restrictions.NoExternalUse;
import org.kohsuke.stapler.HttpResponse;
import org.kohsuke.stapler.StaplerRequest;
import org.kohsuke.stapler.StaplerResponse;
@@ -88,6 +93,8 @@ import java.util.Map.Entry;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* {@link Job} that allows you to run multiple different configurations
@@ -189,6 +196,11 @@ public class MatrixProject extends AbstractProject<MatrixProject,MatrixBuild> im
super(parent, name);
}
@Override
public String getPronoun() {
return AlternativeUiTextProvider.get(PRONOUN, this, Messages.MatrixProject_Pronoun());
}
/**
* Gets the workspace location that {@link MatrixConfiguration} uses.
*
@@ -418,7 +430,7 @@ public class MatrixProject extends AbstractProject<MatrixProject,MatrixBuild> im
r.addAll(step.getProjectActions(this));
for (BuildWrapper step : buildWrappers)
r.addAll(step.getProjectActions(this));
for (Trigger<?> trigger : triggers)
for (Trigger<?> trigger : triggers())
r.addAll(trigger.getProjectActions());
return r;
@@ -602,9 +614,11 @@ public class MatrixProject extends AbstractProject<MatrixProject,MatrixBuild> im
}
// find all active configurations
Set<MatrixConfiguration> active = new LinkedHashSet<MatrixConfiguration>();
final Set<MatrixConfiguration> active = new LinkedHashSet<MatrixConfiguration>();
final boolean isDynamicFilter = isDynamicFilter(getCombinationFilter());
for (Combination c : activeCombinations) {
if(c.evalGroovyExpression(axes,combinationFilter)) {
if(isDynamicFilter || c.evalGroovyExpression(axes,getCombinationFilter())) {
LOGGER.fine("Adding configuration: " + c);
MatrixConfiguration config = configurations.get(c);
if(config==null) {
@@ -621,6 +635,27 @@ public class MatrixProject extends AbstractProject<MatrixProject,MatrixBuild> im
return active;
}
private boolean isDynamicFilter(final String filter) {
if (!isParameterized() || filter == null) return false;
final ParametersDefinitionProperty paramDefProp = getProperty(ParametersDefinitionProperty.class);
for (final ParameterDefinition definition : paramDefProp.getParameterDefinitions()) {
final String name = definition.getName();
final Matcher matcher = Pattern
.compile("\\b" + name + "\\b")
.matcher(filter)
;
if (matcher.find()) return true;
}
return false;
}
private File getConfigurationsDir() {
return new File(getRootDir(),"configurations");
}
@@ -853,11 +888,24 @@ public class MatrixProject extends AbstractProject<MatrixProject,MatrixBuild> im
return rsp;
}
public DescriptorImpl getDescriptor() {
return DESCRIPTOR;
@Override
public ContextMenu doChildrenContextMenu(StaplerRequest request, StaplerResponse response) throws Exception {
ContextMenu menu = new ContextMenu();
for (MatrixConfiguration c : getActiveConfigurations()) {
menu.add(c);
}
return menu;
}
public DescriptorImpl getDescriptor() {
return (DescriptorImpl)Jenkins.getInstance().getDescriptorOrDie(getClass());
}
/**
* Descriptor is instantiated as a field purely for backward compatibility.
* Do not do this in your code. Put @Extension on your DescriptorImpl class instead.
*/
@Restricted(NoExternalUse.class)
@Extension
public static final DescriptorImpl DESCRIPTOR = new DescriptorImpl();
@@ -41,7 +41,7 @@ import java.util.List;
* <p>
* Plugins can implement this extension point to filter out the subset of matrix project to build.
* Most typically, such a plugin would add a custom {@link Action} to a build when it goes to the queue
* ({@link Queue#schedule(Task, int, List)}, then access this from {@link MatrixBuild} to drive
* ({@link Queue#schedule2(Task, int, List)}, then access this from {@link MatrixBuild} to drive
* the filtering logic.
*
* <p>
@@ -37,7 +37,6 @@ import hudson.console.ModelHyperlinkNote;
import hudson.matrix.MatrixConfiguration;
import hudson.model.Fingerprint.BuildPtr;
import hudson.model.Fingerprint.RangeSet;
import hudson.model.PermalinkProjectAction.Permalink;
import hudson.model.listeners.RunListener;
import hudson.model.listeners.SCMListener;
import hudson.scm.ChangeLogParser;
@@ -59,7 +58,6 @@ import hudson.tasks.test.AbstractTestResultAction;
import hudson.tasks.test.AggregatedTestResultAction;
import hudson.util.*;
import jenkins.model.Jenkins;
import jenkins.model.PeepholePermalink;
import jenkins.model.lazy.AbstractLazyLoadRunMap.Direction;
import jenkins.model.lazy.BuildReference;
import org.kohsuke.stapler.HttpResponse;
@@ -92,6 +90,8 @@ import java.util.logging.Logger;
import javax.annotation.CheckForNull;
import org.kohsuke.stapler.interceptor.RequirePOST;
import static java.util.logging.Level.WARNING;
/**
* Base implementation of {@link Run}s that build software.
*
@@ -151,7 +151,7 @@ public abstract class AbstractBuild<P extends AbstractProject<P,R>,R extends Abs
private volatile Set<String> culprits;
/**
* During the build this field remembers {@link BuildWrapper.Environment}s created by
* During the build this field remembers {@link hudson.tasks.BuildWrapper.Environment}s created by
* {@link BuildWrapper}. This design is bit ugly but forced due to compatibility.
*/
protected transient List<Environment> buildEnvironments;
@@ -213,7 +213,11 @@ public abstract class AbstractBuild<P extends AbstractProject<P,R>,R extends Abs
if (r==null) {
// having two neighbors pointing to each other is important to make RunMap.removeValue work
R pb = getParent().builds.search(number-1, Direction.DESC);
P _parent = getParent();
if (_parent == null) {
throw new IllegalStateException("no parent for " + number + " in " + workspace);
}
R pb = _parent._getRuns().search(number-1, Direction.DESC);
if (pb!=null) {
((AbstractBuild)pb).nextBuild = selfReference; // establish bi-di link
this.previousBuild = pb.selfReference;
@@ -354,7 +358,7 @@ public abstract class AbstractBuild<P extends AbstractProject<P,R>,R extends Abs
}
/**
* Normally, a workspace is assigned by {@link RunExecution}, but this lets you set the workspace in case
* Normally, a workspace is assigned by {@link hudson.model.Run.RunExecution}, but this lets you set the workspace in case
* {@link AbstractBuild} is created without a build.
*/
protected void setWorkspace(FilePath ws) {
@@ -471,26 +475,9 @@ public abstract class AbstractBuild<P extends AbstractProject<P,R>,R extends Abs
return hudsonVersion;
}
/**
* Backward compatibility.
*
* We used to have $JENKINS_HOME/jobs/JOBNAME/lastStable and lastSuccessful symlinked to the appropriate
* builds, but now those are done in {@link PeepholePermalink}. So here, we simply create symlinks that
* resolves to the symlink created by {@link PeepholePermalink}.
*/
private void createSymlink(TaskListener listener, String name, Permalink target) throws InterruptedException {
String targetDir;
if (getProject().getBuildDir().equals(new File(getProject().getRootDir(), "builds"))) {
targetDir = "builds/" + target.getId();
} else {
targetDir = getProject().getBuildDir()+"/"+target.getId();
}
Util.createSymlink(getProject().getRootDir(), targetDir, name, listener);
}
/**
* @deprecated as of 1.467
* Please use {@link RunExecution}
* Please use {@link hudson.model.Run.RunExecution}
*/
public abstract class AbstractRunner extends AbstractBuildExecution {
@@ -718,9 +705,6 @@ public abstract class AbstractBuild<P extends AbstractProject<P,R>,R extends Abs
public final void post(BuildListener listener) throws Exception {
try {
post2(listener);
createSymlink(listener, "lastSuccessful", Permalink.LAST_SUCCESSFUL_BUILD);
createSymlink(listener, "lastStable", Permalink.LAST_STABLE_BUILD);
} finally {
// update the culprit list
HashSet<String> r = new HashSet<String>();
@@ -774,7 +758,7 @@ public abstract class AbstractBuild<P extends AbstractProject<P,R>,R extends Abs
} catch (Exception e) {
String msg = "Publisher " + bs.getClass().getName() + " aborted due to exception";
e.printStackTrace(listener.error(msg));
LOGGER.log(Level.WARNING, msg, e);
LOGGER.log(WARNING, msg, e);
setResult(Result.FAILURE);
}
}
@@ -915,9 +899,9 @@ public abstract class AbstractBuild<P extends AbstractProject<P,R>,R extends Abs
try {
return scm.parse(this,changelogFile);
} catch (IOException e) {
e.printStackTrace();
LOGGER.log(WARNING, "Failed to parse "+changelogFile,e);
} catch (SAXException e) {
e.printStackTrace();
LOGGER.log(WARNING, "Failed to parse "+changelogFile,e);
}
return ChangeLogSet.createEmpty(this);
}
@@ -30,6 +30,7 @@ package hudson.model;
import hudson.security.AccessControlled;
import hudson.slaves.ComputerListener;
import hudson.slaves.RetentionStrategy;
import jenkins.model.Jenkins;
import org.kohsuke.stapler.StaplerFallback;
import org.kohsuke.stapler.StaplerProxy;
@@ -116,7 +117,8 @@ public abstract class AbstractCIBase extends Node implements ItemGroup<TopLevelI
if (c!=null) {
c.setNode(n); // reuse
} else {
if(n.getNumExecutors()>0) {
// we always need Computer for the master as a fallback in case there's no other Computer.
if(n.getNumExecutors()>0 || n==Jenkins.getInstance()) {
computers.put(n, c = n.createComputer());
if (!n.isHoldOffLaunchUntilSave() && automaticSlaveLaunch) {
RetentionStrategy retentionStrategy = c.getRetentionStrategy();
@@ -138,6 +140,7 @@ public abstract class AbstractCIBase extends Node implements ItemGroup<TopLevelI
for (Map.Entry<Node, Computer> e : computers.entrySet()) {
if (e.getValue() == computer) {
computers.remove(e.getKey());
computer.onRemoved();
return;
}
}
@@ -52,8 +52,6 @@ import org.kohsuke.stapler.export.ExportedBean;
import java.io.File;
import java.io.IOException;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import org.kohsuke.stapler.StaplerRequest;
import org.kohsuke.stapler.StaplerResponse;
@@ -185,6 +183,7 @@ public abstract class AbstractItem extends Actionable implements Item, HttpDelet
public void setDescription(String description) throws IOException {
this.description = description;
save();
ItemListener.fireOnUpdated(this);
}
/**
@@ -326,39 +325,37 @@ public abstract class AbstractItem extends Actionable implements Item, HttpDelet
if(n.length()==0) return getDisplayName();
else return n+" \u00BB "+getDisplayName();
}
/**
* Gets the display name of the current item relative to the given group.
*
* @since 1.515
* @param p the ItemGroup used as point of reference for the item
* @return
* String like "foo » bar"
*/
public String getRelativeDisplayNameFrom(ItemGroup p) {
return Functions.getRelativeDisplayNameFrom(this, p);
}
/**
* This method only exists to disambiguate {@link #getRelativeNameFrom(ItemGroup)} and {@link #getRelativeNameFrom(Item)}
* @since 1.512
* @see #getRelativeNameFrom(ItemGroup)
*/
public String getRelativeNameFromGroup(ItemGroup p) {
return getRelativeNameFrom(p);
}
/**
* @param p
* The ItemGroup instance used as context to evaluate the relative name of this AbstractItem
* @return
* The name of the current item, relative to p.
* Nested ItemGroups are separated by / character.
*/
public String getRelativeNameFrom(ItemGroup p) {
// first list up all the parents
Map<ItemGroup,Integer> parents = new HashMap<ItemGroup,Integer>();
int depth=0;
while (p!=null) {
parents.put(p, depth++);
if (p instanceof Item)
p = ((Item)p).getParent();
else
p = null;
}
StringBuilder buf = new StringBuilder();
Item i=this;
while (true) {
if (buf.length()>0) buf.insert(0,'/');
buf.insert(0,i.getName());
ItemGroup g = i.getParent();
Integer d = parents.get(g);
if (d!=null) {
String s="";
for (int j=d; j>0; j--)
s+="../";
return s+buf;
}
if (g instanceof Item)
i = (Item)g;
else
return null;
}
return Functions.getRelativeNameFrom(this, p);
}
public String getRelativeNameFrom(Item item) {
@@ -405,7 +402,9 @@ public abstract class AbstractItem extends Actionable implements Item, HttpDelet
}
public String getShortUrl() {
return getParent().getUrlChildPrefix()+'/'+Util.rawEncode(getName())+'/';
String prefix = getParent().getUrlChildPrefix();
String subdir = Util.rawEncode(getName());
return prefix.equals(".") ? subdir + '/' : prefix + '/' + subdir + '/';
}
public String getSearchUrl() {
@@ -477,6 +476,9 @@ public abstract class AbstractItem extends Actionable implements Item, HttpDelet
/**
* Deletes this item.
* Note on the funny name: for reasons of historical compatibility, this URL is {@code /doDelete}
* since it predates {@code <l:confirmationLink>}. {@code /delete} goes to a Jelly page
* which should now be unused by core but is left in case plugins are still using it.
*/
@CLIMethod(name="delete-job")
@RequirePOST
@@ -512,7 +514,7 @@ public abstract class AbstractItem extends Actionable implements Item, HttpDelet
// ignore
}
Jenkins.getInstance().rebuildDependencyGraph();
Jenkins.getInstance().rebuildDependencyGraphAsync();
}
/**
@@ -591,7 +593,7 @@ public abstract class AbstractItem extends Actionable implements Item, HttpDelet
} finally {
Items.updatingByXml.set(false);
}
Jenkins.getInstance().rebuildDependencyGraph();
Jenkins.getInstance().rebuildDependencyGraphAsync();
// if everything went well, commit this new version
out.commit();
@@ -44,17 +44,20 @@ import hudson.model.Cause.RemoteCause;
import hudson.model.Cause.UserIdCause;
import hudson.model.Descriptor.FormException;
import hudson.model.Fingerprint.RangeSet;
import hudson.model.PermalinkProjectAction.Permalink;
import hudson.model.Queue.Executable;
import hudson.model.Queue.Task;
import hudson.model.queue.QueueTaskFuture;
import hudson.model.queue.ScheduleResult;
import hudson.model.queue.SubTask;
import hudson.model.Queue.WaitingItem;
import hudson.model.RunMap.Constructor;
import hudson.model.labels.LabelAtom;
import hudson.model.labels.LabelExpression;
import hudson.model.listeners.ItemListener;
import hudson.model.listeners.SCMPollListener;
import hudson.model.queue.CauseOfBlockage;
import hudson.model.queue.SubTaskContributor;
import hudson.node_monitors.DiskSpaceMonitor;
import hudson.scm.ChangeLogSet;
import hudson.scm.ChangeLogSet.Entry;
import hudson.scm.NullSCM;
@@ -83,18 +86,22 @@ import hudson.widgets.BuildHistoryWidget;
import hudson.widgets.HistoryWidget;
import jenkins.model.Jenkins;
import jenkins.model.JenkinsLocationConfiguration;
import jenkins.model.ModelObjectWithChildren;
import jenkins.model.lazy.AbstractLazyLoadRunMap.Direction;
import jenkins.scm.DefaultSCMCheckoutStrategyImpl;
import jenkins.scm.SCMCheckoutStrategy;
import jenkins.scm.SCMCheckoutStrategyDescriptor;
import jenkins.util.TimeDuration;
import net.sf.json.JSONObject;
import org.acegisecurity.Authentication;
import org.acegisecurity.context.SecurityContext;
import org.acegisecurity.context.SecurityContextHolder;
import org.jenkinsci.bytecode.AdaptField;
import org.kohsuke.accmod.Restricted;
import org.kohsuke.accmod.restrictions.NoExternalUse;
import org.kohsuke.args4j.Argument;
import org.kohsuke.args4j.CmdLineException;
import org.kohsuke.stapler.Ancestor;
import org.kohsuke.stapler.ForwardToView;
import org.kohsuke.stapler.HttpRedirect;
import org.kohsuke.stapler.HttpResponse;
@@ -105,6 +112,7 @@ import org.kohsuke.stapler.StaplerResponse;
import org.kohsuke.stapler.export.Exported;
import org.kohsuke.stapler.interceptor.RequirePOST;
import javax.annotation.Nonnull;
import javax.servlet.ServletException;
import java.io.File;
import java.io.IOException;
@@ -116,17 +124,21 @@ import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import java.util.Set;
import java.util.SortedMap;
import java.util.TreeMap;
import java.util.Vector;
import java.util.concurrent.Future;
import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
import java.util.logging.Level;
import java.util.logging.Logger;
import static hudson.scm.PollingResult.*;
import javax.annotation.CheckForNull;
import static javax.servlet.http.HttpServletResponse.*;
/**
@@ -138,7 +150,7 @@ import static javax.servlet.http.HttpServletResponse.*;
* @see AbstractBuild
*/
@SuppressWarnings("rawtypes")
public abstract class AbstractProject<P extends AbstractProject<P,R>,R extends AbstractBuild<P,R>> extends Job<P,R> implements BuildableItem {
public abstract class AbstractProject<P extends AbstractProject<P,R>,R extends AbstractBuild<P,R>> extends Job<P,R> implements BuildableItem, ModelObjectWithChildren {
/**
* {@link SCM} associated with the project.
@@ -232,7 +244,10 @@ public abstract class AbstractProject<P extends AbstractProject<P,R>,R extends A
/**
* List of all {@link Trigger}s for this project.
*/
protected List<Trigger<?>> triggers = new Vector<Trigger<?>>();
@AdaptField(was=List.class)
protected volatile DescribableList<Trigger<?>,TriggerDescriptor> triggers = new DescribableList<Trigger<?>,TriggerDescriptor>(this);
private static final AtomicReferenceFieldUpdater<AbstractProject,DescribableList> triggersUpdater
= AtomicReferenceFieldUpdater.newUpdater(AbstractProject.class,DescribableList.class,"triggers");
/**
* {@link Action}s contributed from subsidiary objects associated with
@@ -256,7 +271,7 @@ public abstract class AbstractProject<P extends AbstractProject<P,R>,R extends A
protected AbstractProject(ItemGroup parent, String name) {
super(parent,name);
if(!Jenkins.getInstance().getNodes().isEmpty()) {
if(Jenkins.getInstance()!=null && !Jenkins.getInstance().getNodes().isEmpty()) {
// if a new job is configured with Hudson that already has slave nodes
// make it roamable by default
canRoam = true;
@@ -285,10 +300,16 @@ public abstract class AbstractProject<P extends AbstractProject<P,R>,R extends A
RunMap<R> currentBuilds = this.builds;
if (currentBuilds==null) {
if (currentBuilds==null && parent!=null) {
// are we overwriting what currently exist?
// this is primarily when Jenkins is getting reloaded
Item current = parent.getItem(name);
Item current;
try {
current = parent.getItem(name);
} catch (RuntimeException x) {
LOGGER.log(Level.WARNING, "failed to look up " + name + " in " + parent, x);
current = null;
}
if (current!=null && current.getClass()==getClass()) {
currentBuilds = ((AbstractProject)current).builds;
}
@@ -301,6 +322,7 @@ public abstract class AbstractProject<P extends AbstractProject<P,R>,R extends A
}
}
this.builds = builds;
triggers().setOwner(this);
for (Trigger t : triggers())
t.start(this, Items.updatingByXml.get());
if(scm==null)
@@ -319,9 +341,10 @@ public abstract class AbstractProject<P extends AbstractProject<P,R>,R extends A
});
}
private synchronized List<Trigger<?>> triggers() {
@WithBridgeMethods(List.class)
protected DescribableList<Trigger<?>,TriggerDescriptor> triggers() {
if (triggers == null) {
triggers = new Vector<Trigger<?>>();
triggersUpdater.compareAndSet(this,null,new DescribableList<Trigger<?>,TriggerDescriptor>(this));
}
return triggers;
}
@@ -448,7 +471,7 @@ public abstract class AbstractProject<P extends AbstractProject<P,R>,R extends A
* @since 1.401
*/
public String getBuildNowText() {
return AlternativeUiTextProvider.get(BUILD_NOW_TEXT,this,Messages.AbstractProject_BuildNow());
return AlternativeUiTextProvider.get(BUILD_NOW_TEXT, this, isParameterized() ? Messages.AbstractProject_build_with_parameters() : Messages.AbstractProject_BuildNow());
}
/**
@@ -550,6 +573,15 @@ public abstract class AbstractProject<P extends AbstractProject<P,R>,R extends A
}
return null;
}
private R getSomeBuildWithExistingWorkspace() throws IOException, InterruptedException {
int cnt=0;
for (R b = getLastBuild(); cnt<5 && b!=null; b=b.getPreviousBuild()) {
FilePath ws = b.getWorkspace();
if (ws!=null && ws.exists()) return b;
}
return null;
}
/**
* Returns the root directory of the checked-out module.
@@ -671,7 +703,9 @@ public abstract class AbstractProject<P extends AbstractProject<P,R>,R extends A
this.disabled = b;
if(b)
Jenkins.getInstance().getQueue().cancel(this);
save();
ItemListener.fireOnUpdated(this);
}
/**
@@ -759,16 +793,13 @@ public abstract class AbstractProject<P extends AbstractProject<P,R>,R extends A
upstream = new HashSet<AbstractProject>(Items.fromNameList(getParent(),req.getParameter("upstreamProjects"),AbstractProject.class));
}
// dependency setting might have been changed by the user, so rebuild.
Jenkins.getInstance().rebuildDependencyGraph();
convertUpstreamBuildTrigger(upstream);
// notify the queue as the project might be now tied to different node
Jenkins.getInstance().getQueue().scheduleMaintenance();
// this is to reflect the upstream build adjustments done above
Jenkins.getInstance().rebuildDependencyGraph();
Jenkins.getInstance().rebuildDependencyGraphAsync();
}
/**
@@ -851,9 +882,8 @@ public abstract class AbstractProject<P extends AbstractProject<P,R>,R extends A
* Schedules a build of this project.
*
* @return
* true if the project is actually added to the queue.
* false if the queue contained it and therefore the add()
* was noop
* true if the project is added to the queue.
* false if the task was rejected from the queue (such as when the system is being shut down.)
*/
public boolean scheduleBuild(Cause c) {
return scheduleBuild(getQuietPeriod(), c);
@@ -914,9 +944,9 @@ public abstract class AbstractProject<P extends AbstractProject<P,R>,R extends A
queueActions.add(new CauseAction(c));
}
WaitingItem i = Jenkins.getInstance().getQueue().schedule(this, quietPeriod, queueActions);
if(i!=null)
return (QueueTaskFuture)i.getFuture();
ScheduleResult i = Jenkins.getInstance().getQueue().schedule2(this, quietPeriod, queueActions);
if(i.isAccepted())
return (QueueTaskFuture)i.getItem().getFuture();
return null;
}
@@ -1010,13 +1040,18 @@ public abstract class AbstractProject<P extends AbstractProject<P,R>,R extends A
@Override
public RunMap<R> _getRuns() {
if (builds == null) {
throw new IllegalStateException("no run map created yet for " + this);
}
assert builds.baseDirInitialized() : "neither onCreatedFromScratch nor onLoad called on " + this + " yet";
return builds;
}
@Override
public void removeRun(R run) {
this.builds.remove(run);
if (!this.builds.remove(run)) {
LOGGER.log(Level.WARNING, "{0} did not contain {1} to begin with", new Object[] {this, run});
}
}
/**
@@ -1050,7 +1085,7 @@ public abstract class AbstractProject<P extends AbstractProject<P,R>,R extends A
}
@Override
public R getLastBuild() {
public @CheckForNull R getLastBuild() {
return builds.newestBuild();
}
@@ -1169,6 +1204,12 @@ public abstract class AbstractProject<P extends AbstractProject<P,R>,R extends A
return this;
}
@Nonnull
public Authentication getDefaultAuthentication() {
// backward compatible behaviour.
return ACL.SYSTEM;
}
/**
* {@inheritDoc}
*
@@ -1359,7 +1400,13 @@ public abstract class AbstractProject<P extends AbstractProject<P,R>,R extends A
return true; // no SCM
FilePath workspace = build.getWorkspace();
workspace.mkdirs();
try {
workspace.mkdirs();
} catch (IOException e) {
// Can't create workspace dir - Is slave disk full ?
new DiskSpaceMonitor().markNodeOfflineIfDiskspaceIsTooLow(build.getBuiltOn().toComputer());
throw e;
}
boolean r = scm.checkout(build, launcher, workspace, listener, changelogFile);
if (r) {
@@ -1440,7 +1487,7 @@ public abstract class AbstractProject<P extends AbstractProject<P,R>,R extends A
try {
SCMPollListener.fireBeforePolling(this, listener);
PollingResult r = _poll(listener, scm, lb);
PollingResult r = _poll(listener, scm);
SCMPollListener.firePollingSuccess(this,listener, r);
return r;
} catch (AbortException e) {
@@ -1469,18 +1516,20 @@ public abstract class AbstractProject<P extends AbstractProject<P,R>,R extends A
/**
* {@link #poll(TaskListener)} method without the try/catch block that does listener notification and .
*/
private PollingResult _poll(TaskListener listener, SCM scm, R lb) throws IOException, InterruptedException {
private PollingResult _poll(TaskListener listener, SCM scm) throws IOException, InterruptedException {
if (scm.requiresWorkspaceForPolling()) {
// lock the workspace of the last build
FilePath ws=lb.getWorkspace();
R b = getSomeBuildWithExistingWorkspace();
if (b == null) b = getLastBuild();
// lock the workspace for the given build
FilePath ws=b.getWorkspace();
WorkspaceOfflineReason workspaceOfflineReason = workspaceOffline( lb );
WorkspaceOfflineReason workspaceOfflineReason = workspaceOffline( b );
if ( workspaceOfflineReason != null ) {
// workspace offline
for (WorkspaceBrowser browser : Jenkins.getInstance().getExtensionList(WorkspaceBrowser.class)) {
ws = browser.getWorkspace(this);
if (ws != null) {
return pollWithWorkspace(listener, scm, lb, ws, browser.getWorkspaceList());
return pollWithWorkspace(listener, scm, b, ws, browser.getWorkspaceList());
}
}
@@ -1505,8 +1554,8 @@ public abstract class AbstractProject<P extends AbstractProject<P,R>,R extends A
return BUILD_NOW;
}
} else {
WorkspaceList l = lb.getBuiltOn().toComputer().getWorkspaceList();
return pollWithWorkspace(listener, scm, lb, ws, l);
WorkspaceList l = b.getBuiltOn().toComputer().getWorkspaceList();
return pollWithWorkspace(listener, scm, b, ws, l);
}
} else {
@@ -1514,7 +1563,7 @@ public abstract class AbstractProject<P extends AbstractProject<P,R>,R extends A
LOGGER.fine("Polling SCM changes of " + getName());
if (pollingBaseline==null) // see NOTE-NO-BASELINE above
calcPollingBaseline(lb,null,listener);
calcPollingBaseline(getLastBuild(),null,listener);
PollingResult r = scm.poll(this, null, null, listener, pollingBaseline);
pollingBaseline = r.remote;
return r;
@@ -1529,8 +1578,10 @@ public abstract class AbstractProject<P extends AbstractProject<P,R>,R extends A
// so better throughput is achieved over time (modulo the initial cost of creating that many workspaces)
// by having multiple workspaces
WorkspaceList.Lease lease = l.acquire(ws, !concurrentBuild);
Launcher launcher = ws.createLauncher(listener).decorateByEnv(getEnvironment(lb.getBuiltOn(),listener));
Node node = lb.getBuiltOn();
Launcher launcher = ws.createLauncher(listener).decorateByEnv(getEnvironment(node,listener));
try {
listener.getLogger().println("Polling SCM changes on " + node.getSelfLabel().getName());
LOGGER.fine("Polling SCM changes of " + getName());
if (pollingBaseline==null) // see NOTE-NO-BASELINE above
calcPollingBaseline(lb,launcher,listener);
@@ -1601,15 +1652,8 @@ public abstract class AbstractProject<P extends AbstractProject<P,R>,R extends A
protected final synchronized <T extends Describable<T>>
void addToList( T item, List<T> collection ) throws IOException {
for( int i=0; i<collection.size(); i++ ) {
if(collection.get(i).getDescriptor()==item.getDescriptor()) {
// replace
collection.set(i,item);
save();
return;
}
}
// add
//No support to replace item in position, remove then add
removeFromList(item.getDescriptor(), collection);
collection.add(item);
save();
updateTransientActions();
@@ -1617,10 +1661,12 @@ public abstract class AbstractProject<P extends AbstractProject<P,R>,R extends A
protected final synchronized <T extends Describable<T>>
void removeFromList(Descriptor<T> item, List<T> collection) throws IOException {
for( int i=0; i< collection.size(); i++ ) {
if(collection.get(i).getDescriptor()==item) {
final Iterator<T> iCollection = collection.iterator();
while(iCollection.hasNext()) {
final T next = iCollection.next();
if(next.getDescriptor()==item) {
// found it
collection.remove(i);
iCollection.remove();
save();
updateTransientActions();
return;
@@ -1629,8 +1675,8 @@ public abstract class AbstractProject<P extends AbstractProject<P,R>,R extends A
}
@SuppressWarnings("unchecked")
public synchronized Map<TriggerDescriptor,Trigger> getTriggers() {
return (Map)Descriptor.toMap(triggers());
public Map<TriggerDescriptor,Trigger<?>> getTriggers() {
return triggers().toMap();
}
/**
@@ -1793,8 +1839,17 @@ public abstract class AbstractProject<P extends AbstractProject<P,R>,R extends A
if (!isBuildable())
throw HttpResponses.error(SC_INTERNAL_SERVER_ERROR,new IOException(getFullName()+" is not buildable"));
Jenkins.getInstance().getQueue().schedule(this, (int)delay.getTime(), getBuildCause(req));
rsp.sendRedirect(".");
ScheduleResult r = Jenkins.getInstance().getQueue().schedule2(this, delay.getTime(), getBuildCause(req));
if (r.isAccepted()) {
rsp.sendRedirect(SC_CREATED,req.getContextPath()+'/'+r.getItem().getUrl());
} else
rsp.sendRedirect(".");
}
/** @deprecated use {@link #doBuild(StaplerRequest, StaplerResponse, TimeDuration)} */
@Deprecated
public void doBuild(StaplerRequest req, StaplerResponse rsp) throws IOException, ServletException {
doBuild(req, rsp, TimeDuration.fromString(req.getParameter("delay")));
}
/**
@@ -1815,7 +1870,7 @@ public abstract class AbstractProject<P extends AbstractProject<P,R>,R extends A
/**
* Computes the delay by taking the default value and the override in the request parameter into the account.
*
* @deprecated as of 1.488
* @deprecated as of 1.489
* Inject {@link TimeDuration}.
*/
public int getDelay(StaplerRequest req) throws ServletException {
@@ -1848,6 +1903,12 @@ public abstract class AbstractProject<P extends AbstractProject<P,R>,R extends A
}
/** @deprecated use {@link #doBuildWithParameters(StaplerRequest, StaplerResponse, TimeDuration)} */
@Deprecated
public void doBuildWithParameters(StaplerRequest req, StaplerResponse rsp) throws IOException, ServletException {
doBuildWithParameters(req, rsp, TimeDuration.fromString(req.getParameter("delay")));
}
/**
* Schedules a new SCM polling command.
*/
@@ -1877,11 +1938,20 @@ public abstract class AbstractProject<P extends AbstractProject<P,R>,R extends A
delete();
if (req == null || rsp == null)
return;
View view = req.findAncestorObject(View.class);
if (view == null)
rsp.sendRedirect2(req.getContextPath() + '/' + getParent().getUrl());
else
rsp.sendRedirect2(req.getContextPath() + '/' + view.getUrl());
List<Ancestor> ancestors = req.getAncestors();
ListIterator<Ancestor> it = ancestors.listIterator(ancestors.size());
String url = getParent().getUrl(); // fallback but we ought to get to Jenkins.instance at the root
while (it.hasPrevious()) {
Object a = it.previous().getObject();
if (a instanceof View) {
url = ((View) a).getUrl();
break;
} else if (a instanceof ViewGroup) {
url = ((ViewGroup) a).getUrl();
break;
}
}
rsp.sendRedirect2(req.getContextPath() + '/' + url);
}
@Override
@@ -1933,8 +2003,8 @@ public abstract class AbstractProject<P extends AbstractProject<P,R>,R extends A
for (Trigger t : triggers())
t.stop();
triggers = buildDescribable(req, Trigger.for_(this));
for (Trigger t : triggers)
triggers.replaceBy(buildDescribable(req, Trigger.for_(this)));
for (Trigger t : triggers())
t.start(this,true);
for (Publisher _t : Descriptor.newInstancesFromHeteroList(req, json, "publisher", Jenkins.getInstance().getExtensionList(BuildTrigger.DescriptorImpl.class))) {
@@ -1975,6 +2045,17 @@ public abstract class AbstractProject<P extends AbstractProject<P,R>,R extends A
return r;
}
public ContextMenu doChildrenContextMenu(StaplerRequest request, StaplerResponse response) throws Exception {
// not sure what would be really useful here. This needs more thoughts.
// for the time being, I'm starting with permalinks
ContextMenu menu = new ContextMenu();
for (Permalink p : getPermalinks()) {
if (p.resolve(this)!=null)
menu.add(p.getId(),p.getDisplayName());
}
return menu;
}
/**
* Serves the workspace files.
*/
@@ -61,7 +61,7 @@ import org.kohsuke.stapler.StaplerResponse;
* <dl>
* <dt>message.jelly</dt>
* <dd>
* If {@link #isActivated()} returns true, Hudson will use the <tt>message.jelly</tt>
* If {@link #isActivated()} returns true, Jenkins will use the <tt>message.jelly</tt>
* view of this object to render the warning text. This happens in the
* <tt>http://SERVER/jenkins/manage</tt> page. This view should typically render
* a DIV box with class='error' or class='warning' with a human-readable text
@@ -22,7 +22,7 @@ import java.util.logging.Level;
*/
public abstract class AsyncPeriodicWork extends PeriodicWork {
/**
* Name of the work.
* Human readable name of the work.
*/
public final String name;
@@ -135,7 +135,7 @@ public enum BallColor implements StatusIcon {
* Gets the unanimated version.
*/
public BallColor noAnime() {
if(isAnimated()) return valueOf(name().substring(0,name().length()-6));
if(isAnimated()) return valueOf(name().substring(0,name().length()-"_ANIME".length()));
else return this;
}
@@ -90,4 +90,9 @@ public class BooleanParameterValue extends ParameterValue {
public String toString() {
return "(BooleanParameterValue) " + getName() + "='" + value + "'";
}
}
@Override public String getShortDescription() {
return name + '=' + value;
}
}
@@ -31,6 +31,7 @@ import org.kohsuke.stapler.StaplerRequest;
import org.kohsuke.stapler.StaplerResponse;
import java.io.IOException;
import javax.servlet.http.HttpServletResponse;
import jenkins.security.ApiTokenProperty;
import org.acegisecurity.AccessDeniedException;
import org.kohsuke.stapler.HttpResponses;
@@ -84,6 +85,8 @@ public final class BuildAuthorizationToken {
return;
}
rsp.setStatus(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
rsp.addHeader("Allow", "POST");
throw HttpResponses.forwardToView(project, "requirePOST.jelly");
}
@@ -67,7 +67,7 @@ public abstract class BuildVariableContributor implements ExtensionPoint {
public abstract void buildVariablesFor(AbstractBuild build, Map<String,String> variables);
/**
* Returns all the registered {@link EnvironmentContributor}s.
* Returns all the registered {@link BuildVariableContributor}s.
*/
public static ExtensionList<BuildVariableContributor> all() {
return Jenkins.getInstance().getExtensionList(BuildVariableContributor.class);
@@ -149,6 +149,39 @@ public abstract class Cause {
this.upstreamCauses = upstreamCauses;
}
/**
* @since 1.515
*/
@Override
public boolean equals(Object rhs) {
if (this == rhs) return true;
if (!(rhs instanceof UpstreamCause)) return false;
final UpstreamCause o = (UpstreamCause) rhs;
if (upstreamBuild != o.upstreamBuild) return false;
if (!upstreamCauses.equals(o.upstreamCauses)) return false;
if (upstreamUrl == null ? o.upstreamUrl != null : !upstreamUrl.equals(o.upstreamUrl)) return false;
if (upstreamProject == null ? o.upstreamProject != null : !upstreamProject.equals(o.upstreamProject)) return false;
return true;
}
/**
* @since 1.515
*/
@Override
public int hashCode() {
int hashCode = 17;
hashCode = hashCode * 31 + upstreamCauses.hashCode();
hashCode = hashCode * 31 + upstreamBuild;
hashCode = hashCode * 31 + (upstreamUrl == null ? 0 : upstreamUrl.hashCode ());
return hashCode * 31 + (upstreamProject == null ? 0 : upstreamProject.hashCode ());
}
private @Nonnull Cause trim(@Nonnull Cause c, int depth, Set<String> traversed) {
if (!(c instanceof UpstreamCause)) {
return c;
@@ -32,12 +32,15 @@ import org.kohsuke.stapler.export.ExportedBean;
import com.thoughtworks.xstream.converters.UnmarshallingContext;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import jenkins.model.RunAction2;
@ExportedBean
public class CauseAction implements FoldableAction, RunAction {
public class CauseAction implements FoldableAction, RunAction2 {
/**
* @deprecated since 2009-02-28
*/
@@ -50,7 +53,15 @@ public class CauseAction implements FoldableAction, RunAction {
public CauseAction(Cause c) {
this.causes.add(c);
}
public CauseAction(Cause... c) {
this(Arrays.asList(c));
}
public CauseAction(Collection<? extends Cause> causes) {
this.causes.addAll(causes);
}
public CauseAction(CauseAction ca) {
this.causes.addAll(ca.causes);
}
@@ -105,18 +116,14 @@ public class CauseAction implements FoldableAction, RunAction {
return causes.get(0).getShortDescription();
}
public void onLoad() {
// noop
}
public void onBuildComplete() {
@Override public void onLoad(Run<?,?> r) {
// noop
}
/**
* When hooked up to build, notify {@link Cause}s.
*/
public void onAttached(Run owner) {
@Override public void onAttached(Run<?,?> owner) {
if (owner instanceof AbstractBuild) {// this should be always true but being defensive here
AbstractBuild b = (AbstractBuild) owner;
for (Cause c : causes) {
@@ -28,6 +28,7 @@ import hudson.tasks.Recorder;
import hudson.tasks.Builder;
import hudson.tasks.junit.JUnitResultArchiver;
import hudson.scm.SCM;
import javax.annotation.Nonnull;
/**
* Provides a mechanism for synchronizing build executions in the face of concurrent builds.
@@ -141,7 +142,18 @@ public final class CheckPoint {
* If the build (represented by the calling executor thread) is aborted while it's waiting.
*/
public void block() throws InterruptedException {
Run.waitForCheckpoint(this);
Run.waitForCheckpoint(this, null, null);
}
/**
* Like {@link #block()} but allows for richer logging.
* @param listener an optional listener to which
* @param waiter a description of what component is requesting the wait, such as {@link Descriptor#getDisplayName}
* @throws InterruptedException if the build is aborted while waiting
* @since 1.528
*/
public void block(@Nonnull BuildListener listener, @Nonnull String waiter) throws InterruptedException {
Run.waitForCheckpoint(this, listener, waiter);
}
/**
+75 -37
View File
@@ -32,6 +32,7 @@ import hudson.cli.declarative.CLIResolver;
import hudson.console.AnnotatedLargeText;
import hudson.init.Initializer;
import hudson.model.Descriptor.FormException;
import hudson.model.Queue.FlyweightTask;
import hudson.model.labels.LabelAtom;
import hudson.model.queue.WorkUnit;
import hudson.node_monitors.NodeMonitor;
@@ -50,9 +51,6 @@ import hudson.slaves.RetentionStrategy;
import hudson.slaves.WorkspaceList;
import hudson.slaves.OfflineCause;
import hudson.slaves.OfflineCause.ByCLI;
import hudson.tasks.BuildWrapper;
import hudson.tasks.Publisher;
import hudson.util.DaemonThreadFactory;
import hudson.util.EditDistance;
import hudson.util.ExceptionCatchingThreadFactory;
import hudson.util.RemotingDiagnostics;
@@ -78,6 +76,7 @@ import javax.servlet.ServletException;
import java.io.File;
import java.io.FilenameFilter;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.*;
@@ -86,6 +85,8 @@ import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.logging.LogRecord;
import java.util.logging.Level;
import java.util.logging.Logger;
@@ -111,7 +112,9 @@ import static javax.servlet.http.HttpServletResponse.*;
* This object is related to {@link Node} but they have some significant difference.
* {@link Computer} primarily works as a holder of {@link Executor}s, so
* if a {@link Node} is configured (probably temporarily) with 0 executors,
* you won't have a {@link Computer} object for it.
* you won't have a {@link Computer} object for it (except for the master node,
* which always get its {@link Computer} in case we have no static executors and
* we need to run a {@link FlyweightTask} - see JENKINS-7291 for more discussion.)
*
* Also, even if you remove a {@link Node}, it takes time for the corresponding
* {@link Computer} to be removed, if some builds are already in progress on that
@@ -164,7 +167,6 @@ public /*transient*/ abstract class Computer extends Actionable implements Acces
protected final Object statusChangeLock = new Object();
public Computer(Node node) {
assert node.getNumExecutors()!=0 : "Computer created with 0 executors";
setNode(node);
}
@@ -620,7 +622,7 @@ public /*transient*/ abstract class Computer extends Actionable implements Acces
}
public String getUrl() {
return "computer/"+getDisplayName()+"/";
return "computer/" + Util.rawEncode(getDisplayName()) + "/";
}
/**
@@ -661,23 +663,46 @@ public /*transient*/ abstract class Computer extends Actionable implements Acces
/**
* Called by {@link Jenkins#updateComputerList()} to notify {@link Computer} that it will be discarded.
*
* <p>
* Note that at this point {@link #getNode()} returns null.
*
* @see #onRemoved()
*/
protected void kill() {
setNumExecutors(0);
}
/**
* Called by {@link Jenkins} when this computer is removed.
*
* <p>
* This happens when list of nodes are updated (for example by {@link Jenkins#setNodes(List)} and
* the computer becomes redundant. Such {@link Computer}s get {@linkplain #kill() killed}, then
* after all its executors are finished, this method is called.
*
* <p>
* Note that at this point {@link #getNode()} returns null.
*
* @see #kill()
* @since 1.510
*/
protected void onRemoved(){
}
private synchronized void setNumExecutors(int n) {
if(numExecutors==n) return; // no-op
int diff = n-numExecutors;
this.numExecutors = n;
int diff = executors.size()-n;
if(diff<0) {
if (diff>0) {
// we have too many executors
// send signal to all idle executors to potentially kill them off
for( Executor e : executors )
if(e.isIdle())
e.interrupt();
} else {
}
if (diff<0) {
// if the number is increased, add new ones
addNewExecutorIfNecessary();
}
@@ -817,8 +842,10 @@ public /*transient*/ abstract class Computer extends Actionable implements Acces
*
* Note that if an executor dies, we'll leave it in {@link #executors} until
* the administrator yanks it out, so that we can see why it died.
*
* @since 1.509
*/
private boolean isAlive() {
protected boolean isAlive() {
for (Executor e : executors)
if (e.isAlive())
return true;
@@ -1034,7 +1061,19 @@ public /*transient*/ abstract class Computer extends Actionable implements Acces
private static final long serialVersionUID = 1L;
}
public static final ExecutorService threadPoolForRemoting = Executors.newCachedThreadPool(new ExceptionCatchingThreadFactory(new DaemonThreadFactory()));
public static final ExecutorService threadPoolForRemoting = Executors.newCachedThreadPool(new ExceptionCatchingThreadFactory(
new ThreadFactory() {
private final AtomicInteger threadNumber = new AtomicInteger(1);
@Override
public Thread newThread(Runnable r) {
Thread t = new Thread(r);
t.setName("Jenkins-Remoting-Thread-"+threadNumber.getAndIncrement());
t.setDaemon(true);
return t;
}
}));
//
//
@@ -1072,8 +1111,8 @@ public /*transient*/ abstract class Computer extends Actionable implements Acces
offlineMessage = Util.fixEmptyAndTrim(offlineMessage);
setTemporarilyOffline(true,
OfflineCause.create(hudson.slaves.Messages._SlaveComputer_DisconnectedBy(
Jenkins.getAuthentication().getName(),
offlineMessage!=null ? " : " + offlineMessage : "")));
Jenkins.getAuthentication().getName(),
offlineMessage != null ? " : " + offlineMessage : "")));
return HttpResponses.redirectToDot();
}
@@ -1130,20 +1169,7 @@ public /*transient*/ abstract class Computer extends Actionable implements Acces
}
protected void _doScript( StaplerRequest req, StaplerResponse rsp, String view) throws IOException, ServletException {
// ability to run arbitrary script is dangerous
checkPermission(Jenkins.RUN_SCRIPTS);
String text = req.getParameter("script");
if(text!=null) {
try {
req.setAttribute("output",
RemotingDiagnostics.executeGroovy(text,getChannel()));
} catch (InterruptedException e) {
throw new ServletException(e);
}
}
req.getView(this,view).forward(req, rsp);
Jenkins._doScript(req, rsp, req.getView(this, view), getChannel(), getACL());
}
/**
@@ -1173,18 +1199,17 @@ public /*transient*/ abstract class Computer extends Actionable implements Acces
@WebMethod(name = "config.xml")
public void doConfigDotXml(StaplerRequest req, StaplerResponse rsp)
throws IOException, ServletException {
checkPermission(Jenkins.ADMINISTER);
if (req.getMethod().equals("GET")) {
// read
checkPermission(EXTENDED_READ);
rsp.setContentType("application/xml");
Jenkins.XSTREAM2.toXMLUTF8(getNode(), rsp.getOutputStream());
return;
}
if (req.getMethod().equals("POST")) {
// submission
Node result = (Node)Jenkins.XSTREAM2.fromXML(req.getReader());
replaceBy(result);
updateByXml(req.getInputStream());
return;
}
@@ -1211,6 +1236,17 @@ public /*transient*/ abstract class Computer extends Actionable implements Acces
}
}
/**
* Updates Job by its XML definition.
*
* @since 1.526
*/
public void updateByXml(final InputStream source) throws IOException, ServletException {
checkPermission(CONFIGURE);
Node result = (Node)Jenkins.XSTREAM2.fromXML(source);
replaceBy(result);
}
/**
* Really deletes the slave.
*/
@@ -1256,7 +1292,7 @@ public /*transient*/ abstract class Computer extends Actionable implements Acces
/**
* Gets the current {@link Computer} that the build is running.
* This method only works when called during a build, such as by
* {@link Publisher}, {@link BuildWrapper}, etc.
* {@link hudson.tasks.Publisher}, {@link hudson.tasks.BuildWrapper}, etc.
*/
public static Computer currentComputer() {
Executor e = Executor.currentExecutor();
@@ -1327,14 +1363,16 @@ public /*transient*/ abstract class Computer extends Actionable implements Acces
}
public static final PermissionGroup PERMISSIONS = new PermissionGroup(Computer.class,Messages._Computer_Permissions_Title());
/**
* Permission to configure slaves.
*/
public static final Permission CONFIGURE = new Permission(PERMISSIONS,"Configure", Messages._Computer_ConfigurePermission_Description(), Permission.CONFIGURE, PermissionScope.COMPUTER);
/**
* @since 1.532
*/
public static final Permission EXTENDED_READ = new Permission(PERMISSIONS,"ExtendedRead", Messages._Computer_ExtendedReadPermission_Description(), CONFIGURE, Boolean.getBoolean("hudson.security.ExtendedReadPermission"), new PermissionScope[]{PermissionScope.COMPUTER});
public static final Permission DELETE = new Permission(PERMISSIONS,"Delete", Messages._Computer_DeletePermission_Description(), Permission.DELETE, PermissionScope.COMPUTER);
public static final Permission CREATE = new Permission(PERMISSIONS,"Create", Messages._Computer_CreatePermission_Description(), Permission.CREATE, PermissionScope.COMPUTER);
public static final Permission DISCONNECT = new Permission(PERMISSIONS,"Disconnect", Messages._Computer_DisconnectPermission_Description(), Jenkins.ADMINISTER, PermissionScope.COMPUTER);
public static final Permission CONNECT = new Permission(PERMISSIONS,"Connect", Messages._Computer_ConnectPermission_Description(), DISCONNECT, PermissionScope.COMPUTER);
public static final Permission BUILD = new Permission(PERMISSIONS, "Build", Messages._Computer_BuildPermission_Description(), Permission.WRITE, PermissionScope.COMPUTER);
private static final Logger LOGGER = Logger.getLogger(Computer.class.getName());
}
@@ -11,7 +11,7 @@ import java.util.List;
*
* @author Lucie Votypkova
* @since 1.434
* @see jenkins.model.Computer#getComputerPanelBoxs()
* @see hudson.model.Computer#getComputerPanelBoxs()
*/
public abstract class ComputerPanelBox implements ExtensionPoint{
@@ -36,6 +36,8 @@ import hudson.util.DescribableList;
import hudson.util.FormApply;
import hudson.util.FormValidation;
import jenkins.model.Jenkins;
import jenkins.model.ModelObjectWithChildren;
import jenkins.model.ModelObjectWithContextMenu.ContextMenu;
import org.kohsuke.stapler.HttpResponse;
import org.kohsuke.stapler.QueryParameter;
import org.kohsuke.stapler.StaplerRequest;
@@ -65,7 +67,7 @@ import net.sf.json.JSONObject;
* @author Kohsuke Kawaguchi
*/
@ExportedBean
public final class ComputerSet extends AbstractModelObject implements Describable<ComputerSet> {
public final class ComputerSet extends AbstractModelObject implements Describable<ComputerSet>, ModelObjectWithChildren {
/**
* This is the owner that persists {@link #monitors}.
*/
@@ -97,6 +99,14 @@ public final class ComputerSet extends AbstractModelObject implements Describabl
return Jenkins.getInstance().getComputers();
}
public ContextMenu doChildrenContextMenu(StaplerRequest request, StaplerResponse response) throws Exception {
ContextMenu m = new ContextMenu();
for (Computer c : get_all()) {
m.add(c);
}
return m;
}
/**
* Exposing {@link NodeMonitor#all()} for Jelly binding.
*/
@@ -204,7 +214,10 @@ public final class ComputerSet extends AbstractModelObject implements Describabl
for (NodeMonitor nodeMonitor : NodeMonitor.getAll()) {
Thread t = nodeMonitor.triggerUpdate();
t.setName(nodeMonitor.getColumnCaption());
String columnCaption = nodeMonitor.getColumnCaption();
if (columnCaption != null) {
t.setName(columnCaption);
}
}
rsp.forwardToPreviousPage(req);
}
@@ -416,8 +429,8 @@ public final class ComputerSet extends AbstractModelObject implements Describabl
r.add(i);
}
monitors.replaceBy(r.toList());
} catch (IOException e) {
LOGGER.log(Level.WARNING, "Failed to instanciate NodeMonitors",e);
} catch (Throwable x) {
LOGGER.log(Level.WARNING, "Failed to instantiate NodeMonitors", x);
}
}
@@ -28,6 +28,8 @@ import jenkins.model.DependencyDeclarer;
import com.google.common.collect.ImmutableList;
import hudson.security.ACL;
import jenkins.model.Jenkins;
import jenkins.util.DirectedGraph;
import jenkins.util.DirectedGraph.SCC;
import org.acegisecurity.context.SecurityContext;
import org.acegisecurity.context.SecurityContextHolder;
@@ -75,6 +77,9 @@ public class DependencyGraph implements Comparator<AbstractProject> {
private boolean built;
private Comparator<AbstractProject<?,?>> topologicalOrder;
private List<AbstractProject<?,?>> topologicallySorted;
/**
* Builds the dependency graph.
*/
@@ -91,14 +96,57 @@ public class DependencyGraph implements Comparator<AbstractProject> {
forward = finalize(forward);
backward = finalize(backward);
topologicalDagSort();
this.computationalData = null;
built = true;
} finally {
SecurityContextHolder.setContext(saveCtx);
}
}
/**
*
*
* See http://en.wikipedia.org/wiki/Tarjan's_strongly_connected_components_algorithm
*/
private void topologicalDagSort() {
DirectedGraph<AbstractProject> g = new DirectedGraph<AbstractProject>() {
@Override
protected Collection<AbstractProject> nodes() {
final Set<AbstractProject> nodes = new HashSet<AbstractProject>();
nodes.addAll(forward.keySet());
nodes.addAll(backward.keySet());
return nodes;
}
@Override
protected Collection<AbstractProject> forward(AbstractProject node) {
return getDownstream(node);
}
};
List<SCC<AbstractProject>> sccs = g.getStronglyConnectedComponents();
final Map<AbstractProject,Integer> topoOrder = new HashMap<AbstractProject,Integer>();
topologicallySorted = new ArrayList<AbstractProject<?,?>>();
int idx=0;
for (SCC<AbstractProject> scc : sccs) {
for (AbstractProject n : scc) {
topoOrder.put(n,idx++);
topologicallySorted.add(n);
}
}
topologicalOrder = new Comparator<AbstractProject<?, ?>>() {
@Override
public int compare(AbstractProject<?,?> o1, AbstractProject<?,?> o2) {
return topoOrder.get(o1)-topoOrder.get(o2);
}
};
topologicallySorted = Collections.unmodifiableList(topologicallySorted);
}
Collection<AbstractProject> getAllProjects() {
return Jenkins.getInstance().getAllItems(AbstractProject.class);
}
@@ -108,6 +156,7 @@ public class DependencyGraph implements Comparator<AbstractProject> {
*/
private DependencyGraph(boolean dummy) {
forward = backward = Collections.emptyMap();
topologicalDagSort();
built = true;
}
@@ -199,7 +248,7 @@ public class DependencyGraph implements Comparator<AbstractProject> {
if(built)
throw new IllegalStateException();
add(forward,dep.getUpstreamProject(),dep);
add(backward,dep.getDownstreamProject(),dep);
add(backward, dep.getDownstreamProject(), dep);
}
/**
@@ -324,16 +373,22 @@ public class DependencyGraph implements Comparator<AbstractProject> {
public static final DependencyGraph EMPTY = new DependencyGraph(false);
/**
* Compare to Projects based on the topological order defined by this Dependency Graph
* Compare two Projects based on the topological order defined by this Dependency Graph
*/
public int compare(AbstractProject o1, AbstractProject o2) {
Set<AbstractProject> o1sdownstreams = getTransitiveDownstream(o1);
Set<AbstractProject> o2sdownstreams = getTransitiveDownstream(o2);
if (o1sdownstreams.contains(o2)) {
if (o2sdownstreams.contains(o1)) return 0; else return 1;
} else {
if (o2sdownstreams.contains(o1)) return -1; else return 0;
}
return topologicalOrder.compare(o1,o2);
}
/**
* Returns all the projects in the topological order of the dependency.
*
* Intuitively speaking, the first one in the list is the source of the dependency graph,
* and the last one is the sink.
*
* @since 1.521
*/
public List<AbstractProject<?,?>> getTopologicallySorted() {
return topologicallySorted;
}
/**
+36 -68
View File
@@ -31,6 +31,7 @@ import hudson.BulkChange;
import hudson.Util;
import hudson.model.listeners.SaveableListener;
import hudson.util.FormApply;
import hudson.util.FormValidation.CheckMethod;
import hudson.util.ReflectionUtils;
import hudson.util.ReflectionUtils.Parameter;
import hudson.views.ListViewColumn;
@@ -44,7 +45,6 @@ import org.springframework.util.StringUtils;
import org.jvnet.tiger_types.Types;
import org.apache.commons.io.IOUtils;
import static hudson.Functions.*;
import static hudson.util.QuotedStringTokenizer.*;
import static javax.servlet.http.HttpServletResponse.SC_NOT_FOUND;
import javax.servlet.ServletException;
@@ -127,7 +127,7 @@ public abstract class Descriptor<T extends Describable<T>> implements Saveable {
*/
public transient final Class<? extends T> clazz;
private transient final Map<String,String> checkMethods = new ConcurrentHashMap<String,String>();
private transient final Map<String,CheckMethod> checkMethods = new ConcurrentHashMap<String,CheckMethod>();
/**
* Lazily computed list of properties on {@link #clazz} and on the descriptor itself.
@@ -223,7 +223,22 @@ public abstract class Descriptor<T extends Describable<T>> implements Saveable {
*
* @see #getHelpFile(String)
*/
private transient final Map<String,String> helpRedirect = new HashMap<String, String>();
private transient final Map<String,HelpRedirect> helpRedirect = new HashMap<String,HelpRedirect>();
private static class HelpRedirect {
private final Class<? extends Describable> owner;
private final String fieldNameToRedirectTo;
private HelpRedirect(Class<? extends Describable> owner, String fieldNameToRedirectTo) {
this.owner = owner;
this.fieldNameToRedirectTo = fieldNameToRedirectTo;
}
private String resolve() {
// the resolution has to be deferred to avoid ordering issue among descriptor registrations.
return Jenkins.getInstance().getDescriptor(owner).getHelpFile(fieldNameToRedirectTo);
}
}
/**
*
@@ -347,68 +362,27 @@ public abstract class Descriptor<T extends Describable<T>> implements Saveable {
}
/**
* If the field "xyz" of a {@link Describable} has the corresponding "doCheckXyz" method,
* return the form-field validation string. Otherwise null.
* <p>
* This method is used to hook up the form validation method to the corresponding HTML input element.
* @deprecated since 1.528
* Use {@link #getCheckMethod(String)}
*/
public String getCheckUrl(String fieldName) {
String method = checkMethods.get(fieldName);
if(method==null) {
method = calcCheckUrl(fieldName);
checkMethods.put(fieldName,method);
}
if (method.equals(NONE)) // == would do, but it makes IDE flag a warning
return null;
// put this under the right contextual umbrella.
// a is always non-null because we already have Hudson as the sentinel
return '\'' + jsStringEscape(getCurrentDescriptorByNameUrl()) + "/'+" + method;
}
private String calcCheckUrl(String fieldName) {
String capitalizedFieldName = StringUtils.capitalize(fieldName);
Method method = ReflectionUtils.getPublicMethodNamed(getClass(),"doCheck"+ capitalizedFieldName);
if(method==null)
return NONE;
return '\'' + getDescriptorUrl() + "/check" + capitalizedFieldName + '\'' + buildParameterList(method, new StringBuilder()).append(".toString()");
return getCheckMethod(fieldName).toCheckUrl();
}
/**
* Builds query parameter line by figuring out what should be submitted
* If the field "xyz" of a {@link Describable} has the corresponding "doCheckXyz" method,
* return the model of the check method.
* <p>
* This method is used to hook up the form validation method to the corresponding HTML input element.
*/
private StringBuilder buildParameterList(Method method, StringBuilder query) {
for (Parameter p : ReflectionUtils.getParameters(method)) {
QueryParameter qp = p.annotation(QueryParameter.class);
if (qp!=null) {
String name = qp.value();
if (name.length()==0) name = p.name();
if (name==null || name.length()==0)
continue; // unknown parameter name. we'll report the error when the form is submitted.
RelativePath rp = p.annotation(RelativePath.class);
if (rp!=null)
name = rp.value()+'/'+name;
if (query.length()==0) query.append("+qs(this)");
if (name.equals("value")) {
// The special 'value' parameter binds to the the current field
query.append(".addThis()");
} else {
query.append(".nearBy('"+name+"')");
}
continue;
}
Method m = ReflectionUtils.getPublicMethodNamed(p.type(), "fromStapler");
if (m!=null) buildParameterList(m,query);
public CheckMethod getCheckMethod(String fieldName) {
CheckMethod method = checkMethods.get(fieldName);
if(method==null) {
method = new CheckMethod(this,fieldName);
checkMethods.put(fieldName,method);
}
return query;
return method;
}
/**
@@ -477,7 +451,7 @@ public abstract class Descriptor<T extends Describable<T>> implements Saveable {
}
/**
* Akin to {@link #getPropertyType(Object,String) but never returns null.
* Akin to {@link #getPropertyType(Object,String)} but never returns null.
* @throws AssertionError in case the field cannot be found
* @since 1.492
*/
@@ -657,8 +631,8 @@ public abstract class Descriptor<T extends Describable<T>> implements Saveable {
}
public String getHelpFile(Klass<?> clazz, String fieldName) {
String v = helpRedirect.get(fieldName);
if (v!=null) return v;
HelpRedirect r = helpRedirect.get(fieldName);
if (r!=null) return r.resolve();
for (Klass<?> c : clazz.getAncestors()) {
String page = "/descriptor/" + getId() + "/help";
@@ -688,8 +662,7 @@ public abstract class Descriptor<T extends Describable<T>> implements Saveable {
* @since 1.425
*/
protected void addHelpFileRedirect(String fieldName, Class<? extends Describable> owner, String fieldNameToRedirectTo) {
helpRedirect.put(fieldName,
Jenkins.getInstance().getDescriptor(owner).getHelpFile(fieldNameToRedirectTo));
helpRedirect.put(fieldName, new HelpRedirect(owner,fieldNameToRedirectTo));
}
/**
@@ -1005,11 +978,6 @@ public abstract class Descriptor<T extends Describable<T>> implements Saveable {
private static final Logger LOGGER = Logger.getLogger(Descriptor.class.getName());
/**
* Used in {@link #checkMethods} to indicate that there's no check method.
*/
private static final String NONE = "\u0000";
/**
* Special type indicating that {@link Descriptor} describes itself.
* @see Descriptor#Descriptor(Class)
@@ -26,19 +26,13 @@ package hudson.model;
import hudson.FilePath;
import hudson.Util;
import hudson.util.IOException2;
import hudson.FilePath.FileCallable;
import hudson.remoting.VirtualChannel;
import jenkins.model.Jenkins;
import org.kohsuke.stapler.StaplerRequest;
import org.kohsuke.stapler.StaplerResponse;
import org.kohsuke.stapler.HttpResponse;
import org.apache.tools.ant.types.FileSet;
import org.apache.tools.ant.DirectoryScanner;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.FilenameFilter;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
@@ -53,6 +47,9 @@ import java.util.Locale;
import java.util.StringTokenizer;
import java.util.logging.Logger;
import java.util.logging.Level;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
import jenkins.util.VirtualFile;
/**
* Has convenience methods to serve file system.
@@ -69,7 +66,7 @@ public final class DirectoryBrowserSupport implements HttpResponse {
public final String title;
private final FilePath base;
private final VirtualFile base;
private final String icon;
private final boolean serveDirIndex;
private String indexFileName = "index.html";
@@ -79,7 +76,7 @@ public final class DirectoryBrowserSupport implements HttpResponse {
* Use {@link #DirectoryBrowserSupport(ModelObject, FilePath, String, String, boolean)}
*/
public DirectoryBrowserSupport(ModelObject owner, String title) {
this(owner,null,title,null,false);
this(owner, (VirtualFile) null, title, null, false);
}
/**
@@ -96,6 +93,24 @@ public final class DirectoryBrowserSupport implements HttpResponse {
* False to serve "index.html"
*/
public DirectoryBrowserSupport(ModelObject owner, FilePath base, String title, String icon, boolean serveDirIndex) {
this(owner, base.toVirtualFile(), title, icon, serveDirIndex);
}
/**
* @param owner
* The parent model object under which the directory browsing is added.
* @param base
* The root of the directory that's bound to URL.
* @param title
* Used in the HTML caption.
* @param icon
* The icon file name, like "folder.gif"
* @param serveDirIndex
* True to generate the directory index.
* False to serve "index.html"
* @since 1.532
*/
public DirectoryBrowserSupport(ModelObject owner, VirtualFile base, String title, String icon, boolean serveDirIndex) {
this.owner = owner;
this.base = base;
this.title = title;
@@ -133,6 +148,10 @@ public final class DirectoryBrowserSupport implements HttpResponse {
* from the {@code doXYZ} method and let Stapler generate a response for you.
*/
public void serveFile(StaplerRequest req, StaplerResponse rsp, FilePath root, String icon, boolean serveDirIndex) throws IOException, ServletException, InterruptedException {
serveFile(req, rsp, root.toVirtualFile(), icon, serveDirIndex);
}
private void serveFile(StaplerRequest req, StaplerResponse rsp, VirtualFile root, String icon, boolean serveDirIndex) throws IOException, ServletException, InterruptedException {
// handle form submission
String pattern = req.getParameter("pattern");
if(pattern==null)
@@ -163,7 +182,7 @@ public final class DirectoryBrowserSupport implements HttpResponse {
String pathElement = pathTokens.nextToken();
// Treat * and ? as wildcard unless they match a literal filename
if((pathElement.contains("?") || pathElement.contains("*"))
&& inBase && !(new FilePath(root, (_base.length() > 0 ? _base + "/" : "") + pathElement).exists()))
&& inBase && !root.child((_base.length() > 0 ? _base + "/" : "") + pathElement).exists())
inBase = false;
if(pathElement.equals("*zip*")) {
// the expected syntax is foo/bar/*zip*/bar.zip
@@ -189,20 +208,23 @@ public final class DirectoryBrowserSupport implements HttpResponse {
String rest = _rest.toString();
// this is the base file/directory
FilePath baseFile = new FilePath(root,base);
VirtualFile baseFile = root.child(base);
if(baseFile.isDirectory()) {
if(zip) {
rsp.setContentType("application/zip");
baseFile.zip(rsp.getOutputStream(),rest);
zip(rsp.getOutputStream(), baseFile, rest);
return;
}
if (plain) {
rsp.setContentType("text/plain;charset=UTF-8");
OutputStream os = rsp.getOutputStream();
try {
for (String kid : baseFile.act(new SimpleChildList())) {
os.write(kid.getBytes("UTF-8"));
for (VirtualFile kid : baseFile.list()) {
os.write(kid.getName().getBytes("UTF-8"));
if (kid.isDirectory()) {
os.write('/');
}
os.write('\n');
}
os.flush();
@@ -221,15 +243,15 @@ public final class DirectoryBrowserSupport implements HttpResponse {
}
}
FileCallable<List<List<Path>>> glob = null;
List<List<Path>> glob = null;
if(rest.length()>0) {
// the rest is Ant glob pattern
glob = new PatternScanner(rest,createBackRef(restSize));
glob = patternScan(baseFile, rest, createBackRef(restSize));
} else
if(serveDirIndex) {
// serve directory index
glob = new ChildPathBuilder(req.getLocale());
glob = buildChildPaths(baseFile, req.getLocale());
}
if(glob!=null) {
@@ -239,7 +261,7 @@ public final class DirectoryBrowserSupport implements HttpResponse {
req.setAttribute("parentPath",parentPaths);
req.setAttribute("backPath", createBackRef(restSize));
req.setAttribute("topPath", createBackRef(parentPaths.size()+restSize));
req.setAttribute("files", baseFile.act(glob));
req.setAttribute("files", glob);
req.setAttribute("icon", icon);
req.setAttribute("path", path);
req.setAttribute("pattern",rest);
@@ -262,24 +284,25 @@ public final class DirectoryBrowserSupport implements HttpResponse {
boolean view = rest.equals("*view*");
if(rest.equals("*fingerprint*")) {
rsp.forward(Jenkins.getInstance().getFingerprint(baseFile.digest()),"/",req);
rsp.forward(Jenkins.getInstance().getFingerprint(Util.getDigestOf(baseFile.open())), "/", req);
return;
}
ContentInfo ci = baseFile.act(new ContentInfo());
long lastModified = baseFile.lastModified();
long length = baseFile.length();
if(LOGGER.isLoggable(Level.FINE))
LOGGER.fine("Serving "+baseFile+" with lastModified="+ci.lastModified+", contentLength="+ci.contentLength);
LOGGER.fine("Serving "+baseFile+" with lastModified=" + lastModified + ", length=" + length);
InputStream in = baseFile.read();
InputStream in = baseFile.open();
if (view) {
// for binary files, provide the file name for download
rsp.setHeader("Content-Disposition", "inline; filename=" + baseFile.getName());
// pseudo file name to let the Stapler set text/plain
rsp.serveFile(req, in, ci.lastModified, -1, ci.contentLength, "plain.txt");
rsp.serveFile(req, in, lastModified, -1, length, "plain.txt");
} else {
rsp.serveFile(req, in, ci.lastModified, -1, ci.contentLength, baseFile.getName() );
rsp.serveFile(req, in, lastModified, -1, length, baseFile.getName() );
}
}
@@ -290,19 +313,6 @@ public final class DirectoryBrowserSupport implements HttpResponse {
return path;
}
private static final class ContentInfo implements FileCallable<ContentInfo> {
long contentLength;
long lastModified;
public ContentInfo invoke(File f, VirtualChannel channel) throws IOException {
contentLength = f.length();
lastModified = f.lastModified();
return this;
}
private static final long serialVersionUID = 1L;
}
/**
* Builds a list of {@link Path} that represents ancestors
* from a string like "/foo/bar/zot".
@@ -328,6 +338,19 @@ public final class DirectoryBrowserSupport implements HttpResponse {
return buf.toString();
}
private static void zip(OutputStream outputStream, VirtualFile dir, String glob) throws IOException {
ZipOutputStream zos = new ZipOutputStream(outputStream);
for (String n : dir.list(glob.length() == 0 ? "**" : glob)) {
ZipEntry e = new ZipEntry(n);
VirtualFile f = dir.child(n);
e.setTime(f.lastModified());
zos.putNextEntry(e);
Util.copyStream(f.open(), outputStream);
zos.closeEntry();
}
zos.close();
}
/**
* Represents information about one file or folder.
*/
@@ -349,7 +372,7 @@ public final class DirectoryBrowserSupport implements HttpResponse {
private final long size;
/**
* If the current user can read the file.
* If the current user can read the file.
*/
private final boolean isReadable;
@@ -393,14 +416,14 @@ public final class DirectoryBrowserSupport implements HttpResponse {
private static final class FileComparator implements Comparator<File> {
private static final class FileComparator implements Comparator<VirtualFile> {
private Collator collator;
public FileComparator(Locale locale) {
this.collator = Collator.getInstance(locale);
}
public int compare(File lhs, File rhs) {
public int compare(VirtualFile lhs, VirtualFile rhs) {
// directories first, files next
int r = dirRank(lhs)-dirRank(rhs);
if(r!=0) return r;
@@ -408,29 +431,13 @@ public final class DirectoryBrowserSupport implements HttpResponse {
return this.collator.compare(lhs.getName(), rhs.getName());
}
private int dirRank(File f) {
private int dirRank(VirtualFile f) {
try {
if(f.isDirectory()) return 0;
else return 1;
}
}
/**
* Simple list of names of children of a folder.
* Subfolders will have a trailing slash appended.
*/
private static final class SimpleChildList implements FileCallable<List<String>> {
private static final long serialVersionUID = 1L;
public List<String> invoke(File f, VirtualChannel channel) throws IOException {
List<String> r = new ArrayList<String>();
String[] kids = f.list(); // no need to sort
for (String kid : kids) {
if (new File(f, kid).isDirectory()) {
r.add(kid + "/");
} else {
r.add(kid);
}
} catch (IOException ex) {
return 0;
}
return r;
}
}
@@ -439,22 +446,14 @@ public final class DirectoryBrowserSupport implements HttpResponse {
* list of {@link Path} represents one child item to be shown
* (this mechanism is used to skip empty intermediate directory.)
*/
private static final class ChildPathBuilder implements FileCallable<List<List<Path>>> {
private Locale locale;
public ChildPathBuilder(Locale locale) {
this.locale = locale;
}
public List<List<Path>> invoke(File cur, VirtualChannel channel) throws IOException {
private static List<List<Path>> buildChildPaths(VirtualFile cur, Locale locale) throws IOException {
List<List<Path>> r = new ArrayList<List<Path>>();
File[] files = cur.listFiles();
if (files != null) {
Arrays.sort(files,new FileComparator(this.locale));
VirtualFile[] files = cur.list();
Arrays.sort(files,new FileComparator(locale));
for( File f : files ) {
Path p = new Path(Util.rawEncode(f.getName()),f.getName(),f.isDirectory(),f.length(), f.canRead());
for( VirtualFile f : files ) {
Path p = new Path(Util.rawEncode(f.getName()), f.getName(), f.isDirectory(), f.length(), f.canRead());
if(!f.isDirectory()) {
r.add(Collections.singletonList(p));
} else {
@@ -464,53 +463,38 @@ public final class DirectoryBrowserSupport implements HttpResponse {
String relPath = Util.rawEncode(f.getName());
while(true) {
// files that don't start with '.' qualify for 'meaningful files', nor SCM related files
File[] sub = f.listFiles(new FilenameFilter() {
public boolean accept(File dir, String name) {
return !name.startsWith(".") && !name.equals("CVS") && !name.equals(".svn");
List<VirtualFile> sub = new ArrayList<VirtualFile>();
for (VirtualFile vf : f.list()) {
String name = vf.getName();
if (!name.startsWith(".") && !name.equals("CVS") && !name.equals(".svn")) {
sub.add(vf);
}
});
if(sub==null || sub.length!=1 || !sub[0].isDirectory())
}
if (sub.size() !=1 || !sub.get(0).isDirectory())
break;
f = sub[0];
f = sub.get(0);
relPath += '/'+Util.rawEncode(f.getName());
l.add(new Path(relPath,f.getName(),true,0, f.canRead()));
}
r.add(l);
}
}
}
return r;
}
private static final long serialVersionUID = 1L;
}
/**
* Runs ant GLOB against the current {@link FilePath} and returns matching
* paths.
* @param baseRef String like "../../../" that cancels the 'rest' portion. Can be "./"
*/
private static class PatternScanner implements FileCallable<List<List<Path>>> {
private final String pattern;
/**
* String like "../../../" that cancels the 'rest' portion. Can be "./"
*/
private final String baseRef;
public PatternScanner(String pattern,String baseRef) {
this.pattern = pattern;
this.baseRef = baseRef;
}
public List<List<Path>> invoke(File baseDir, VirtualChannel channel) throws IOException {
FileSet fs = Util.createFileSet(baseDir,pattern);
DirectoryScanner ds = fs.getDirectoryScanner();
String[] files = ds.getIncludedFiles();
private static List<List<Path>> patternScan(VirtualFile baseDir, String pattern, String baseRef) throws IOException {
String[] files = baseDir.list(pattern);
if (files.length > 0) {
List<List<Path>> r = new ArrayList<List<Path>>(files.length);
for (String match : files) {
List<Path> file = buildPathList(baseDir, new File(baseDir,match));
List<Path> file = buildPathList(baseDir, baseDir.child(match), baseRef);
r.add(file);
}
return r;
@@ -522,7 +506,7 @@ public final class DirectoryBrowserSupport implements HttpResponse {
/**
* Builds a path list from the current workspace directory down to the specified file path.
*/
private List<Path> buildPathList(File baseDir, File filePath) throws IOException {
private static List<Path> buildPathList(VirtualFile baseDir, VirtualFile filePath, String baseRef) throws IOException {
List<Path> pathList = new ArrayList<Path>();
StringBuilder href = new StringBuilder(baseRef);
@@ -533,8 +517,8 @@ public final class DirectoryBrowserSupport implements HttpResponse {
/**
* Builds the path list and href recursively top-down.
*/
private void buildPathList(File baseDir, File filePath, List<Path> pathList, StringBuilder href) throws IOException {
File parent = filePath.getParentFile();
private static void buildPathList(VirtualFile baseDir, VirtualFile filePath, List<Path> pathList, StringBuilder href) throws IOException {
VirtualFile parent = filePath.getParent();
if (!baseDir.equals(parent)) {
buildPathList(baseDir, parent, pathList, href);
}
@@ -548,8 +532,6 @@ public final class DirectoryBrowserSupport implements HttpResponse {
pathList.add(path);
}
private static final long serialVersionUID = 1L;
}
private static final Logger LOGGER = Logger.getLogger(DirectoryBrowserSupport.class.getName());
}
@@ -49,8 +49,8 @@ public class DisplayNameListener extends ItemListener {
* displayed.
*/
public void onCopied(Item src, Item item) {
// bug 5056825 - Display name field should be cleared when you copy a job.
if(item instanceof AbstractItem) {
// bug 5056825 - Display name field should be cleared when you copy a job within the same folder.
if(item instanceof AbstractItem && src.getParent() == item.getParent()) {
AbstractItem dest = (AbstractItem)item;
try {
dest.setDisplayName(null);
@@ -73,6 +73,11 @@ public abstract class EnvironmentContributor implements ExtensionPoint {
* This method gets invoked concurrently for multiple {@link Run}s that are being built at the same time,
* so it must be concurrent-safe.
*
* <p>
* When building environment variables for a build, Jenkins will also invoke
* {@link #buildEnvironmentFor(Job, EnvVars, TaskListener)}. This method only needs to add
* variables that are scoped to builds.
*
* @param r
* Build that's being performed. Never null.
* @param envs
@@ -81,7 +86,29 @@ public abstract class EnvironmentContributor implements ExtensionPoint {
* @param listener
* Connected to the build console. Can be used to report errors. Never null.
*/
public abstract void buildEnvironmentFor(Run r, EnvVars envs, TaskListener listener) throws IOException, InterruptedException;
public void buildEnvironmentFor(Run r, EnvVars envs, TaskListener listener) throws IOException, InterruptedException {}
/**
* Contributes environment variables used for a job.
*
* <p>
* This method can be called repeatedly for the same {@link Job}, thus
* the computation of this method needs to be efficient.
*
* <p>
* This method gets invoked concurrently for multiple {@link Job}s,
* so it must be concurrent-safe.
*
* @param j
* Job for which some activities are launched.
* @param envs
* Partially built environment variable map. Implementation of this method is expected to
* add additional variables here. Never null.
* @param listener
* Connected to the build console. Can be used to report errors. Never null.
* @since 1.527
*/
public void buildEnvironmentFor(Job j, EnvVars envs, TaskListener listener) throws IOException, InterruptedException {}
/**
* Returns all the registered {@link EnvironmentContributor}s.
+21 -9
View File
@@ -38,7 +38,8 @@ import hudson.security.ACL;
import jenkins.model.InterruptedBuildAction;
import jenkins.model.Jenkins;
import org.acegisecurity.Authentication;
import org.acegisecurity.providers.anonymous.AnonymousAuthenticationToken;
import org.acegisecurity.context.SecurityContext;
import org.acegisecurity.context.SecurityContextHolder;
import org.kohsuke.stapler.HttpResponse;
import org.kohsuke.stapler.HttpResponses;
import org.kohsuke.stapler.StaplerRequest;
@@ -49,6 +50,7 @@ import org.kohsuke.stapler.export.Exported;
import javax.servlet.ServletException;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Vector;
import java.util.logging.Logger;
@@ -81,7 +83,7 @@ public class Executor extends Thread implements ModelObject {
*/
private int number;
/**
* {@link Queue.Executable} being executed right now, or null if the executor is idle.
* {@link hudson.model.Queue.Executable} being executed right now, or null if the executor is idle.
*/
private volatile Queue.Executable executable;
@@ -122,7 +124,7 @@ public class Executor extends Thread implements ModelObject {
*/
public void interrupt(Result result) {
Authentication a = Jenkins.getAuthentication();
if(a instanceof AnonymousAuthenticationToken || a==ACL.SYSTEM)
if (a == ACL.SYSTEM)
interrupt(result, new CauseOfInterruption[0]);
else {
// worth recording who did it
@@ -135,6 +137,9 @@ public class Executor extends Thread implements ModelObject {
* Interrupt the execution. Mark the cause and the status accordingly.
*/
public void interrupt(Result result, CauseOfInterruption... causes) {
if (LOGGER.isLoggable(FINE))
LOGGER.log(FINE, String.format("%s is interrupted(%s): %s", getDisplayName(), result, Util.join(Arrays.asList(causes),",")), new InterruptedException());
interruptStatus = result;
synchronized (this.causes) {
for (CauseOfInterruption c : causes) {
@@ -210,6 +215,7 @@ public class Executor extends Thread implements ModelObject {
task = workUnit.work;
startTime = System.currentTimeMillis();
executable = task.createExecutable();
workUnit.setExecutable(executable);
}
if (LOGGER.isLoggable(FINE))
LOGGER.log(FINE, getName()+" is going to execute "+executable);
@@ -231,10 +237,16 @@ public class Executor extends Thread implements ModelObject {
((Actionable) executable).addAction(action);
}
}
setName(threadName+" : executing "+executable.toString());
if (LOGGER.isLoggable(FINE))
LOGGER.log(FINE, getName()+" is now executing "+executable);
queue.execute(executable, task);
final SecurityContext savedContext = ACL.impersonate(workUnit.context.item.authenticate());
try {
setName(threadName + " : executing " + executable.toString());
if (LOGGER.isLoggable(FINE))
LOGGER.log(FINE, getName()+" is now executing "+executable);
queue.execute(executable, task);
} finally {
SecurityContextHolder.setContext(savedContext);
}
} catch (Throwable e) {
// for some reason the executor died. this is really
// a bug in the code, but we don't want the executor to die,
@@ -286,7 +298,7 @@ public class Executor extends Thread implements ModelObject {
}
/**
* Returns the current {@link Queue.Task} this executor is running.
* Returns the current {@link hudson.model.Queue.Task} this executor is running.
*
* @return
* null if the executor is idle.
@@ -563,7 +575,7 @@ public class Executor extends Thread implements ModelObject {
* was compiled against Hudson < 1.383
*
* @deprecated as of 1.388
* Use {@link Executables#getEstimatedDurationFor(Executable)}
* Use {@link Executables#getEstimatedDurationFor(Queue.Executable)}
*/
public static long getEstimatedDurationFor(Executable e) {
return Executables.getEstimatedDurationFor(e);
@@ -86,12 +86,21 @@ public class FileParameterDefinition extends ParameterDefinition {
// the requested file parameter wasn't uploaded
return null;
}
FileParameterValue p = new FileParameterValue(getName(), src);
FileParameterValue p = new FileParameterValue(getName(), src, getFileName(src.getName()));
p.setDescription(getDescription());
p.setLocation(getName());
return p;
}
/**
* Strip off the path portion if the given path contains it.
*/
private String getFileName(String possiblyPathName) {
possiblyPathName = possiblyPathName.substring(possiblyPathName.lastIndexOf('/')+1);
possiblyPathName = possiblyPathName.substring(possiblyPathName.lastIndexOf('\\')+1);
return possiblyPathName;
}
@Override
public ParameterValue createValue(CLICommand command, String value) throws IOException, InterruptedException {
// capture the file to the server
@@ -66,6 +66,9 @@ public class FileParameterValue extends ParameterValue {
*/
private final String originalFileName;
/**
* Overrides the location in the build to place this file. Initially set to {@link #getName()}
*/
private String location;
@DataBoundConstructor
@@ -81,6 +84,7 @@ public class FileParameterValue extends ParameterValue {
super(name);
this.file = file;
this.originalFileName = originalFileName;
setLocation(name);
}
// post initialization hook
@@ -129,7 +133,7 @@ public class FileParameterValue extends ParameterValue {
return new BuildWrapper() {
@Override
public Environment setUp(AbstractBuild build, Launcher launcher, BuildListener listener) throws IOException, InterruptedException {
if (!StringUtils.isEmpty(file.getName())) {
if (!StringUtils.isEmpty(location)) {
listener.getLogger().println("Copying file to "+location);
FilePath locationFilePath = build.getWorkspace().child(location);
locationFilePath.getParent().mkdirs();
@@ -175,6 +179,10 @@ public class FileParameterValue extends ParameterValue {
return "(FileParameterValue) " + getName() + "='" + originalFileName + "'";
}
@Override public String getShortDescription() {
return name + "=" + originalFileName;
}
/**
* Serve this file parameter in response to a {@link StaplerRequest}.
*
+356 -24
View File
@@ -24,11 +24,11 @@
package hudson.model;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableListMultimap;
import com.thoughtworks.xstream.XStream;
import com.thoughtworks.xstream.converters.Converter;
import com.thoughtworks.xstream.converters.MarshallingContext;
import com.thoughtworks.xstream.converters.UnmarshallingContext;
import com.thoughtworks.xstream.converters.basic.DateConverter;
import com.thoughtworks.xstream.converters.collections.CollectionConverter;
import com.thoughtworks.xstream.io.HierarchicalStreamReader;
import com.thoughtworks.xstream.io.HierarchicalStreamWriter;
@@ -38,11 +38,13 @@ import hudson.BulkChange;
import hudson.Extension;
import hudson.model.listeners.ItemListener;
import hudson.model.listeners.SaveableListener;
import hudson.util.AtomicFileWriter;
import hudson.util.HexBinaryConverter;
import hudson.util.Iterators;
import hudson.util.PersistedList;
import hudson.util.RunList;
import hudson.util.XStream2;
import java.io.EOFException;
import jenkins.model.FingerprintFacet;
import jenkins.model.Jenkins;
import jenkins.model.TransientFingerprintFacetFactory;
@@ -51,6 +53,7 @@ import org.kohsuke.stapler.export.ExportedBean;
import java.io.File;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.AbstractCollection;
import java.util.ArrayList;
import java.util.Collection;
@@ -60,9 +63,13 @@ import java.util.Date;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.TreeMap;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.annotation.CheckForNull;
import org.xmlpull.v1.XmlPullParserException;
/**
* A file being tracked by Jenkins.
@@ -180,6 +187,10 @@ public class Fingerprint implements ModelObject, Saveable {
return false;
}
@Override public String toString() {
return name + " #" + number;
}
}
/**
@@ -242,6 +253,13 @@ public class Fingerprint implements ModelObject, Saveable {
return this.end<that.start ||that.end<this.start;
}
/**
* Returns true if two {@link Range}s do not share any common integer.
*/
public boolean isDisjoint(Range that) {
return this.end<=that.start || that.end<=this.start;
}
/**
* Returns true if this range only represents a single number.
*/
@@ -249,6 +267,13 @@ public class Fingerprint implements ModelObject, Saveable {
return end-1==start;
}
/**
* If this range contains every int that's in the other range, return true
*/
public boolean contains(Range that) {
return this.start<=that.start && that.end<=this.end;
}
/**
* Returns the {@link Range} that combines two ranges.
*/
@@ -258,10 +283,35 @@ public class Fingerprint implements ModelObject, Saveable {
Math.min(this.start,that.start),
Math.max(this.end ,that.end ));
}
/**
* Returns the {@link Range} that represents the intersection of the two.
*/
public Range intersect(Range that) {
assert !isDisjoint(that);
return new Range(
Math.max(this.start, that.start),
Math.min(this.end, that.end));
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Range that = (Range) o;
return start == that.start && end == that.end;
}
@Override
public int hashCode() {
return 31 * start + end;
}
}
/**
* Set of {@link Range}s.
* Set of {@link Range}s. Mutable.
*/
@ExportedBean(defaultVisibility=3)
public static final class RangeSet {
@@ -276,6 +326,11 @@ public class Fingerprint implements ModelObject, Saveable {
this.ranges = data;
}
private RangeSet(Range initial) {
this();
ranges.add(initial);
}
/**
* List all numbers in this range set, in the ascending order.
*/
@@ -363,6 +418,12 @@ public class Fingerprint implements ModelObject, Saveable {
ranges.add(new Range(n,n+1));
}
public synchronized void addAll(int... n) {
for (int i : n)
add(i);
}
private void checkCollapse(int i) {
if(i<0 || i==ranges.size()-1) return;
Range lhs = ranges.get(i);
@@ -418,6 +479,121 @@ public class Fingerprint implements ModelObject, Saveable {
this.ranges.addAll(that.ranges.subList(rhs,that.ranges.size()));
}
/**
* Updates this range set by the intersection of this range and the given range.
*
* @return true if this range set was modified as a result.
*/
public synchronized boolean retainAll(RangeSet that) {
List<Range> intersection = new ArrayList<Range>();
int lhs=0,rhs=0;
while(lhs<this.ranges.size() && rhs<that.ranges.size()) {
Range lr = this.ranges.get(lhs);
Range rr = that.ranges.get(rhs);
if(lr.end<=rr.start) {// lr has no overlap with that.ranges
lhs++;
continue;
}
if(rr.end<=lr.start) {// rr has no overlap with this.ranges
rhs++;
continue;
}
// overlap. figure out the intersection
Range v = lr.intersect(rr);
intersection.add(v);
// move on to the next pair
if (lr.end<rr.end) {
lhs++;
} else {
rhs++;
}
}
boolean same = this.ranges.equals(intersection);
if (!same) {
this.ranges.clear();
this.ranges.addAll(intersection);
return true;
} else {
return false;
}
}
/**
* Updates this range set by removing all the values in the given range set.
*
* @return true if this range set was modified as a result.
*/
public synchronized boolean removeAll(RangeSet that) {
boolean modified = false;
List<Range> sub = new ArrayList<Range>();
int lhs=0,rhs=0;
while(lhs<this.ranges.size() && rhs<that.ranges.size()) {
Range lr = this.ranges.get(lhs);
Range rr = that.ranges.get(rhs);
if(lr.end<=rr.start) {// lr has no overlap with that.ranges. lr stays
sub.add(lr);
lhs++;
continue;
}
if(rr.end<=lr.start) {// rr has no overlap with this.ranges
rhs++;
continue;
}
// some overlap between lr and rr
assert !lr.isDisjoint(rr);
modified = true;
if (rr.contains(lr)) {
// lr completely removed by rr
lhs++;
continue;
}
// we want to look at A and B below, if they are non-null.
// |------------| lr
// |-----| rr
// A B
//
// note that lr and rr could be something like or the other way around
// |------------| lr
// |------------| rr
// A (no B)
if (lr.start<rr.start) {// if A is non-empty, that will stay
Range a = new Range(lr.start, rr.start);
sub.add(a);
}
if (rr.end<lr.end) {// if B is non-empty
// we still need to check that with that.ranges, so keep it in the place of lr.
// how much of them will eventually stay is up to the remainder of that.ranges
this.ranges.set(lhs,new Range(rr.end,lr.end));
rhs++;
} else {
// if B is empty, we are done considering lr
lhs++;
}
}
if (!modified) return false; // no changes
// whatever that remains in lhs will survive
sub.addAll(this.ranges.subList(lhs,this.ranges.size()));
this.ranges.clear();
this.ranges.addAll(sub);
return true;
}
@Override
public synchronized String toString() {
StringBuilder buf = new StringBuilder();
@@ -428,6 +604,20 @@ public class Fingerprint implements ModelObject, Saveable {
return buf.toString();
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
return ranges.equals(((RangeSet) o).ranges);
}
@Override
public int hashCode() {
return ranges.hashCode();
}
public synchronized boolean isEmpty() {
return ranges.isEmpty();
}
@@ -503,7 +693,10 @@ public class Fingerprint implements ModelObject, Saveable {
public void marshal(Object source, HierarchicalStreamWriter writer, MarshallingContext context) {
RangeSet src = (RangeSet) source;
writer.setValue(serialize(src));
}
static String serialize(RangeSet src) {
StringBuilder buf = new StringBuilder(src.ranges.size()*10);
for (Range r : src.ranges) {
if(buf.length()>0) buf.append(',');
@@ -512,7 +705,7 @@ public class Fingerprint implements ModelObject, Saveable {
else
buf.append(r.start).append('-').append(r.end-1);
}
writer.setValue(buf.toString());
return buf.toString();
}
public Object unmarshal(HierarchicalStreamReader reader, final UnmarshallingContext context) {
@@ -555,6 +748,8 @@ public class Fingerprint implements ModelObject, Saveable {
}
}
}
private static final DateConverter DATE_CONVERTER = new DateConverter();
private final Date timestamp;
@@ -573,7 +768,7 @@ public class Fingerprint implements ModelObject, Saveable {
*/
private final Hashtable<String,RangeSet> usages = new Hashtable<String,RangeSet>();
private PersistedList<FingerprintFacet> facets = new PersistedList<FingerprintFacet>(this);
PersistedList<FingerprintFacet> facets = new PersistedList<FingerprintFacet>(this);
/**
* Lazily computed immutable {@link FingerprintFacet}s created from {@link TransientFingerprintFacetFactory}.
@@ -581,11 +776,15 @@ public class Fingerprint implements ModelObject, Saveable {
private transient volatile List<FingerprintFacet> transientFacets = null;
public Fingerprint(Run build, String fileName, byte[] md5sum) throws IOException {
this.original = build==null ? null : new BuildPtr(build);
this(build==null ? null : new BuildPtr(build), fileName, md5sum);
save();
}
Fingerprint(BuildPtr original, String fileName, byte[] md5sum) {
this.original = original;
this.md5sum = md5sum;
this.fileName = fileName;
this.timestamp = new Date();
save();
}
/**
@@ -696,14 +895,19 @@ public class Fingerprint implements ModelObject, Saveable {
}
public synchronized void add(AbstractBuild b) throws IOException {
add(b.getParent().getFullName(),b.getNumber());
add(b.getParent().getFullName(), b.getNumber());
}
/**
* Records that a build of a job has used this file.
*/
public synchronized void add(String jobFullName, int n) throws IOException {
synchronized(usages) {
addWithoutSaving(jobFullName, n);
save();
}
void addWithoutSaving(String jobFullName, int n) {
synchronized(usages) { // TODO why not synchronized (this) like some, though not all, other accesses?
RangeSet r = usages.get(jobFullName);
if(r==null) {
r = new RangeSet();
@@ -711,7 +915,6 @@ public class Fingerprint implements ModelObject, Saveable {
}
r.add(n);
}
save();
}
/**
@@ -742,6 +945,69 @@ public class Fingerprint implements ModelObject, Saveable {
return false;
}
/**
* Trim off references to non-existent builds and jobs, thereby making the fingerprint smaller.
*
* @return true
* if this record was modified.
*/
public synchronized boolean trim() throws IOException {
boolean modified = false;
for (Entry<String,RangeSet> e : new Hashtable<String,RangeSet>(usages).entrySet()) {// copy because we mutate
Job j = Jenkins.getInstance().getItemByFullName(e.getKey(),Job.class);
if(j==null) {// no such job any more. recycle the record
modified = true;
usages.remove(e.getKey());
continue;
}
Run firstBuild = j.getFirstBuild();
if(firstBuild==null) {// no builds. recycle the whole record
modified = true;
usages.remove(e.getKey());
continue;
}
RangeSet cur = e.getValue();
// builds that are around without the keepLog flag on are normally clustered together (in terms of build #)
// so our basic strategy is to discard everything up to the first ephemeral build, except those builds
// that are marked as kept
RangeSet kept = new RangeSet();
Run r = firstBuild;
while (r!=null && r.isKeepLog()) {
kept.add(r.getNumber());
r = r.getNextBuild();
}
if (r==null) {
// all the build records are permanently kept ones, so we'll just have to keep 'kept' out of whatever currently in 'cur'
modified |= cur.retainAll(kept);
} else {
// otherwise we are ready to discard [0,r.number) except those marked as 'kept'
RangeSet discarding = new RangeSet(new Range(-1,r.getNumber()));
discarding.removeAll(kept);
modified |= cur.removeAll(discarding);
}
if (cur.isEmpty()) {
usages.remove(e.getKey());
modified = true;
}
}
if (modified) {
if (logger.isLoggable(Level.FINE)) {
logger.log(Level.FINE, "Saving trimmed {0}", getFingerprintFile(md5sum));
}
save();
}
return modified;
}
/**
* Gets the associated {@link FingerprintFacet}s.
*
@@ -774,21 +1040,13 @@ public class Fingerprint implements ModelObject, Saveable {
@Override
public boolean add(FingerprintFacet e) {
try {
facets.add(e);
return true;
} catch (IOException x) {
throw new Error(x);
}
facets.add(e);
return true;
}
@Override
public boolean remove(Object o) {
try {
return facets.remove((FingerprintFacet)o);
} catch (IOException x) {
throw new Error(x);
}
return facets.remove(o);
}
@Override
@@ -838,13 +1096,66 @@ public class Fingerprint implements ModelObject, Saveable {
start = System.currentTimeMillis();
File file = getFingerprintFile(md5sum);
getConfigFile(file).write(this);
save(file);
SaveableListener.fireOnChange(this, getConfigFile(file));
if(logger.isLoggable(Level.FINE))
logger.fine("Saving fingerprint "+file+" took "+(System.currentTimeMillis()-start)+"ms");
}
void save(File file) throws IOException {
if (facets.isEmpty()) {
file.getParentFile().mkdirs();
// JENKINS-16301: fast path for the common case.
AtomicFileWriter afw = new AtomicFileWriter(file);
try {
PrintWriter w = new PrintWriter(afw);
w.println("<?xml version='1.0' encoding='UTF-8'?>");
w.println("<fingerprint>");
w.print(" <timestamp>");
w.print(DATE_CONVERTER.toString(timestamp));
w.println("</timestamp>");
if (original != null) {
w.println(" <original>");
w.print(" <name>");
w.print(Util.xmlEscape(original.name));
w.println("</name>");
w.print(" <number>");
w.print(original.number);
w.println("</number>");
w.println(" </original>");
}
w.print(" <md5sum>");
w.print(Util.toHexString(md5sum));
w.println("</md5sum>");
w.print(" <fileName>");
w.print(Util.xmlEscape(fileName));
w.println("</fileName>");
w.println(" <usages>");
for (Map.Entry<String,RangeSet> e : usages.entrySet()) {
w.println(" <entry>");
w.print(" <string>");
w.print(Util.xmlEscape(e.getKey()));
w.println("</string>");
w.print(" <ranges>");
w.print(RangeSet.ConverterImpl.serialize(e.getValue()));
w.println("</ranges>");
w.println(" </entry>");
}
w.println(" </usages>");
w.println(" <facets/>");
w.print("</fingerprint>");
w.flush();
afw.commit();
} finally {
afw.abort();
}
} else {
// Slower fallback that can persist facets.
getConfigFile(file).write(this);
}
}
/**
* Update references to a renamed job in the fingerprint
*/
@@ -894,10 +1205,10 @@ public class Fingerprint implements ModelObject, Saveable {
/**
* Loads a {@link Fingerprint} from a file in the image.
*/
/*package*/ static Fingerprint load(byte[] md5sum) throws IOException {
/*package*/ static @CheckForNull Fingerprint load(byte[] md5sum) throws IOException {
return load(getFingerprintFile(md5sum));
}
/*package*/ static Fingerprint load(File file) throws IOException {
/*package*/ static @CheckForNull Fingerprint load(File file) throws IOException {
XmlFile configFile = getConfigFile(file);
if(!configFile.exists())
return null;
@@ -923,7 +1234,13 @@ public class Fingerprint implements ModelObject, Saveable {
// generally we don't want to wipe out user data just because we can't load it,
// but if the file size is 0, which is what's reported in HUDSON-2012, then it seems
// like recovering it silently by deleting the file is not a bad idea.
logger.log(Level.WARNING, "Size zero fingerprint. Disk corruption? "+configFile,e);
logger.log(Level.WARNING, "Size zero fingerprint. Disk corruption? {0}", configFile);
file.delete();
return null;
}
String parseError = messageOfParseException(e);
if (parseError != null) {
logger.log(Level.WARNING, "Malformed XML in {0}: {1}", new Object[] {configFile, parseError});
file.delete();
return null;
}
@@ -931,6 +1248,21 @@ public class Fingerprint implements ModelObject, Saveable {
throw e;
}
}
private static String messageOfParseException(Throwable t) {
if (t instanceof XmlPullParserException || t instanceof EOFException) {
return t.getMessage();
}
Throwable t2 = t.getCause();
if (t2 != null) {
return messageOfParseException(t2);
} else {
return null;
}
}
@Override public String toString() {
return "Fingerprint[original=" + original + ",hash=" + getHashString() + ",fileName=" + fileName + ",timestamp=" + DATE_CONVERTER.toString(timestamp) + ",usages=" + new TreeMap<String,RangeSet>(usages) + ",facets=" + facets + "]";
}
private static final XStream XSTREAM = new XStream2();
static {
@@ -61,7 +61,7 @@ public final class FingerprintCleanupThread extends AsyncPeriodicWork {
return Jenkins.getInstance().getExtensionList(AsyncPeriodicWork.class).get(FingerprintCleanupThread.class);
}
protected void execute(TaskListener listener) {
public void execute(TaskListener listener) {
int numFiles = 0;
File root = new File(Jenkins.getInstance().getRootDir(),"fingerprints");
@@ -100,14 +100,21 @@ public final class FingerprintCleanupThread extends AsyncPeriodicWork {
private boolean check(File fingerprintFile) {
try {
Fingerprint fp = Fingerprint.load(fingerprintFile);
if(!fp.isAlive()) {
if (fp == null || !fp.isAlive()) {
logger.fine("deleting obsolete " + fingerprintFile);
fingerprintFile.delete();
return true;
} else {
// get the fingerprint in the official map so have the changes visible to Jenkins
// otherwise the mutation made in FingerprintMap can override our trimming.
logger.finer("possibly trimming " + fingerprintFile);
fp = Jenkins.getInstance()._getFingerprint(fp.getHashString());
return fp.trim();
}
} catch (IOException e) {
logger.log(Level.WARNING, "Failed to process "+fingerprintFile, e);
return false;
}
return false;
}
private static final FileFilter LENGTH2DIR_FILTER = new FileFilter() {
@@ -25,6 +25,8 @@ package hudson.model;
import hudson.Extension;
import jenkins.model.Jenkins;
import org.kohsuke.accmod.Restricted;
import org.kohsuke.accmod.restrictions.NoExternalUse;
/**
* Free-style software project.
@@ -50,9 +52,14 @@ public class FreeStyleProject extends Project<FreeStyleProject,FreeStyleBuild> i
}
public DescriptorImpl getDescriptor() {
return DESCRIPTOR;
return (DescriptorImpl)Jenkins.getInstance().getDescriptorOrDie(getClass());
}
/**
* Descriptor is instantiated as a field purely for backward compatibility.
* Do not do this in your code. Put @Extension on your DescriptorImpl class instead.
*/
@Restricted(NoExternalUse.class)
@Extension(ordinal=1000)
public static final DescriptorImpl DESCRIPTOR = new DescriptorImpl();
@@ -26,6 +26,7 @@ package hudson.model;
import java.io.IOException;
import java.util.Collection;
import java.io.File;
import javax.annotation.CheckForNull;
/**
* Represents a grouping inherent to a kind of {@link Item}s.
@@ -64,9 +65,9 @@ public interface ItemGroup<T extends Item> extends PersistenceRoot, ModelObject
String getUrlChildPrefix();
/**
* Gets the {@link Item} inside this group that has a given name.
* Gets the {@link Item} inside this group that has a given name, or null if it does not exist.
*/
T getItem(String name);
@CheckForNull T getItem(String name);
/**
* Assigns the {@link Item#getRootDir() root directory} for children.
@@ -189,9 +189,6 @@ public abstract class ItemGroupMixIn {
/**
* Copies an existing {@link TopLevelItem} to a new name.
*
* The caller is responsible for calling {@link ItemListener#fireOnCopied(Item, Item)}. This method
* cannot do that because it doesn't know how to make the newly added item reachable from the parent.
*/
@SuppressWarnings({"unchecked"})
public synchronized <T extends TopLevelItem> T copy(T src, String name) throws IOException {
@@ -213,7 +210,7 @@ public abstract class ItemGroupMixIn {
add(result);
ItemListener.fireOnCopied(src,result);
Hudson.getInstance().rebuildDependencyGraph();
Jenkins.getInstance().rebuildDependencyGraphAsync();
return result;
}
@@ -239,7 +236,7 @@ public abstract class ItemGroupMixIn {
add(result);
ItemListener.fireOnCreated(result);
Jenkins.getInstance().rebuildDependencyGraph();
Jenkins.getInstance().rebuildDependencyGraphAsync();
return result;
} catch (IOException e) {
@@ -270,6 +267,7 @@ public abstract class ItemGroupMixIn {
}
item.save();
add(item);
Jenkins.getInstance().rebuildDependencyGraphAsync();
if (notify)
ItemListener.fireOnCreated(item);
+44 -1
View File
@@ -166,7 +166,7 @@ public class Items {
while(tokens.hasMoreTokens()) {
String relativeName = tokens.nextToken().trim();
String canonicalName = getCanonicalName(context, relativeName);
if (canonicalName.startsWith(oldFullName)) {
if (canonicalName.equals(oldFullName) || canonicalName.startsWith(oldFullName+'/')) {
String newCanonicalName = newFullName + canonicalName.substring(oldFullName.length());
// relative name points to the renamed item, let's compute the new relative name
newValue.add( computeRelativeNameAfterRenaming(canonicalName, newCanonicalName, relativeName) );
@@ -235,6 +235,49 @@ public class Items {
public static XmlFile getConfigFile(Item item) {
return getConfigFile(item.getRootDir());
}
/**
* Gets all the {@link Item}s recursively in the {@link ItemGroup} tree
* and filter them by the given type.
*
* @since 1.512
*/
public static <T extends Item> List<T> getAllItems(final ItemGroup root, Class<T> type) {
List<T> r = new ArrayList<T>();
Stack<ItemGroup> q = new Stack<ItemGroup>();
q.push(root);
while(!q.isEmpty()) {
ItemGroup<?> parent = q.pop();
for (Item i : parent.getItems()) {
if(type.isInstance(i)) {
if (i.hasPermission(Item.READ))
r.add(type.cast(i));
}
if(i instanceof ItemGroup)
q.push((ItemGroup)i);
}
}
// sort by relative name, ignoring case
Collections.sort(r, new Comparator<T>() {
@Override
public int compare(T o1, T o2) {
if (o1 == null) {
if (o2 == null) {
return 0;
}
return 1;
}
if (o2 == null) {
return -1;
}
return o1.getRelativeNameFrom(root).compareToIgnoreCase(o2.getRelativeNameFrom(root));
}
});
return r;
}
/**
* Used to load/save job configuration.
+7 -3
View File
@@ -105,9 +105,13 @@ public final class JDK extends ToolInstallation implements NodeSpecific<JDK>, En
* @deprecated as of 1.460. Use {@link #buildEnvVars(EnvVars)}
*/
public void buildEnvVars(Map<String,String> env) {
String home = getHome();
if (home == null) {
return;
}
// see EnvVars javadoc for why this adds PATH.
env.put("PATH+JDK",getHome()+"/bin");
env.put("JAVA_HOME",getHome());
env.put("PATH+JDK",home+"/bin");
env.put("JAVA_HOME", home);
}
/**
@@ -149,7 +153,7 @@ public final class JDK extends ToolInstallation implements NodeSpecific<JDK>, En
public static class DescriptorImpl extends ToolDescriptor<JDK> {
public String getDisplayName() {
return "JDK"; // XXX I18N
return "JDK"; // TODO I18N
}
public @Override JDK[] getInstallations() {
+86 -26
View File
@@ -26,7 +26,7 @@ package hudson.model;
import com.google.common.base.Function;
import com.google.common.collect.Collections2;
import com.infradna.tool.bridge_method_injector.WithBridgeMethods;
import hudson.BulkChange;
import hudson.EnvVars;
import hudson.Extension;
import hudson.ExtensionPoint;
@@ -64,11 +64,11 @@ import hudson.widgets.Widget;
import jenkins.model.BuildDiscarder;
import jenkins.model.Jenkins;
import jenkins.model.ProjectNamingStrategy;
import jenkins.scm.SCMCheckoutStrategy;
import jenkins.security.HexStringConfidentialKey;
import jenkins.util.io.OnMaster;
import net.sf.json.JSONException;
import net.sf.json.JSONObject;
import org.jfree.chart.ChartFactory;
import org.jfree.chart.JFreeChart;
import org.jfree.chart.axis.CategoryAxis;
@@ -89,6 +89,7 @@ import org.kohsuke.stapler.export.Exported;
import org.kohsuke.stapler.interceptor.RequirePOST;
import javax.servlet.ServletException;
import java.awt.*;
import java.io.*;
import java.net.URLEncoder;
@@ -127,6 +128,13 @@ public abstract class Job<JobT extends Job<JobT, RunT>, RunT extends Run<JobT, R
*/
private transient volatile boolean holdOffBuildUntilSave;
/**
* {@link ItemListener}s can, and do, modify the job with a corresponding save which will clear
* {@link #holdOffBuildUntilSave} prematurely. The {@link LastItemListener} is responsible for
* clearing this flag as the last item listener.
*/
private transient volatile boolean holdOffBuildUntilUserSave;
private volatile BuildDiscarder logRotator;
/**
@@ -152,7 +160,7 @@ public abstract class Job<JobT extends Job<JobT, RunT>, RunT extends Run<JobT, R
@Override
public synchronized void save() throws IOException {
super.save();
holdOffBuildUntilSave = false;
holdOffBuildUntilSave = holdOffBuildUntilUserSave;
}
@Override
@@ -206,7 +214,25 @@ public abstract class Job<JobT extends Job<JobT, RunT>, RunT extends Run<JobT, R
super.onCopiedFrom(src);
synchronized (this) {
this.nextBuildNumber = 1; // reset the next build number
this.holdOffBuildUntilSave = true;
this.holdOffBuildUntilUserSave = true;
this.holdOffBuildUntilSave = this.holdOffBuildUntilUserSave;
}
}
@Extension(ordinal = -Double.MAX_VALUE)
public static class LastItemListener extends ItemListener {
@Override
public void onCopied(Item src, Item item) {
// If any of the other ItemListeners modify the job, they effect
// a save, which will clear the holdOffBuildUntilUserSave and
// causing a regression of JENKINS-2494
if (item instanceof Job) {
Job job = (Job) item;
synchronized (job) {
job.holdOffBuildUntilUserSave = false;
}
}
}
}
@@ -228,7 +254,7 @@ public abstract class Job<JobT extends Job<JobT, RunT>, RunT extends Run<JobT, R
return new TextFile(new File(this.getRootDir(), "nextBuildNumber"));
}
protected boolean isHoldOffBuildUntilSave() {
public synchronized boolean isHoldOffBuildUntilSave() {
return holdOffBuildUntilSave;
}
@@ -343,6 +369,11 @@ public abstract class Job<JobT extends Job<JobT, RunT>, RunT extends Run<JobT, R
// see http://www.nabble.com/Run-Job-with-JDK-1.4.2-tf4468601.html
env.put("CLASSPATH","");
// apply them in a reverse order so that higher ordinal ones can modify values added by lower ordinal ones
for (EnvironmentContributor ec : EnvironmentContributor.all().reverseView())
ec.buildEnvironmentFor(this,env,listener);
return env;
}
@@ -809,11 +840,7 @@ public abstract class Job<JobT extends Job<JobT, RunT>, RunT extends Run<JobT, R
@Exported
@QuickSilver
public RunT getLastUnsuccessfulBuild() {
RunT r = getLastBuild();
while (r != null
&& (r.isBuilding() || r.getResult() == Result.SUCCESS))
r = r.getPreviousBuild();
return r;
return (RunT)Permalink.LAST_UNSUCCESSFUL_BUILD.resolve(this);
}
/**
@@ -823,11 +850,7 @@ public abstract class Job<JobT extends Job<JobT, RunT>, RunT extends Run<JobT, R
@Exported
@QuickSilver
public RunT getLastUnstableBuild() {
RunT r = getLastBuild();
while (r != null
&& (r.isBuilding() || r.getResult() != Result.UNSTABLE))
r = r.getPreviousBuild();
return r;
return (RunT)Permalink.LAST_UNSTABLE_BUILD.resolve(this);
}
/**
@@ -837,11 +860,7 @@ public abstract class Job<JobT extends Job<JobT, RunT>, RunT extends Run<JobT, R
@Exported
@QuickSilver
public RunT getLastStableBuild() {
RunT r = getLastBuild();
while (r != null
&& (r.isBuilding() || r.getResult().isWorseThan(Result.SUCCESS)))
r = r.getPreviousBuild();
return r;
return (RunT)Permalink.LAST_STABLE_BUILD.resolve(this);
}
/**
@@ -850,10 +869,7 @@ public abstract class Job<JobT extends Job<JobT, RunT>, RunT extends Run<JobT, R
@Exported
@QuickSilver
public RunT getLastFailedBuild() {
RunT r = getLastBuild();
while (r != null && (r.isBuilding() || r.getResult() != Result.FAILURE))
r = r.getPreviousBuild();
return r;
return (RunT)Permalink.LAST_FAILED_BUILD.resolve(this);
}
/**
@@ -890,8 +906,52 @@ public abstract class Job<JobT extends Job<JobT, RunT>, RunT extends Run<JobT, R
return result;
}
/**
* Returns candidate build for calculating the estimated duration of the current run.
*
* Returns the 3 last successful (stable or unstable) builds, if there are any.
* Failing to find 3 of those, it will return up to 3 last unsuccessful builds.
*
* In any case it will not go more than 6 builds into the past to avoid costly build loading.
*/
@SuppressWarnings("unchecked")
protected List<RunT> getEstimatedDurationCandidates() {
List<RunT> candidates = new ArrayList<RunT>(3);
RunT lastSuccessful = (RunT) Permalink.LAST_SUCCESSFUL_BUILD.resolve(this);
int lastSuccessfulNumber = -1;
if (lastSuccessful != null) {
candidates.add(lastSuccessful);
lastSuccessfulNumber = lastSuccessful.getNumber();
}
int i = 0;
RunT r = (RunT) Permalink.LAST_BUILD.resolve(this);
List<RunT> fallbackCandidates = new ArrayList<RunT>(3);
while (r != null && candidates.size() < 3 && i < 6) {
if (!r.isBuilding() && r.getResult() != null && r.getNumber() != lastSuccessfulNumber) {
Result result = r.getResult();
if (result.isBetterOrEqualTo(Result.UNSTABLE)) {
candidates.add(r);
} else if (result.isCompleteBuild()) {
fallbackCandidates.add(r);
}
}
i++;
r = r.getPreviousBuild();
}
while (candidates.size() < 3) {
if (fallbackCandidates.isEmpty())
break;
RunT run = fallbackCandidates.remove(0);
candidates.add(run);
}
return candidates;
}
public long getEstimatedDuration() {
List<RunT> builds = getLastBuildsOverThreshold(3, Result.UNSTABLE);
List<RunT> builds = getEstimatedDurationCandidates();
if(builds.isEmpty()) return -1;
@@ -933,7 +993,7 @@ public abstract class Job<JobT extends Job<JobT, RunT>, RunT extends Run<JobT, R
if (lastBuild != null)
return lastBuild.getIconColor();
else
return BallColor.GREY;
return BallColor.NOTBUILT;
}
/**
@@ -46,4 +46,8 @@ public class JobParameterValue extends ParameterValue {
env.put(name,job.toString());
env.put(name.toUpperCase(Locale.ENGLISH),job.toString()); // backward compatibility pre 1.345
}
@Override public String getShortDescription() {
return name + "=" + job.getFullDisplayName();
}
}
+67 -2
View File
@@ -39,11 +39,17 @@ import hudson.model.labels.LabelExpressionLexer;
import hudson.model.labels.LabelExpressionParser;
import hudson.model.labels.LabelOperatorPrecedence;
import hudson.model.labels.LabelVisitor;
import hudson.security.ACL;
import hudson.slaves.NodeProvisioner;
import hudson.slaves.Cloud;
import hudson.util.QuotedStringTokenizer;
import hudson.util.VariableResolver;
import jenkins.model.Jenkins;
import jenkins.model.ModelObjectWithChildren;
import org.acegisecurity.context.SecurityContext;
import org.acegisecurity.context.SecurityContextHolder;
import org.kohsuke.stapler.StaplerRequest;
import org.kohsuke.stapler.StaplerResponse;
import org.kohsuke.stapler.export.Exported;
import org.kohsuke.stapler.export.ExportedBean;
@@ -54,6 +60,7 @@ import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.Collection;
import java.util.Stack;
import java.util.TreeSet;
import com.thoughtworks.xstream.converters.Converter;
@@ -70,7 +77,7 @@ import com.thoughtworks.xstream.io.HierarchicalStreamReader;
* @see Jenkins#getLabel(String)
*/
@ExportedBean
public abstract class Label extends Actionable implements Comparable<Label>, ModelObject {
public abstract class Label extends Actionable implements Comparable<Label>, ModelObjectWithChildren {
/**
* Display name of this label.
*/
@@ -336,12 +343,62 @@ public abstract class Label extends Actionable implements Comparable<Label>, Mod
public List<AbstractProject> getTiedJobs() {
List<AbstractProject> r = new ArrayList<AbstractProject>();
for (AbstractProject<?,?> p : Jenkins.getInstance().getAllItems(AbstractProject.class)) {
if(this.equals(p.getAssignedLabel()))
if(p instanceof TopLevelItem && this.equals(p.getAssignedLabel()))
r.add(p);
}
return r;
}
/**
* Returns a count of projects that are tied on this node. In a system without security this should be the same
* as {@code getTiedJobs().size()} but significantly faster as it involves fewer temporary objects and avoids
* sorting the intermediary list. In a system with security, this will likely return a higher value as it counts
* all jobs (mostly) irrespective of access.
* @return a count of projects that are tied on this node.
*/
public int getTiedJobCount() {
// denormalize for performance
// we don't need to respect security as much when returning a simple count
SecurityContext context = ACL.impersonate(ACL.SYSTEM);
try {
int result = 0;
// top level gives the map without checking security of items in the map
// therefore best performance
for (TopLevelItem topLevelItem : Jenkins.getInstance().getItemMap().values()) {
if (topLevelItem instanceof AbstractProject) {
final AbstractProject project = (AbstractProject) topLevelItem;
if (this.equals(project.getAssignedLabel())) {
result++;
}
}
if (topLevelItem instanceof ItemGroup) {
Stack<ItemGroup> q = new Stack<ItemGroup>();
q.push((ItemGroup) topLevelItem);
while (!q.isEmpty()) {
ItemGroup<?> parent = q.pop();
// we run the risk of permissions checks in ItemGroup#getItems()
// not much we can do here though
for (Item i : parent.getItems()) {
if (i instanceof AbstractProject) {
final AbstractProject project = (AbstractProject) i;
if (this.equals(project.getAssignedLabel())) {
result++;
}
}
if (i instanceof ItemGroup) {
q.push((ItemGroup) i);
}
}
}
}
}
return result;
} finally {
SecurityContextHolder.setContext(context);
}
}
public boolean contains(Node node) {
return getNodes().contains(node);
}
@@ -455,6 +512,14 @@ public abstract class Label extends Actionable implements Comparable<Label>, Mod
return name;
}
public ContextMenu doChildrenContextMenu(StaplerRequest request, StaplerResponse response) throws Exception {
ContextMenu menu = new ContextMenu();
for (Node node : getNodes()) {
menu.add(node);
}
return menu;
}
public static final class ConverterImpl implements Converter {
public ConverterImpl() {
}
+101 -46
View File
@@ -26,6 +26,7 @@ package hudson.model;
import hudson.Extension;
import hudson.Util;
import hudson.diagnosis.OldDataMonitor;
import hudson.model.Descriptor.FormException;
import hudson.util.CaseInsensitiveComparator;
import hudson.util.DescribableList;
@@ -33,6 +34,17 @@ import hudson.util.FormValidation;
import hudson.util.HttpResponses;
import hudson.views.ListViewColumn;
import hudson.views.ViewJobFilter;
import java.io.IOException;
import java.util.*;
import java.util.regex.Pattern;
import java.util.regex.PatternSyntaxException;
import javax.annotation.concurrent.GuardedBy;
import javax.servlet.ServletException;
import net.sf.json.JSONObject;
import org.kohsuke.stapler.DataBoundConstructor;
import org.kohsuke.stapler.HttpResponse;
import org.kohsuke.stapler.QueryParameter;
@@ -40,17 +52,6 @@ import org.kohsuke.stapler.StaplerRequest;
import org.kohsuke.stapler.StaplerResponse;
import org.kohsuke.stapler.interceptor.RequirePOST;
import javax.annotation.concurrent.GuardedBy;
import javax.servlet.ServletException;
import java.io.IOException;
import java.util.ArrayList;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.SortedSet;
import java.util.TreeSet;
import java.util.regex.Pattern;
import java.util.regex.PatternSyntaxException;
/**
* Displays {@link Job}s in a flat list view.
*
@@ -62,7 +63,7 @@ public class ListView extends View implements Saveable {
* List of job names. This is what gets serialized.
*/
@GuardedBy("this")
/*package*/ final SortedSet<String> jobNames = new TreeSet<String>(CaseInsensitiveComparator.INSTANCE);
/*package*/ /*almost-final*/ SortedSet<String> jobNames = new TreeSet<String>(CaseInsensitiveComparator.INSTANCE);
private DescribableList<ViewJobFilter, Descriptor<ViewJobFilter>> jobFilters;
@@ -73,6 +74,11 @@ public class ListView extends View implements Saveable {
*/
private String includeRegex;
/**
* Whether to recurse in ItemGroups
*/
private boolean recurse;
/**
* Compiled include pattern from the includeRegex string.
*/
@@ -97,8 +103,17 @@ public class ListView extends View implements Saveable {
}
private Object readResolve() {
if(includeRegex!=null)
includePattern = Pattern.compile(includeRegex);
if(includeRegex!=null) {
try {
includePattern = Pattern.compile(includeRegex);
} catch (PatternSyntaxException x) {
includeRegex = null;
OldDataMonitor.report(this, Collections.<Throwable>singleton(x));
}
}
if (jobNames == null) {
jobNames = new TreeSet<String>(CaseInsensitiveComparator.INSTANCE);
}
initColumns();
initJobFilters();
return this;
@@ -138,24 +153,19 @@ public class ListView extends View implements Saveable {
*/
public List<TopLevelItem> getItems() {
SortedSet<String> names;
List<TopLevelItem> items = new ArrayList<TopLevelItem>();
synchronized (this) {
names = new TreeSet<String>(jobNames);
}
if (includePattern != null) {
for (Item item : getOwnerItemGroup().getItems()) {
String itemName = item.getName();
if (includePattern.matcher(itemName).matches()) {
names.add(itemName);
}
}
}
ItemGroup<? extends TopLevelItem> parent = getOwnerItemGroup();
List<TopLevelItem> parentItems = new ArrayList<TopLevelItem>(parent.getItems());
includeItems(parent, parentItems, names);
Boolean statusFilter = this.statusFilter; // capture the value to isolate us from concurrent update
List<TopLevelItem> items = new ArrayList<TopLevelItem>(names.size());
for (TopLevelItem item : getOwnerItemGroup().getItems()) {
if (!names.contains(item.getName())) continue;
for (TopLevelItem item : Items.getAllItems(getOwnerItemGroup(), TopLevelItem.class)) {
if (!names.contains(item.getRelativeNameFrom(getOwnerItemGroup()))) continue;
// Add if no status filter or filter matches enabled/disabled status:
if(statusFilter == null || !(item instanceof AbstractProject)
|| ((AbstractProject)item).isDisabled() ^ statusFilter)
@@ -164,7 +174,7 @@ public class ListView extends View implements Saveable {
// check the filters
Iterable<ViewJobFilter> jobFilters = getJobFilters();
List<TopLevelItem> allItems = new ArrayList<TopLevelItem>(getOwnerItemGroup().getItems());
List<TopLevelItem> allItems = new ArrayList<TopLevelItem>(parentItems);
for (ViewJobFilter jobFilter: jobFilters) {
items = jobFilter.filter(items, allItems, this);
}
@@ -173,10 +183,35 @@ public class ListView extends View implements Saveable {
return items;
}
public synchronized boolean contains(TopLevelItem item) {
return jobNames.contains(item.getName());
@Override
public boolean contains(TopLevelItem item) {
return getItems().contains(item);
}
private void includeItems(ItemGroup<? extends TopLevelItem> root, Collection<? extends Item> parentItems, SortedSet<String> names) {
if (includePattern != null) {
for (Item item : parentItems) {
if (recurse && item instanceof ItemGroup) {
ItemGroup<?> ig = (ItemGroup<?>) item;
includeItems(root, ig.getItems(), names);
}
if (item instanceof TopLevelItem) {
String itemName = item.getRelativeNameFrom(root);
if (includePattern.matcher(itemName).matches()) {
names.add(itemName);
}
}
}
}
}
public synchronized boolean jobNamesContains(TopLevelItem item) {
if (item == null) return false;
return jobNames.contains(item.getRelativeNameFrom(getOwnerItemGroup()));
}
/**
* Adds the given item to this view.
@@ -185,7 +220,7 @@ public class ListView extends View implements Saveable {
*/
public void add(TopLevelItem item) throws IOException {
synchronized (this) {
jobNames.add(item.getName());
jobNames.add(item.getRelativeNameFrom(getOwnerItemGroup()));
}
save();
}
@@ -193,6 +228,17 @@ public class ListView extends View implements Saveable {
public String getIncludeRegex() {
return includeRegex;
}
public boolean isRecurse() {
return recurse;
}
/*
* For testing purposes
*/
void setRecurse(boolean recurse) {
this.recurse = recurse;
}
/**
* Filter by enabled/disabled status of jobs.
@@ -208,7 +254,7 @@ public class ListView extends View implements Saveable {
TopLevelItem item = ((ModifiableItemGroup<? extends TopLevelItem>)ig).doCreateItem(req, rsp);
if(item!=null) {
synchronized (this) {
jobNames.add(item.getName());
jobNames.add(item.getRelativeNameFrom(getOwnerItemGroup()));
}
owner.save();
}
@@ -257,38 +303,47 @@ public class ListView extends View implements Saveable {
*/
@Override
protected void submit(StaplerRequest req) throws ServletException, FormException, IOException {
JSONObject json = req.getSubmittedForm();
synchronized (this) {
recurse = json.optBoolean("recurse", true);
jobNames.clear();
for (TopLevelItem item : getOwnerItemGroup().getItems()) {
if(req.getParameter(item.getName())!=null)
jobNames.add(item.getName());
Iterable<? extends TopLevelItem> items;
if (recurse) {
items = Items.getAllItems(getOwnerItemGroup(), TopLevelItem.class);
} else {
items = getOwnerItemGroup().getItems();
}
for (TopLevelItem item : items) {
String relativeNameFrom = item.getRelativeNameFrom(getOwnerItemGroup());
if(req.getParameter(relativeNameFrom)!=null) {
jobNames.add(relativeNameFrom);
}
}
}
if (req.getParameter("useincluderegex") != null) {
includeRegex = Util.nullify(req.getParameter("includeRegex"));
if (includeRegex == null)
includePattern = null;
else
includePattern = Pattern.compile(includeRegex);
} else {
includeRegex = null;
includePattern = null;
}
setIncludeRegex(req.getParameter("useincluderegex") != null ? req.getParameter("includeRegex") : null);
if (columns == null) {
columns = new DescribableList<ListViewColumn,Descriptor<ListViewColumn>>(this);
}
columns.rebuildHetero(req, req.getSubmittedForm(), ListViewColumn.all(), "columns");
columns.rebuildHetero(req, json, ListViewColumn.all(), "columns");
if (jobFilters == null) {
jobFilters = new DescribableList<ViewJobFilter,Descriptor<ViewJobFilter>>(this);
}
jobFilters.rebuildHetero(req, req.getSubmittedForm(), ViewJobFilter.all(), "jobFilters");
jobFilters.rebuildHetero(req, json, ViewJobFilter.all(), "jobFilters");
String filter = Util.fixEmpty(req.getParameter("statusFilter"));
statusFilter = filter != null ? "1".equals(filter) : null;
}
public void setIncludeRegex(String includeRegex) {
this.includeRegex = Util.nullify(includeRegex);
if (this.includeRegex == null)
this.includePattern = null;
else
this.includePattern = Pattern.compile(includeRegex);
}
@Extension
public static class DescriptorImpl extends ViewDescriptor {
@@ -23,6 +23,8 @@
*/
package hudson.model;
import hudson.Extension;
import hudson.ExtensionPoint;
import hudson.model.Queue.Task;
import hudson.model.queue.MappingWorksheet;
import hudson.model.queue.MappingWorksheet.ExecutorChunk;
@@ -36,10 +38,15 @@ import java.util.List;
/**
* Strategy that decides which {@link Task} gets run on which {@link Executor}.
*
* <p>
* Even though this is marked as {@link ExtensionPoint}, you do not register
* your implementation with @{@link Extension}. Instead, call {@link Queue#setLoadBalancer(LoadBalancer)}
* to install your implementation.
*
* @author Kohsuke Kawaguchi
* @since 1.301
*/
public abstract class LoadBalancer /*implements ExtensionPoint*/ {
public abstract class LoadBalancer implements ExtensionPoint {
/**
* Chooses the executor(s) to carry out the build for the given task.
*
@@ -77,7 +77,7 @@ public abstract class LoadStatistics {
public final MultiStageTimeSeries totalExecutors;
/**
* Number of {@link Queue.BuildableItem}s that can run on any node in this node set but blocked.
* Number of {@link hudson.model.Queue.BuildableItem}s that can run on any node in this node set but blocked.
*/
@Exported
public final MultiStageTimeSeries queueLength;
@@ -31,6 +31,7 @@ import hudson.security.Permission;
import jenkins.model.Jenkins;
import java.util.List;
import org.kohsuke.stapler.interceptor.RequirePOST;
/**
* Extension point to add icon to <tt>http://server/hudson/manage</tt> page.
@@ -80,6 +81,17 @@ public abstract class ManagementLink implements ExtensionPoint, Action {
*/
public abstract String getUrlName();
/**
* Allows implementations to request that this link show a confirmation dialog, and use POST if confirmed.
* Suitable for links which perform an action rather than simply displaying a page.
* @return true if this link takes an action
* @see RequirePOST
* @since 1.512
*/
public boolean getRequiresConfirmation() {
return false;
}
/**
* All registered instances.
* @deprecated as of 1.286
@@ -95,7 +107,7 @@ public abstract class ManagementLink implements ExtensionPoint, Action {
}
/**
* @return permission required for user to access this management link, in addition to {@link Jenkins.ADMINISTER}
* @return permission required for user to access this management link, in addition to {@link Jenkins#ADMINISTER}
*/
public Permission getRequiredPermission() {
return null;
@@ -145,7 +145,7 @@ public class MyViewsProperty extends UserProperty implements ViewGroup, Action,
}
public HttpResponse doIndex() {
return new HttpRedirect("view/" + getPrimaryView().getViewName() + "/");
return new HttpRedirect("view/" + Util.rawEncode(getPrimaryView().getViewName()) + "/");
}
public synchronized void doCreateView(StaplerRequest req, StaplerResponse rsp)
@@ -262,5 +262,5 @@ public class MyViewsProperty extends UserProperty implements ViewGroup, Action,
}
}
}
+51 -12
View File
@@ -35,6 +35,7 @@ import hudson.model.Queue.Task;
import hudson.model.labels.LabelAtom;
import hudson.model.queue.CauseOfBlockage;
import hudson.node_monitors.NodeMonitor;
import hudson.remoting.Callable;
import hudson.remoting.VirtualChannel;
import hudson.security.ACL;
import hudson.security.AccessControlled;
@@ -58,10 +59,13 @@ import java.util.Set;
import java.util.List;
import java.util.logging.Logger;
import javax.annotation.CheckForNull;
import javax.annotation.Nonnull;
import jenkins.model.Jenkins;
import jenkins.util.io.OnMaster;
import net.sf.json.JSONObject;
import org.acegisecurity.Authentication;
import org.jvnet.localizer.Localizable;
import org.kohsuke.stapler.BindInterceptor;
import org.kohsuke.stapler.Stapler;
import org.kohsuke.stapler.StaplerRequest;
@@ -69,14 +73,19 @@ import org.kohsuke.stapler.export.ExportedBean;
import org.kohsuke.stapler.export.Exported;
/**
* Base type of Hudson slaves (although in practice, you probably extend {@link Slave} to define a new slave type.)
* Base type of Jenkins slaves (although in practice, you probably extend {@link Slave} to define a new slave type.)
*
* <p>
* As a special case, {@link Jenkins} extends from here.
*
* <p>
* Nodes are persisted objects that capture user configurations, and instances get thrown away and recreated whenever
* the configuration changes. Running state of nodes are captured by {@link Computer}s.
*
* @author Kohsuke Kawaguchi
* @see NodeMonitor
* @see NodeDescriptor
* @see Computer
*/
@ExportedBean
public abstract class Node extends AbstractModelObject implements ReconfigurableDescribable<Node>, ExtensionPoint, AccessControlled, OnMaster {
@@ -84,7 +93,7 @@ public abstract class Node extends AbstractModelObject implements Reconfigurable
private static final Logger LOGGER = Logger.getLogger(Node.class.getName());
/**
* Newly copied slaves get this flag set, so that Hudson doesn't try to start this node until its configuration
* Newly copied slaves get this flag set, so that Jenkins doesn't try to start/remove this node until its configuration
* is saved once.
*/
protected volatile transient boolean holdOffLaunchUntilSave;
@@ -222,7 +231,7 @@ public abstract class Node extends AbstractModelObject implements Reconfigurable
public TagCloud<LabelAtom> getLabelCloud() {
return new TagCloud<LabelAtom>(getAssignedLabels(),new WeightFunction<LabelAtom>() {
public float weight(LabelAtom item) {
return item.getTiedJobs().size();
return item.getTiedJobCount();
}
});
}
@@ -322,8 +331,23 @@ public abstract class Node extends AbstractModelObject implements Reconfigurable
if(l!=null && !l.contains(this))
return CauseOfBlockage.fromMessage(Messages._Node_LabelMissing(getNodeName(),l)); // the task needs to be executed on label that this node doesn't have.
if(l==null && getMode()== Mode.EXCLUSIVE)
return CauseOfBlockage.fromMessage(Messages._Node_BecauseNodeIsReserved(getNodeName())); // this node is reserved for tasks that are tied to it
if(l==null && getMode()== Mode.EXCLUSIVE) {
// flyweight tasks need to get executed somewhere, if every node
if (!(item.task instanceof Queue.FlyweightTask && (
this instanceof Jenkins
|| Jenkins.getInstance().getNumExecutors() < 1
|| Jenkins.getInstance().getMode() == Mode.EXCLUSIVE)
)) {
return CauseOfBlockage.fromMessage(Messages._Node_BecauseNodeIsReserved(getNodeName())); // this node is reserved for tasks that are tied to it
}
}
Authentication identity = item.authenticate();
if (!getACL().hasPermission(identity,Computer.BUILD)) {
// doesn't have a permission
// TODO: does it make more sense to define a separate permission?
return CauseOfBlockage.fromMessage(Messages._Node_LackingBuildPermission(identity.getName(),getNodeName()));
}
// Check each NodeProperty to see whether they object to this node
// taking the task
@@ -379,7 +403,7 @@ public abstract class Node extends AbstractModelObject implements Reconfigurable
/**
* Gets the {@link NodeProperty} instances configured for this {@link Node}.
*/
public abstract DescribableList<NodeProperty<?>, NodePropertyDescriptor> getNodeProperties();
public abstract @Nonnull DescribableList<NodeProperty<?>, NodePropertyDescriptor> getNodeProperties();
// used in the Jelly script to expose descriptors
public List<NodePropertyDescriptor> getNodePropertyDescriptors() {
@@ -436,26 +460,41 @@ public abstract class Node extends AbstractModelObject implements Reconfigurable
* @throws InterruptedException
* if the operation is aborted.
*/
public abstract ClockDifference getClockDifference() throws IOException, InterruptedException;
public ClockDifference getClockDifference() throws IOException, InterruptedException {
VirtualChannel channel = getChannel();
if(channel==null)
throw new IOException(getNodeName()+" is offline");
return channel.call(getClockDifferenceCallable());
}
/**
* Returns a {@link Callable} that when run on the channel, estimates the clock difference.
*
* @return
* always non-null.
* @since 1.522
*/
public abstract Callable<ClockDifference,IOException> getClockDifferenceCallable();
/**
* Constants that control how Hudson allocates jobs to slaves.
*/
public enum Mode {
NORMAL(Messages.Node_Mode_NORMAL()),
EXCLUSIVE(Messages.Node_Mode_EXCLUSIVE());
NORMAL(Messages._Node_Mode_NORMAL()),
EXCLUSIVE(Messages._Node_Mode_EXCLUSIVE());
private final String description;
private final Localizable description;
public String getDescription() {
return description;
return description.toString();
}
public String getName() {
return name();
}
Mode(String description) {
Mode(Localizable description) {
this.description = description;
}
@@ -36,11 +36,11 @@ import org.kohsuke.stapler.export.Exported;
*
* @author Kohsuke Kawaguchi
* @see Jenkins#overallLoad
* @see UnlabeldLoadStatistics
* @see jenkins.model.UnlabeledLoadStatistics
*/
public class OverallLoadStatistics extends LoadStatistics {
/**
* Number of total {@link Queue.BuildableItem}s that represents blocked builds.
* Number of total {@link hudson.model.Queue.BuildableItem}s that represents blocked builds.
*
* @deprecated as of 1.467
* Use {@link #queueLength}. Left as an alias here for backward compatibility.
@@ -32,6 +32,7 @@ import hudson.util.DescriptorList;
import java.io.Serializable;
import java.io.IOException;
import java.util.logging.Logger;
import jenkins.model.Jenkins;
import net.sf.json.JSONObject;
@@ -137,6 +138,19 @@ public abstract class ParameterDefinition implements
return description;
}
/**
* return parameter description, applying the configured MarkupFormatter for jenkins instance.
* @since 1.521
*/
public String getFormattedDescription() {
try {
return Jenkins.getInstance().getMarkupFormatter().translate(description);
} catch (IOException e) {
LOGGER.warning("failed to translate description using configured markup formatter");
return "";
}
}
/**
* {@inheritDoc}
*/
@@ -241,4 +255,6 @@ public abstract class ParameterDefinition implements
return "Parameter";
}
}
private static final Logger LOGGER = Logger.getLogger(ParameterDefinition.class.getName());
}
Loaded 100 of 864 files, more files were not shown because too many files have changed in this diff. Show more