Skip to content
体验新版
项目
组织
正在加载...
登录
切换导航
打开侧边栏
FIY695
jenkins
提交
b698b672
J
jenkins
项目概览
FIY695
/
jenkins
与 Fork 源项目一致
从无法访问的项目Fork
通知
1
Star
0
Fork
0
代码
文件
提交
分支
Tags
贡献者
分支图
Diff
Issue
0
列表
看板
标记
里程碑
合并请求
0
Wiki
0
Wiki
分析
仓库
DevOps
项目成员
Pages
J
jenkins
项目概览
项目概览
详情
发布
仓库
仓库
文件
提交
分支
标签
贡献者
分支图
比较
Issue
0
Issue
0
列表
看板
标记
里程碑
合并请求
0
合并请求
0
Pages
分析
分析
仓库分析
DevOps
Wiki
0
Wiki
成员
成员
收起侧边栏
关闭侧边栏
动态
分支图
创建新Issue
提交
Issue看板
体验新版 GitCode,发现更多精彩内容 >>
提交
b698b672
编写于
9月 13, 2013
作者:
K
Kohsuke Kawaguchi
浏览文件
操作
浏览文件
下载
差异文件
[FIXED JENKINS-17236] Introduce ArtifactManager
Merge branch 'ArtifactManager-JENKINS-17236'
上级
eea972ec
f60b631a
变更
23
隐藏空白更改
内联
并排
Showing
23 changed file
with
1243 addition
and
326 deletion
+1243
-326
changelog.html
changelog.html
+3
-0
core/src/main/java/hudson/FilePath.java
core/src/main/java/hudson/FilePath.java
+82
-38
core/src/main/java/hudson/model/DirectoryBrowserSupport.java
core/src/main/java/hudson/model/DirectoryBrowserSupport.java
+86
-104
core/src/main/java/hudson/model/Run.java
core/src/main/java/hudson/model/Run.java
+73
-12
core/src/main/java/hudson/tasks/ArtifactArchiver.java
core/src/main/java/hudson/tasks/ArtifactArchiver.java
+33
-12
core/src/main/java/hudson/util/DirScanner.java
core/src/main/java/hudson/util/DirScanner.java
+25
-35
core/src/main/java/jenkins/model/ArtifactManager.java
core/src/main/java/jenkins/model/ArtifactManager.java
+82
-0
core/src/main/java/jenkins/model/ArtifactManagerConfiguration.java
...main/java/jenkins/model/ArtifactManagerConfiguration.java
+68
-0
core/src/main/java/jenkins/model/ArtifactManagerFactory.java
core/src/main/java/jenkins/model/ArtifactManagerFactory.java
+56
-0
core/src/main/java/jenkins/model/ArtifactManagerFactoryDescriptor.java
.../java/jenkins/model/ArtifactManagerFactoryDescriptor.java
+41
-0
core/src/main/java/jenkins/model/StandardArtifactManager.java
.../src/main/java/jenkins/model/StandardArtifactManager.java
+78
-0
core/src/main/java/jenkins/util/VirtualFile.java
core/src/main/java/jenkins/util/VirtualFile.java
+344
-0
core/src/main/resources/jenkins/model/ArtifactManagerConfiguration/config.groovy
.../jenkins/model/ArtifactManagerConfiguration/config.groovy
+35
-0
core/src/test/java/hudson/model/RunTest.java
core/src/test/java/hudson/model/RunTest.java
+35
-3
maven-plugin/src/main/java/hudson/maven/MavenBuild.java
maven-plugin/src/main/java/hudson/maven/MavenBuild.java
+54
-1
maven-plugin/src/main/java/hudson/maven/MavenBuildProxy.java
maven-plugin/src/main/java/hudson/maven/MavenBuildProxy.java
+17
-1
maven-plugin/src/main/java/hudson/maven/MavenModuleSetBuild.java
...lugin/src/main/java/hudson/maven/MavenModuleSetBuild.java
+3
-0
maven-plugin/src/main/java/hudson/maven/reporters/MavenArtifact.java
...n/src/main/java/hudson/maven/reporters/MavenArtifact.java
+27
-61
maven-plugin/src/main/java/hudson/maven/reporters/MavenArtifactArchiver.java
...in/java/hudson/maven/reporters/MavenArtifactArchiver.java
+2
-3
maven-plugin/src/main/java/hudson/maven/reporters/MavenArtifactRecord.java
...main/java/hudson/maven/reporters/MavenArtifactRecord.java
+11
-22
maven-plugin/src/test/java/hudson/maven/reporters/SurefireArchiverUnitTest.java
...java/hudson/maven/reporters/SurefireArchiverUnitTest.java
+2
-0
test/src/test/java/hudson/maven/MavenMultiModuleTest.java
test/src/test/java/hudson/maven/MavenMultiModuleTest.java
+86
-0
test/src/test/java/hudson/model/RunTest.java
test/src/test/java/hudson/model/RunTest.java
+0
-34
未找到文件。
changelog.html
浏览文件 @
b698b672
...
...
@@ -67,6 +67,9 @@ Upcoming changes</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>
)
</ul>
</div>
<!--=TRUNK-END=-->
...
...
core/src/main/java/hudson/FilePath.java
浏览文件 @
b698b672
...
...
@@ -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
;
...
...
@@ -98,8 +97,10 @@ import com.jcraft.jzlib.GZIPOutputStream;
import
com.sun.jna.Native
;
import
hudson.os.PosixException
;
import
java.io.BufferedInputStream
;
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
;
...
...
@@ -197,7 +198,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
);
}
...
...
@@ -1041,6 +1042,13 @@ public final class FilePath implements Serializable {
});
}
/**
* Gets the {@link VirtualFile} representation of this {@link FilePath}
*/
public
VirtualFile
toVirtualFile
()
{
return
VirtualFile
.
forFilePath
(
this
);
}
/**
* Creates this directory.
*/
...
...
@@ -1880,6 +1888,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.531
*/
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
>()
{
...
...
@@ -1887,37 +1907,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
();
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
();
}
public
int
getNumCopied
()
{
return
copySize
;
}
@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
();
}
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
);
}
});
return
count
.
get
();
}
});
}
else
...
...
@@ -1929,7 +1942,7 @@ 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
();
...
...
@@ -1939,7 +1952,7 @@ public final class FilePath implements Serializable {
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
),
fileMask
,
excludes
,
TarCompression
.
GZIP
.
compress
(
pipe
.
getOut
()));
return
writeToTar
(
new
File
(
remote
),
scanner
,
TarCompression
.
GZIP
.
compress
(
pipe
.
getOut
()));
}
});
try
{
...
...
@@ -1957,14 +1970,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
);
...
...
@@ -2013,10 +2026,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
();
}
...
...
@@ -2479,4 +2492,35 @@ public final class FilePath implements Serializable {
}
});
}
/**
* Helper class to make it easy to send an explicit list of files using {@link FilePath} methods.
* @since 1.531
*/
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
);
}
}
}
}
core/src/main/java/hudson/model/DirectoryBrowserSupport.java
浏览文件 @
b698b672
...
...
@@ -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.531
*/
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
.
contentL
ength
);
LOGGER
.
fine
(
"Serving "
+
baseFile
+
" with lastModified="
+
lastModified
+
", length="
+
l
ength
);
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
.
contentL
ength
,
"plain.txt"
);
rsp
.
serveFile
(
req
,
in
,
lastModified
,
-
1
,
l
ength
,
"plain.txt"
);
}
else
{
rsp
.
serveFile
(
req
,
in
,
ci
.
lastModified
,
-
1
,
ci
.
contentL
ength
,
baseFile
.
getName
()
);
rsp
.
serveFile
(
req
,
in
,
lastModified
,
-
1
,
l
ength
,
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
<
Virtual
File
>
{
private
Collator
collator
;
public
FileComparator
(
Locale
locale
)
{
this
.
collator
=
Collator
.
getInstance
(
locale
);
}
public
int
compare
(
File
lhs
,
File
rhs
)
{
public
int
compare
(
VirtualFile
lhs
,
Virtual
File
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
(
Virtual
File
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
,
Virtual
File
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
());
}
core/src/main/java/hudson/model/Run.java
浏览文件 @
b698b672
...
...
@@ -52,14 +52,12 @@ import hudson.security.AccessControlled;
import
hudson.security.Permission
;
import
hudson.security.PermissionGroup
;
import
hudson.tasks.BuildWrapper
;
import
hudson.tasks.BuildStep
;
import
hudson.tasks.test.AbstractTestResultAction
;
import
hudson.util.FlushProofOutputStream
;
import
hudson.util.FormApply
;
import
hudson.util.IOException2
;
import
hudson.util.LogTaskListener
;
import
hudson.util.XStream2
;
import
hudson.util.ProcessTree
;
import
java.io.BufferedReader
;
import
java.io.File
;
...
...
@@ -121,8 +119,13 @@ import java.io.StringWriter;
import
static
java
.
util
.
logging
.
Level
.*;
import
javax.annotation.CheckForNull
;
import
javax.annotation.Nonnull
;
import
jenkins.model.ArtifactManager
;
import
jenkins.model.ArtifactManagerConfiguration
;
import
jenkins.model.ArtifactManagerFactory
;
import
jenkins.model.PeepholePermalink
;
import
jenkins.model.StandardArtifactManager
;
import
jenkins.model.RunAction2
;
import
jenkins.util.VirtualFile
;
/**
* A particular execution of {@link Job}.
...
...
@@ -257,6 +260,12 @@ public abstract class Run <JobT extends Job<JobT,RunT>,RunT extends Run<JobT,Run
*/
private
volatile
transient
RunExecution
runner
;
/**
* Artifact manager associated with this build, if any.
* @since 1.531
*/
private
@CheckForNull
ArtifactManager
artifactManager
;
private
static
final
SimpleDateFormat
CANONICAL_ID_FORMATTER
=
new
SimpleDateFormat
(
"yyyy-MM-dd_HH-mm-ss"
);
public
static
final
ThreadLocal
<
SimpleDateFormat
>
ID_FORMATTER
=
new
IDFormatterProvider
();
private
static
final
class
IDFormatterProvider
extends
ThreadLocal
<
SimpleDateFormat
>
{
...
...
@@ -326,6 +335,9 @@ public abstract class Run <JobT extends Job<JobT,RunT>,RunT extends Run<JobT,Run
((
RunAction
)
a
).
onLoad
();
}
}
if
(
artifactManager
!=
null
)
{
artifactManager
.
onLoad
(
this
);
}
}
/**
...
...
@@ -964,9 +976,53 @@ public abstract class Run <JobT extends Job<JobT,RunT>,RunT extends Run<JobT,Run
return
new
File
(
project
.
getBuildDir
(),
getId
());
}
/**
* Gets an object responsible for storing and retrieving build artifacts.
* If {@link #pickArtifactManager} has previously been called on this build,
* and a nondefault manager selected, that will be returned.
* Otherwise (including if we are loading a historical build created prior to this feature) {@link StandardArtifactManager} is used.
* <p>This method should be used when existing artifacts are to be loaded, displayed, or removed.
* If adding artifacts, use {@link #pickArtifactManager} instead.
* @return an appropriate artifact manager
* @since 1.531
*/
public
final
@Nonnull
ArtifactManager
getArtifactManager
()
{
return
artifactManager
!=
null
?
artifactManager
:
new
StandardArtifactManager
(
this
);
}
/**
* Selects an object responsible for storing and retrieving build artifacts.
* The first time this is called on a running build, {@link ArtifactManagerConfiguration} is checked
* to see if one will handle this build.
* If so, that manager is saved in the build and it will be used henceforth.
* If no manager claimed the build, {@link StandardArtifactManager} is used.
* <p>This method should be used when a build step expects to archive some artifacts.
* If only displaying existing artifacts, use {@link #getArtifactManager} instead.
* @return an appropriate artifact manager
* @throws IOException if a custom manager was selected but the selection could not be saved
* @since 1.531
*/
public
final
synchronized
@Nonnull
ArtifactManager
pickArtifactManager
()
throws
IOException
{
if
(
artifactManager
!=
null
)
{
return
artifactManager
;
}
else
{
for
(
ArtifactManagerFactory
f
:
ArtifactManagerConfiguration
.
get
().
getArtifactManagerFactories
())
{
ArtifactManager
mgr
=
f
.
managerFor
(
this
);
if
(
mgr
!=
null
)
{
artifactManager
=
mgr
;
save
();
return
mgr
;
}
}
return
new
StandardArtifactManager
(
this
);
}
}
/**
* Gets the directory where the artifacts are archived.
* @deprecated Should only be used from {@link StandardArtifactManager} or subclasses.
*/
@Deprecated
public
File
getArtifactsDir
()
{
return
new
File
(
getRootDir
(),
"archive"
);
}
...
...
@@ -984,7 +1040,11 @@ public abstract class Run <JobT extends Job<JobT,RunT>,RunT extends Run<JobT,Run
*/
public
List
<
Artifact
>
getArtifactsUpTo
(
int
n
)
{
ArtifactList
r
=
new
ArtifactList
();
addArtifacts
(
getArtifactsDir
(),
""
,
""
,
r
,
null
,
n
);
try
{
addArtifacts
(
getArtifactManager
().
root
(),
""
,
""
,
r
,
null
,
n
);
}
catch
(
IOException
x
)
{
LOGGER
.
log
(
Level
.
WARNING
,
null
,
x
);
}
r
.
computeDisplayName
();
return
r
;
}
...
...
@@ -999,18 +1059,17 @@ public abstract class Run <JobT extends Job<JobT,RunT>,RunT extends Run<JobT,Run
return
!
getArtifactsUpTo
(
1
).
isEmpty
();
}
private
int
addArtifacts
(
File
dir
,
String
path
,
String
pathHref
,
ArtifactList
r
,
Artifact
parent
,
int
upTo
)
{
String
[]
children
=
dir
.
list
();
if
(
children
==
null
)
return
0
;
Arrays
.
sort
(
children
,
String
.
CASE_INSENSITIVE_ORDER
);
private
int
addArtifacts
(
VirtualFile
dir
,
String
path
,
String
pathHref
,
ArtifactList
r
,
Artifact
parent
,
int
upTo
)
throws
IOException
{
VirtualFile
[]
kids
=
dir
.
list
();
Arrays
.
sort
(
kids
);
int
n
=
0
;
for
(
String
child
:
children
)
{
for
(
VirtualFile
sub
:
kids
)
{
String
child
=
sub
.
getName
();
String
childPath
=
path
+
child
;
String
childHref
=
pathHref
+
Util
.
rawEncode
(
child
);
File
sub
=
new
File
(
dir
,
child
);
String
length
=
sub
.
isFile
()
?
String
.
valueOf
(
sub
.
length
())
:
""
;
boolean
collapsed
=
(
children
.
length
==
1
&&
parent
!=
null
);
boolean
collapsed
=
(
kids
.
length
==
1
&&
parent
!=
null
);
Artifact
a
;
if
(
collapsed
)
{
// Collapse single items into parent node where possible:
...
...
@@ -1138,7 +1197,7 @@ public abstract class Run <JobT extends Job<JobT,RunT>,RunT extends Run<JobT,Run
@ExportedBean
public
class
Artifact
{
/**
* Relative path name from
{@link Run#getArtifactsDir()}
* Relative path name from
artifacts root.
*/
@Exported
(
visibility
=
3
)
public
final
String
relativePath
;
...
...
@@ -1181,7 +1240,9 @@ public abstract class Run <JobT extends Job<JobT,RunT>,RunT extends Run<JobT,Run
/**
* Gets the artifact file.
* @deprecated May not be meaningful with custom artifact managers. Use {@link ArtifactManager#load} with {@link #relativePath} instead.
*/
@Deprecated
public
File
getFile
()
{
return
new
File
(
getArtifactsDir
(),
relativePath
);
}
...
...
@@ -1933,7 +1994,7 @@ public abstract class Run <JobT extends Job<JobT,RunT>,RunT extends Run<JobT,Run
if
(
Functions
.
isArtifactsPermissionEnabled
())
{
checkPermission
(
ARTIFACTS
);
}
return
new
DirectoryBrowserSupport
(
this
,
new
FilePath
(
getArtifactsDir
()),
project
.
getDisplayName
()+
' '
+
getDisplayName
(),
"package.png"
,
true
);
return
new
DirectoryBrowserSupport
(
this
,
getArtifactManager
().
root
(),
project
.
getDisplayName
()
+
' '
+
getDisplayName
(),
"package.png"
,
true
);
}
/**
...
...
core/src/main/java/hudson/tasks/ArtifactArchiver.java
浏览文件 @
b698b672
...
...
@@ -31,14 +31,17 @@ import hudson.model.AbstractBuild;
import
hudson.model.AbstractProject
;
import
hudson.model.BuildListener
;
import
hudson.model.Result
;
import
hudson.remoting.VirtualChannel
;
import
hudson.util.FormValidation
;
import
java.io.File
;
import
org.kohsuke.stapler.StaplerRequest
;
import
org.kohsuke.stapler.DataBoundConstructor
;
import
org.kohsuke.stapler.AncestorInPath
;
import
org.kohsuke.stapler.QueryParameter
;
import
java.io.File
;
import
java.io.IOException
;
import
java.util.HashMap
;
import
java.util.Map
;
import
net.sf.json.JSONObject
;
import
javax.annotation.Nonnull
;
...
...
@@ -123,9 +126,6 @@ public class ArtifactArchiver extends Recorder {
return
true
;
}
File
dir
=
build
.
getArtifactsDir
();
dir
.
mkdirs
();
listener
.
getLogger
().
println
(
Messages
.
ArtifactArchiver_ARCHIVING_ARTIFACTS
());
try
{
FilePath
ws
=
build
.
getWorkspace
();
...
...
@@ -134,7 +134,11 @@ public class ArtifactArchiver extends Recorder {
}
String
artifacts
=
build
.
getEnvironment
(
listener
).
expand
(
this
.
artifacts
);
if
(
ws
.
copyRecursiveTo
(
artifacts
,
excludes
,
new
FilePath
(
dir
))==
0
)
{
Map
<
String
,
String
>
files
=
ws
.
act
(
new
ListFiles
(
artifacts
,
excludes
));
if
(!
files
.
isEmpty
())
{
build
.
pickArtifactManager
().
archive
(
ws
,
launcher
,
listener
,
files
);
}
else
{
Result
result
=
build
.
getResult
();
if
(
result
!=
null
&&
result
.
isBetterOrEqualTo
(
Result
.
UNSTABLE
))
{
// If the build failed, don't complain that there was no matching artifact.
...
...
@@ -165,6 +169,23 @@ public class ArtifactArchiver extends Recorder {
return
true
;
}
private
static
final
class
ListFiles
implements
FilePath
.
FileCallable
<
Map
<
String
,
String
>>
{
private
static
final
long
serialVersionUID
=
1
;
private
final
String
includes
,
excludes
;
ListFiles
(
String
includes
,
String
excludes
)
{
this
.
includes
=
includes
;
this
.
excludes
=
excludes
;
}
@Override
public
Map
<
String
,
String
>
invoke
(
File
basedir
,
VirtualChannel
channel
)
throws
IOException
,
InterruptedException
{
Map
<
String
,
String
>
r
=
new
HashMap
<
String
,
String
>();
for
(
String
f
:
Util
.
createFileSet
(
basedir
,
includes
,
excludes
).
getDirectoryScanner
().
getIncludedFiles
())
{
f
=
f
.
replace
(
File
.
separatorChar
,
'/'
);
r
.
put
(
f
,
f
);
}
return
r
;
}
}
@Override
public
boolean
prebuild
(
AbstractBuild
<?,
?>
build
,
BuildListener
listener
)
{
if
(
latestOnly
)
{
...
...
@@ -176,14 +197,14 @@ public class ArtifactArchiver extends Recorder {
bestResultSoFar
=
b
.
getResult
();
}
else
{
// remove old artifacts
File
ad
=
b
.
getArtifactsDir
();
if
(
ad
.
exists
())
{
listener
.
getLogger
().
println
(
Messages
.
ArtifactArchiver_DeletingOld
(
b
.
getDisplayName
()));
try
{
Util
.
deleteRecursive
(
ad
);
}
catch
(
IOException
e
)
{
e
.
printStackTrace
(
listener
.
error
(
e
.
getMessage
()));
try
{
if
(
b
.
getArtifactManager
().
delete
())
{
listener
.
getLogger
().
println
(
Messages
.
ArtifactArchiver_DeletingOld
(
b
.
getDisplayName
()));
}
}
catch
(
IOException
e
)
{
e
.
printStackTrace
(
listener
.
error
(
e
.
getMessage
()));
}
catch
(
InterruptedException
x
)
{
x
.
printStackTrace
(
listener
.
error
(
x
.
getMessage
()));
}
}
}
...
...
core/src/main/java/hudson/util/DirScanner.java
浏览文件 @
b698b672
...
...
@@ -26,29 +26,36 @@ public abstract class DirScanner implements Serializable {
*/
public
abstract
void
scan
(
File
dir
,
FileVisitor
visitor
)
throws
IOException
;
/**
* @since 1.531
*/
protected
final
void
scanSingle
(
File
f
,
String
relative
,
FileVisitor
visitor
)
throws
IOException
{
if
(
visitor
.
understandsSymlink
())
{
try
{
String
target
;
try
{
target
=
Util
.
resolveSymlink
(
f
);
}
catch
(
IOException
x
)
{
// JENKINS-13202
target
=
null
;
}
if
(
target
!=
null
)
{
visitor
.
visitSymlink
(
f
,
target
,
relative
);
return
;
}
}
catch
(
InterruptedException
e
)
{
throw
(
IOException
)
new
InterruptedIOException
().
initCause
(
e
);
}
}
visitor
.
visit
(
f
,
relative
);
}
/**
* Scans everything recursively.
*/
public
static
class
Full
extends
DirScanner
{
private
void
scan
(
File
f
,
String
path
,
FileVisitor
visitor
)
throws
IOException
{
if
(
f
.
canRead
())
{
if
(
visitor
.
understandsSymlink
())
{
try
{
String
target
;
try
{
target
=
Util
.
resolveSymlink
(
f
);
}
catch
(
IOException
x
)
{
// JENKINS-13202
target
=
null
;
}
if
(
target
!=
null
)
{
visitor
.
visitSymlink
(
f
,
target
,
path
+
f
.
getName
());
return
;
}
}
catch
(
InterruptedException
e
)
{
throw
(
IOException
)
new
InterruptedIOException
().
initCause
(
e
);
}
}
visitor
.
visit
(
f
,
path
+
f
.
getName
());
scanSingle
(
f
,
path
+
f
.
getName
(),
visitor
);
if
(
f
.
isDirectory
())
{
for
(
File
child
:
f
.
listFiles
()
)
scan
(
child
,
path
+
f
.
getName
()+
'/'
,
visitor
);
...
...
@@ -113,24 +120,7 @@ public abstract class DirScanner implements Serializable {
DirectoryScanner
ds
=
fs
.
getDirectoryScanner
(
new
org
.
apache
.
tools
.
ant
.
Project
());
for
(
String
f
:
ds
.
getIncludedFiles
())
{
File
file
=
new
File
(
dir
,
f
);
if
(
visitor
.
understandsSymlink
())
{
try
{
String
target
;
try
{
target
=
Util
.
resolveSymlink
(
file
);
}
catch
(
IOException
x
)
{
// JENKINS-13202
target
=
null
;
}
if
(
target
!=
null
)
{
visitor
.
visitSymlink
(
file
,
target
,
f
);
continue
;
}
}
catch
(
InterruptedException
e
)
{
throw
(
IOException
)
new
InterruptedIOException
().
initCause
(
e
);
}
}
visitor
.
visit
(
file
,
f
);
scanSingle
(
file
,
f
,
visitor
);
}
}
}
...
...
core/src/main/java/jenkins/model/ArtifactManager.java
0 → 100644
浏览文件 @
b698b672
/*
* The MIT License
*
* Copyright 2013 Jesse Glick.
*
* 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
jenkins.model
;
import
hudson.FilePath
;
import
hudson.Launcher
;
import
hudson.model.AbstractBuild
;
import
hudson.model.BuildListener
;
import
hudson.model.Run
;
import
hudson.tasks.ArtifactArchiver
;
import
java.io.IOException
;
import
java.util.Map
;
import
jenkins.util.VirtualFile
;
/**
* Manager of artifacts for one build.
* @see ArtifactManagerFactory
* @since 1.531
*/
public
abstract
class
ArtifactManager
{
/**
* Called when this manager is loaded from disk.
* The selected manager will be persisted inside a build, so the build reference should be {@code transient} (quasi-{@code final}) and restored here.
* @param build a historical build with which this manager was associated
*/
public
abstract
void
onLoad
(
Run
<?,?>
build
);
/**
* Archive all configured artifacts from a build.
* <p>If called multiple times for the same build, do not delete the old artifacts but keep them all, unless overwritten.
* For example, the XVNC plugin could use this to save {@code screenshot.jpg} if so configured.
* <p>This method is typically invoked on a running build, though e.g. in the case of Maven module builds,
* the build may actually be {@link hudson.model.Run.State#COMPLETED} when this is called
* (since it is the parent build which is still running and performing archiving).
* @param workspace the root directory from which to copy files (typically {@link AbstractBuild#getWorkspace} but not necessarily)
* @param launcher a launcher to use if external processes need to be forked
* @param listener a way to print messages about progress or problems
* @param artifacts map from paths in the archive area to paths relative to {@code workspace} (all paths {@code /}-separated)
* @throws IOException if transfer or copying failed in any way
* @throws InterruptedException if transfer was interrupted
* @see ArtifactArchiver#perform(AbstractBuild, Launcher, BuildListener)
*/
public
abstract
void
archive
(
FilePath
workspace
,
Launcher
launcher
,
BuildListener
listener
,
Map
<
String
,
String
>
artifacts
)
throws
IOException
,
InterruptedException
;
/**
* Delete all artifacts associated with an earlier build (if any).
* @return true if there was actually anything to delete
* @throws IOException if deletion could not be completed
* @throws InterruptedException if deletion was interrupted
*/
public
abstract
boolean
delete
()
throws
IOException
,
InterruptedException
;
/**
* Returns a representation of the root directory of archived artifacts.
* @return the archive root
*/
public
abstract
VirtualFile
root
();
}
core/src/main/java/jenkins/model/ArtifactManagerConfiguration.java
0 → 100644
浏览文件 @
b698b672
/*
* The MIT License
*
* Copyright 2013 Jesse Glick.
*
* 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
jenkins.model
;
import
hudson.Extension
;
import
hudson.util.DescribableList
;
import
java.io.IOException
;
import
net.sf.json.JSONObject
;
import
org.kohsuke.stapler.StaplerRequest
;
/**
* List of configured {@link ArtifactManagerFactory}s.
* @since 1.531
*/
@Extension
public
class
ArtifactManagerConfiguration
extends
GlobalConfiguration
{
public
static
ArtifactManagerConfiguration
get
()
{
return
Jenkins
.
getInstance
().
getInjector
().
getInstance
(
ArtifactManagerConfiguration
.
class
);
}
private
final
DescribableList
<
ArtifactManagerFactory
,
ArtifactManagerFactoryDescriptor
>
artifactManagerFactories
=
new
DescribableList
<
ArtifactManagerFactory
,
ArtifactManagerFactoryDescriptor
>(
this
);
public
ArtifactManagerConfiguration
()
{
load
();
}
private
Object
readResolve
()
{
artifactManagerFactories
.
setOwner
(
this
);
return
this
;
}
public
DescribableList
<
ArtifactManagerFactory
,
ArtifactManagerFactoryDescriptor
>
getArtifactManagerFactories
()
{
return
artifactManagerFactories
;
}
@Override
public
boolean
configure
(
StaplerRequest
req
,
JSONObject
json
)
throws
FormException
{
try
{
artifactManagerFactories
.
rebuildHetero
(
req
,
json
,
ArtifactManagerFactoryDescriptor
.
all
(),
"artifactManagerFactories"
);
return
true
;
}
catch
(
IOException
x
)
{
throw
new
FormException
(
x
,
"artifactManagerFactories"
);
}
}
}
core/src/main/java/jenkins/model/ArtifactManagerFactory.java
0 → 100644
浏览文件 @
b698b672
/*
* The MIT License
*
* Copyright 2013 Jesse Glick.
*
* 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
jenkins.model
;
import
hudson.ExtensionPoint
;
import
hudson.model.AbstractDescribableImpl
;
import
hudson.model.Run
;
import
javax.annotation.CheckForNull
;
import
org.kohsuke.stapler.DataBoundConstructor
;
/**
* Pluggable ability to manage transfer and/or storage of build artifacts.
* The descriptor should specify at least a display name, and optionally a {@code config} view.
* Since the user can configure this class, you must have a {@link DataBoundConstructor}.
* @see ArtifactManagerConfiguration
* @see ArtifactManagerFactoryDescriptor
* @since 1.531
*/
public
abstract
class
ArtifactManagerFactory
extends
AbstractDescribableImpl
<
ArtifactManagerFactory
>
implements
ExtensionPoint
{
/**
* Optionally creates a manager for a particular build.
* All configured factories are consulted in sequence; the first manager thus yielded (if any) will be stored in the build.
* {@link StandardArtifactManager} is used as a fallback.
* @param build a running (or recently completed) build ready for {@link ArtifactManager#archive}
* @return a manager, or null if this manager should not handle this kind of project, builds on this kind of slave, etc.
*/
public
abstract
@CheckForNull
ArtifactManager
managerFor
(
Run
<?,?>
build
);
@Override
public
ArtifactManagerFactoryDescriptor
getDescriptor
()
{
return
(
ArtifactManagerFactoryDescriptor
)
super
.
getDescriptor
();
}
}
core/src/main/java/jenkins/model/ArtifactManagerFactoryDescriptor.java
0 → 100644
浏览文件 @
b698b672
/*
* The MIT License
*
* Copyright 2013 Jesse Glick.
*
* 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
jenkins.model
;
import
hudson.DescriptorExtensionList
;
import
hudson.model.Descriptor
;
/**
* Definition of a kind of artifact manager.
* @see ArtifactManagerFactory
* @since 1.531
*/
public
abstract
class
ArtifactManagerFactoryDescriptor
extends
Descriptor
<
ArtifactManagerFactory
>
{
public
static
DescriptorExtensionList
<
ArtifactManagerFactory
,
ArtifactManagerFactoryDescriptor
>
all
()
{
return
Jenkins
.
getInstance
().
getDescriptorList
(
ArtifactManagerFactory
.
class
);
}
}
core/src/main/java/jenkins/model/StandardArtifactManager.java
0 → 100644
浏览文件 @
b698b672
/*
* The MIT License
*
* Copyright 2013 Jesse Glick.
*
* 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
jenkins.model
;
import
hudson.FilePath
;
import
hudson.Launcher
;
import
hudson.Util
;
import
hudson.model.BuildListener
;
import
hudson.model.Run
;
import
java.io.File
;
import
java.io.IOException
;
import
java.util.Map
;
import
jenkins.util.VirtualFile
;
/**
* Default artifact manager which transfers files over the remoting channel and stores them inside the build directory.
* May be subclassed to provide an artifact manager which uses the standard storage but which only overrides {@link #archive}.
* @since 1.531
*/
public
class
StandardArtifactManager
extends
ArtifactManager
{
protected
transient
Run
<?,?>
build
;
public
StandardArtifactManager
(
Run
<?,?>
build
)
{
onLoad
(
build
);
}
@Override
public
final
void
onLoad
(
Run
<?,?>
build
)
{
this
.
build
=
build
;
}
@Override
public
void
archive
(
FilePath
workspace
,
Launcher
launcher
,
BuildListener
listener
,
final
Map
<
String
,
String
>
artifacts
)
throws
IOException
,
InterruptedException
{
File
dir
=
getArtifactsDir
();
String
description
=
"transfer of "
+
artifacts
.
size
()
+
" files"
;
// TODO improve when just one file
workspace
.
copyRecursiveTo
(
new
FilePath
.
ExplicitlySpecifiedDirScanner
(
artifacts
),
new
FilePath
(
dir
),
description
);
}
@Override
public
final
boolean
delete
()
throws
IOException
,
InterruptedException
{
File
ad
=
getArtifactsDir
();
if
(!
ad
.
exists
())
{
return
false
;
}
Util
.
deleteRecursive
(
ad
);
return
true
;
}
@Override
public
VirtualFile
root
()
{
return
VirtualFile
.
forFile
(
getArtifactsDir
());
}
@SuppressWarnings
(
"deprecation"
)
private
File
getArtifactsDir
()
{
return
build
.
getArtifactsDir
();
}
}
core/src/main/java/jenkins/util/VirtualFile.java
0 → 100644
浏览文件 @
b698b672
/*
* The MIT License
*
* Copyright 2013 Jesse Glick.
*
* 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
jenkins.util
;
import
hudson.FilePath
;
import
hudson.model.DirectoryBrowserSupport
;
import
hudson.remoting.VirtualChannel
;
import
hudson.util.DirScanner
;
import
hudson.util.FileVisitor
;
import
java.io.File
;
import
java.io.FileInputStream
;
import
java.io.IOException
;
import
java.io.InputStream
;
import
java.net.URI
;
import
java.util.ArrayList
;
import
java.util.List
;
import
javax.annotation.Nonnull
;
/**
* Abstraction over {@link File}, {@link FilePath}, or other items such as network resources or ZIP entries.
* Assumed to be read-only and makes very limited assumptions, just enough to display content and traverse directories.
* @see DirectoryBrowserSupport
* @since 1.531
*/
public
abstract
class
VirtualFile
implements
Comparable
<
VirtualFile
>
{
/**
* Gets the base name, meaning just the last portion of the path name without any
* directories.
*
* For a “root directory” this may be the empty string.
* @return a simple name (no slashes)
*/
public
abstract
@Nonnull
String
getName
();
/**
* Gets a URI.
* Should at least uniquely identify this virtual file within its root, but not necessarily globally.
* @return a URI (need not be absolute)
*/
public
abstract
URI
toURI
();
/**
* Gets the parent file.
* Need only operate within the originally given root.
* @return the parent
*/
public
abstract
VirtualFile
getParent
();
/**
* Checks whether this file exists and is a directory.
* @return true if it is a directory, false if a file or nonexistent
* @throws IOException in case checking status failed
*/
public
abstract
boolean
isDirectory
()
throws
IOException
;
/**
* Checks whether this file exists and is a plain file.
* @return true if it is a file, false if a directory or nonexistent
* @throws IOException in case checking status failed
*/
public
abstract
boolean
isFile
()
throws
IOException
;
/**
* Checks whether this file exists.
* @return true if it is a plain file or directory, false if nonexistent
* @throws IOException in case checking status failed
*/
public
abstract
boolean
exists
()
throws
IOException
;
/**
* Lists children of this directory.
* @return a list of children (files and subdirectories); empty for a file or nonexistent directory
* @throws IOException if this directory exists but listing was not possible for some other reason
*/
public
abstract
@Nonnull
VirtualFile
[]
list
()
throws
IOException
;
/**
* Lists recursive files of this directory with pattern matching.
* @param glob an Ant-style glob
* @return a list of relative names of children (files directly inside or in subdirectories)
* @throws IOException if this is not a directory, or listing was not possible for some other reason
*/
public
abstract
@Nonnull
String
[]
list
(
String
glob
)
throws
IOException
;
/**
* Obtains a child file.
* @param name a relative path, possibly including {@code /} (but not {@code ..})
* @return a representation of that child, whether it actually exists or not
*/
public
abstract
@Nonnull
VirtualFile
child
(
@Nonnull
String
name
);
/**
* Gets the file length.
* @return a length, or 0 if inapplicable (e.g. a directory)
* @throws IOException if checking the length failed
*/
public
abstract
long
length
()
throws
IOException
;
/**
* Gets the file timestamp.
* @return a length, or 0 if inapplicable
* @throws IOException if checking the timestamp failed
*/
public
abstract
long
lastModified
()
throws
IOException
;
/**
* Checks whether this file can be read.
* @return true normally
* @throws IOException if checking status failed
*/
public
abstract
boolean
canRead
()
throws
IOException
;
/**
* Opens an input stream on the file so its contents can be read.
* @return an open stream
* @throws IOException if it could not be opened
*/
public
abstract
InputStream
open
()
throws
IOException
;
/**
* Does case-insensitive comparison.
* @inheritDoc
*/
@Override
public
final
int
compareTo
(
VirtualFile
o
)
{
return
getName
().
compareToIgnoreCase
(
o
.
getName
());
}
/**
* Compares according to {@link #toURI}.
* @inheritDoc
*/
@Override
public
final
boolean
equals
(
Object
obj
)
{
return
obj
instanceof
VirtualFile
&&
toURI
().
equals
(((
VirtualFile
)
obj
).
toURI
());
}
/**
* Hashes according to {@link #toURI}.
* @inheritDoc
*/
@Override
public
final
int
hashCode
()
{
return
toURI
().
hashCode
();
}
/**
* Displays {@link #toURI}.
* @inheritDoc
*/
@Override
public
final
String
toString
()
{
return
toURI
().
toString
();
}
/**
* Creates a virtual file wrapper for a local file.
* @param f a disk file (need not exist)
* @return a wrapper
*/
public
static
VirtualFile
forFile
(
final
File
f
)
{
return
new
VirtualFile
()
{
@Override
public
String
getName
()
{
return
f
.
getName
();
}
@Override
public
URI
toURI
()
{
return
f
.
toURI
();
}
@Override
public
VirtualFile
getParent
()
{
return
forFile
(
f
.
getParentFile
());
}
@Override
public
boolean
isDirectory
()
throws
IOException
{
return
f
.
isDirectory
();
}
@Override
public
boolean
isFile
()
throws
IOException
{
return
f
.
isFile
();
}
@Override
public
boolean
exists
()
throws
IOException
{
return
f
.
exists
();
}
@Override
public
VirtualFile
[]
list
()
throws
IOException
{
File
[]
kids
=
f
.
listFiles
();
if
(
kids
==
null
)
{
return
new
VirtualFile
[
0
];
}
VirtualFile
[]
vfs
=
new
VirtualFile
[
kids
.
length
];
for
(
int
i
=
0
;
i
<
kids
.
length
;
i
++)
{
vfs
[
i
]
=
forFile
(
kids
[
i
]);
}
return
vfs
;
}
@Override
public
String
[]
list
(
String
glob
)
throws
IOException
{
return
new
Scanner
(
glob
).
invoke
(
f
,
null
);
}
@Override
public
VirtualFile
child
(
String
name
)
{
return
forFile
(
new
File
(
f
,
name
));
}
@Override
public
long
length
()
throws
IOException
{
return
f
.
length
();
}
@Override
public
long
lastModified
()
throws
IOException
{
return
f
.
lastModified
();
}
@Override
public
boolean
canRead
()
throws
IOException
{
return
f
.
canRead
();
}
@Override
public
InputStream
open
()
throws
IOException
{
return
new
FileInputStream
(
f
);
}
};
}
/**
* Creates a virtual file wrapper for a remotable file.
* @param f a local or remote file (need not exist)
* @return a wrapper
*/
public
static
VirtualFile
forFilePath
(
final
FilePath
f
)
{
return
new
VirtualFile
()
{
@Override
public
String
getName
()
{
return
f
.
getName
();
}
@Override
public
URI
toURI
()
{
try
{
return
f
.
toURI
();
}
catch
(
Exception
x
)
{
return
URI
.
create
(
f
.
getRemote
());
}
}
@Override
public
VirtualFile
getParent
()
{
return
f
.
getParent
().
toVirtualFile
();
}
@Override
public
boolean
isDirectory
()
throws
IOException
{
try
{
return
f
.
isDirectory
();
}
catch
(
InterruptedException
x
)
{
throw
(
IOException
)
new
IOException
(
x
.
toString
()).
initCause
(
x
);
}
}
@Override
public
boolean
isFile
()
throws
IOException
{
// TODO should probably introduce a method for this purpose
return
exists
()
&&
!
isDirectory
();
}
@Override
public
boolean
exists
()
throws
IOException
{
try
{
return
f
.
exists
();
}
catch
(
InterruptedException
x
)
{
throw
(
IOException
)
new
IOException
(
x
.
toString
()).
initCause
(
x
);
}
}
@Override
public
VirtualFile
[]
list
()
throws
IOException
{
try
{
List
<
FilePath
>
kids
=
f
.
list
();
if
(
kids
==
null
)
{
return
new
VirtualFile
[
0
];
}
VirtualFile
[]
vfs
=
new
VirtualFile
[
kids
.
size
()];
for
(
int
i
=
0
;
i
<
vfs
.
length
;
i
++)
{
vfs
[
i
]
=
forFilePath
(
kids
.
get
(
i
));
}
return
vfs
;
}
catch
(
InterruptedException
x
)
{
throw
(
IOException
)
new
IOException
(
x
.
toString
()).
initCause
(
x
);
}
}
@Override
public
String
[]
list
(
String
glob
)
throws
IOException
{
try
{
return
f
.
act
(
new
Scanner
(
glob
));
}
catch
(
InterruptedException
x
)
{
throw
(
IOException
)
new
IOException
(
x
.
toString
()).
initCause
(
x
);
}
}
@Override
public
VirtualFile
child
(
String
name
)
{
return
forFilePath
(
f
.
child
(
name
));
}
@Override
public
long
length
()
throws
IOException
{
try
{
return
f
.
length
();
}
catch
(
InterruptedException
x
)
{
throw
(
IOException
)
new
IOException
(
x
.
toString
()).
initCause
(
x
);
}
}
@Override
public
long
lastModified
()
throws
IOException
{
try
{
return
f
.
lastModified
();
}
catch
(
InterruptedException
x
)
{
throw
(
IOException
)
new
IOException
(
x
.
toString
()).
initCause
(
x
);
}
}
@Override
public
boolean
canRead
()
throws
IOException
{
try
{
return
f
.
act
(
new
Readable
());
}
catch
(
InterruptedException
x
)
{
throw
(
IOException
)
new
IOException
(
x
.
toString
()).
initCause
(
x
);
}
}
@Override
public
InputStream
open
()
throws
IOException
{
return
f
.
read
();
}
};
}
private
static
final
class
Scanner
implements
FilePath
.
FileCallable
<
String
[]>
{
private
final
String
glob
;
Scanner
(
String
glob
)
{
this
.
glob
=
glob
;
}
@Override
public
String
[]
invoke
(
File
f
,
VirtualChannel
channel
)
throws
IOException
{
final
List
<
String
>
paths
=
new
ArrayList
<
String
>();
new
DirScanner
.
Glob
(
glob
,
null
).
scan
(
f
,
new
FileVisitor
()
{
@Override
public
void
visit
(
File
f
,
String
relativePath
)
throws
IOException
{
paths
.
add
(
relativePath
);
}
});
return
paths
.
toArray
(
new
String
[
paths
.
size
()]);
}
}
private
static
final
class
Readable
implements
FilePath
.
FileCallable
<
Boolean
>
{
@Override
public
Boolean
invoke
(
File
f
,
VirtualChannel
channel
)
throws
IOException
,
InterruptedException
{
return
f
.
canRead
();
}
}
}
core/src/main/resources/jenkins/model/ArtifactManagerConfiguration/config.groovy
0 → 100644
浏览文件 @
b698b672
/*
* The MIT License
*
* Copyright 2013 Jesse Glick.
*
* 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
jenkins.model.ArtifactManagerConfiguration
;
f
=
namespace
(
lib
.
FormTagLib
);
if
(!
jenkins
.
model
.
ArtifactManagerFactoryDescriptor
.
all
().
isEmpty
())
{
f
.
section
(
title:
_
(
"Artifact Management for Builds"
))
{
f
.
block
()
{
f
.
repeatableHeteroProperty
(
field:
"artifactManagerFactories"
,
hasHeader:
true
)
}
}
}
core/src/test/java/hudson/model/RunTest.java
浏览文件 @
b698b672
...
...
@@ -25,18 +25,17 @@
package
hudson.model
;
import
hudson.Util
;
import
hudson.model.Run.Artifact
;
import
hudson.util.StreamTaskListener
;
import
java.io.ByteArrayOutputStream
;
import
java.io.File
;
import
java.nio.charset.Charset
;
import
java.util.Date
;
import
java.util.List
;
import
java.util.TimeZone
;
import
java.util.concurrent.Callable
;
import
java.util.concurrent.ExecutorService
;
import
java.util.concurrent.Executors
;
import
javax.xml.stream.events.Characters
;
import
static
org
.
junit
.
Assert
.*;
import
org.junit.Test
;
...
...
@@ -114,4 +113,37 @@ public class RunTest {
}
}
private
List
<?
extends
Run
<?,
?>.
Artifact
>
createArtifactList
(
String
...
paths
)
throws
Exception
{
Run
r
=
new
Run
(
new
StubJob
(),
0
)
{};
Run
.
ArtifactList
list
=
r
.
new
ArtifactList
();
for
(
String
p
:
paths
)
{
list
.
add
(
r
.
new
Artifact
(
p
,
p
,
p
,
String
.
valueOf
(
p
.
length
()),
"n"
+
list
.
size
()));
// Assuming all test inputs don't need urlencoding
}
list
.
computeDisplayName
();
return
list
;
}
@Test
public
void
artifactListDisambiguation1
()
throws
Exception
{
List
<?
extends
Run
<?,
?>.
Artifact
>
a
=
createArtifactList
(
"a/b/c.xml"
,
"d/f/g.xml"
,
"h/i/j.xml"
);
assertEquals
(
a
.
get
(
0
).
getDisplayPath
(),
"c.xml"
);
assertEquals
(
a
.
get
(
1
).
getDisplayPath
(),
"g.xml"
);
assertEquals
(
a
.
get
(
2
).
getDisplayPath
(),
"j.xml"
);
}
@Test
public
void
artifactListDisambiguation2
()
throws
Exception
{
List
<?
extends
Run
<?,
?>.
Artifact
>
a
=
createArtifactList
(
"a/b/c.xml"
,
"d/f/g.xml"
,
"h/i/g.xml"
);
assertEquals
(
a
.
get
(
0
).
getDisplayPath
(),
"c.xml"
);
assertEquals
(
a
.
get
(
1
).
getDisplayPath
(),
"f/g.xml"
);
assertEquals
(
a
.
get
(
2
).
getDisplayPath
(),
"i/g.xml"
);
}
@Test
public
void
artifactListDisambiguation3
()
throws
Exception
{
List
<?
extends
Run
<?,
?>.
Artifact
>
a
=
createArtifactList
(
"a.xml"
,
"a/a.xml"
);
assertEquals
(
a
.
get
(
0
).
getDisplayPath
(),
"a.xml"
);
assertEquals
(
a
.
get
(
1
).
getDisplayPath
(),
"a/a.xml"
);
}
}
maven-plugin/src/main/java/hudson/maven/MavenBuild.java
浏览文件 @
b698b672
...
...
@@ -25,6 +25,7 @@ package hudson.maven;
import
hudson.EnvVars
;
import
hudson.FilePath
;
import
hudson.Launcher
;
import
hudson.maven.reporters.MavenArtifactRecord
;
import
hudson.maven.reporters.SurefireArchiver
;
import
hudson.maven.reporters.TestFailureDetector
;
...
...
@@ -43,6 +44,7 @@ import hudson.model.Run;
import
hudson.model.TaskListener
;
import
hudson.model.listeners.RunListener
;
import
hudson.remoting.Channel
;
import
hudson.remoting.VirtualChannel
;
import
hudson.scm.ChangeLogSet
;
import
hudson.scm.ChangeLogSet.Entry
;
import
hudson.tasks.BuildWrapper
;
...
...
@@ -71,12 +73,15 @@ import java.util.ArrayList;
import
java.util.Calendar
;
import
java.util.Collections
;
import
java.util.HashMap
;
import
java.util.Iterator
;
import
java.util.LinkedHashMap
;
import
java.util.List
;
import
java.util.Map
;
import
java.util.concurrent.atomic.AtomicBoolean
;
import
java.util.logging.Level
;
import
java.util.logging.Logger
;
import
javax.annotation.CheckForNull
;
import
jenkins.model.ArtifactManager
;
import
jenkins.mvn.SettingsProvider
;
...
...
@@ -402,6 +407,8 @@ public class MavenBuild extends AbstractMavenBuild<MavenModule,MavenBuild> {
class
ProxyImpl
implements
MavenBuildProxy
,
Serializable
{
private
static
final
long
serialVersionUID
=
8865133776526671879L
;
private
final
Map
<
String
,
String
>
artifacts
=
new
LinkedHashMap
<
String
,
String
>();
public
<
V
,
T
extends
Throwable
>
V
execute
(
BuildCallable
<
V
,
T
>
program
)
throws
T
,
IOException
,
InterruptedException
{
return
program
.
call
(
MavenBuild
.
this
);
}
...
...
@@ -429,10 +436,47 @@ public class MavenBuild extends AbstractMavenBuild<MavenModule,MavenBuild> {
return
new
FilePath
(
MavenBuild
.
this
.
getParent
().
getParent
().
getRootDir
());
}
/**
* @deprecated Does not work with {@link ArtifactManager}.
*/
@Deprecated
public
FilePath
getArtifactsDir
()
{
return
new
FilePath
(
MavenBuild
.
this
.
getArtifactsDir
());
}
@Override
public
void
queueArchiving
(
String
artifactPath
,
String
artifact
)
{
artifacts
.
put
(
artifactPath
,
artifact
);
}
void
performArchiving
(
Launcher
launcher
,
BuildListener
listener
)
throws
IOException
,
InterruptedException
{
for
(
Map
.
Entry
<
String
,
String
>
e
:
artifacts
.
entrySet
())
{
listener
.
getLogger
().
println
(
"[JENKINS] Archiving "
+
e
.
getValue
()
+
" to "
+
e
.
getKey
());
}
ArtifactManager
am
=
pickArtifactManager
();
FilePath
ws
=
getWorkspace
();
Map
<
String
,
String
>
artifactsInsideWorkspace
=
new
LinkedHashMap
<
String
,
String
>();
String
prefix
=
ws
.
act
(
new
CanonicalPath
())
+
'/'
;
// try to relativize paths to workspace
Iterator
<
Map
.
Entry
<
String
,
String
>>
it
=
artifacts
.
entrySet
().
iterator
();
while
(
it
.
hasNext
())
{
Map
.
Entry
<
String
,
String
>
e
=
it
.
next
();
String
p
=
new
FilePath
(
ws
,
e
.
getValue
()).
act
(
new
CanonicalPath
());
if
(!
p
.
startsWith
(
prefix
))
{
listener
.
getLogger
().
println
(
p
+
" is not inside "
+
prefix
+
"; will archive in a separate pass"
);
continue
;
}
artifactsInsideWorkspace
.
put
(
e
.
getKey
(),
p
.
substring
(
prefix
.
length
()));
it
.
remove
();
}
if
(!
artifactsInsideWorkspace
.
isEmpty
())
{
am
.
archive
(
ws
,
launcher
,
listener
,
artifactsInsideWorkspace
);
}
// Now handle other files outside the workspace, if any.
for
(
Map
.
Entry
<
String
,
String
>
e
:
artifacts
.
entrySet
())
{
FilePath
f
=
new
FilePath
(
ws
,
e
.
getValue
());
am
.
archive
(
f
.
getParent
(),
launcher
,
listener
,
Collections
.
singletonMap
(
e
.
getKey
(),
f
.
getName
()));
}
}
public
void
setResult
(
Result
result
)
{
MavenBuild
.
this
.
setResult
(
result
);
}
...
...
@@ -476,6 +520,13 @@ public class MavenBuild extends AbstractMavenBuild<MavenModule,MavenBuild> {
}
}
private
static
final
class
CanonicalPath
implements
FilePath
.
FileCallable
<
String
>
{
private
static
final
long
serialVersionUID
=
1
;
@Override
public
String
invoke
(
File
f
,
VirtualChannel
channel
)
throws
IOException
,
InterruptedException
{
return
f
.
getCanonicalPath
().
replace
(
File
.
separatorChar
,
'/'
);
}
}
public
class
ProxyImpl2
extends
ProxyImpl
implements
MavenBuildProxy2
{
private
static
final
long
serialVersionUID
=
-
3377221864644014218L
;
...
...
@@ -761,9 +812,11 @@ public class MavenBuild extends AbstractMavenBuild<MavenModule,MavenBuild> {
{
boolean
normalExit
=
false
;
try
{
ProxyImpl
proxy
=
new
ProxyImpl
();
Result
r
=
process
.
call
(
new
Builder
(
listener
,
new
ProxyImpl
()
,
listener
,
proxy
,
getProject
(),
margs
.
toList
(),
systemProps
));
proxy
.
performArchiving
(
launcher
,
listener
);
normalExit
=
true
;
return
r
;
}
finally
{
...
...
maven-plugin/src/main/java/hudson/maven/MavenBuildProxy.java
浏览文件 @
b698b672
...
...
@@ -25,6 +25,7 @@ package hudson.maven;
import
hudson.FilePath
;
import
hudson.model.Result
;
import
hudson.remoting.Asynchronous
;
import
hudson.remoting.Callable
;
import
hudson.remoting.DelegatingCallable
;
...
...
@@ -32,6 +33,7 @@ import java.io.IOException;
import
java.io.Serializable
;
import
java.util.Calendar
;
import
java.util.List
;
import
jenkins.model.ArtifactManager
;
/**
* Remoting proxy interface for {@link MavenReporter}s to talk to {@link MavenBuild}
...
...
@@ -93,10 +95,20 @@ public interface MavenBuildProxy {
FilePath
getModuleSetRootDir
();
/**
* @
see MavenBuild#getArtifactsDir()
* @
deprecated Does not work with {@link ArtifactManager}.
*/
@Deprecated
FilePath
getArtifactsDir
();
/**
* @param artifactPath a relative {@code /}-separated path
* @param artifact absolute path name on the slave in the workspace
* @see ArtifactManager#archive
* @since 1.531
*/
@Asynchronous
void
queueArchiving
(
String
artifactPath
,
String
artifact
);
/**
* @see MavenBuild#setResult(Result)
*/
...
...
@@ -211,6 +223,10 @@ public interface MavenBuildProxy {
return
core
.
getArtifactsDir
();
}
@Override
public
void
queueArchiving
(
String
artifactPath
,
String
artifact
)
{
core
.
queueArchiving
(
artifactPath
,
artifact
);
}
public
void
setResult
(
Result
result
)
{
core
.
setResult
(
result
);
}
...
...
maven-plugin/src/main/java/hudson/maven/MavenModuleSetBuild.java
浏览文件 @
b698b672
...
...
@@ -811,6 +811,9 @@ public class MavenModuleSetBuild extends AbstractMavenBuild<MavenModuleSet,Maven
mpa
=
new
MavenProbeAction
(
project
,
process
.
channel
);
addAction
(
mpa
);
r
=
process
.
call
(
builder
);
for
(
ProxyImpl2
proxy
:
proxies
.
values
())
{
proxy
.
performArchiving
(
launcher
,
listener
);
}
return
r
;
}
finally
{
builder
.
end
(
launcher
);
...
...
maven-plugin/src/main/java/hudson/maven/reporters/MavenArtifact.java
浏览文件 @
b698b672
...
...
@@ -23,7 +23,6 @@
*/
package
hudson.maven.reporters
;
import
hudson.FilePath
;
import
hudson.Util
;
import
hudson.maven.MavenBuild
;
import
hudson.maven.MavenBuildProxy
;
...
...
@@ -34,7 +33,6 @@ import hudson.model.Run;
import
hudson.util.LRUStringConverter
;
import
jenkins.model.Jenkins
;
import
hudson.util.HttpResponses
;
import
org.apache.maven.artifact.Artifact
;
import
org.apache.maven.artifact.factory.ArtifactFactory
;
import
org.apache.maven.artifact.handler.ArtifactHandler
;
...
...
@@ -48,10 +46,16 @@ import org.kohsuke.stapler.export.Exported;
import
org.kohsuke.stapler.export.ExportedBean
;
import
java.io.File
;
import
java.io.FileNotFoundException
;
import
java.io.FileOutputStream
;
import
java.io.IOException
;
import
java.io.OutputStream
;
import
java.io.Serializable
;
import
java.util.Map
;
import
java.util.logging.Logger
;
import
javax.servlet.ServletException
;
import
org.kohsuke.stapler.StaplerRequest
;
import
org.kohsuke.stapler.StaplerResponse
;
/**
* Captures information about an artifact created by Maven and archived by
...
...
@@ -158,6 +162,7 @@ public final class MavenArtifact implements Serializable {
/**
* Creates a Maven {@link Artifact} back from the persisted data.
* {@link Artifact#getFile} should be deleted when you are finished as it is a temporary copy.
*/
public
Artifact
toArtifact
(
ArtifactHandlerManager
handlerManager
,
ArtifactFactory
factory
,
MavenBuild
build
)
throws
IOException
{
// Hack: presence of custom ArtifactHandler during builds could influence the file extension
...
...
@@ -194,11 +199,18 @@ public final class MavenArtifact implements Serializable {
/**
* Obtains the {@link File} representing the archived artifact.
* This is a temporary copy which you should delete when finished.
* @throws FileNotFoundException if the archived artifact was missing
*/
public
File
getFile
(
MavenBuild
build
)
throws
IOException
{
File
f
=
new
File
(
new
File
(
new
File
(
new
File
(
build
.
getArtifactsDir
(),
groupId
),
artifactId
),
version
),
canonicalName
);
if
(!
f
.
exists
())
throw
new
IOException
(
"Archived artifact is missing: "
+
f
);
File
f
=
File
.
createTempFile
(
"jenkins-"
,
canonicalName
);
f
.
deleteOnExit
();
OutputStream
os
=
new
FileOutputStream
(
f
);
try
{
Util
.
copyStreamAndClose
(
build
.
getArtifactManager
().
root
().
child
(
artifactPath
()).
open
(),
os
);
}
finally
{
os
.
close
();
}
return
f
;
}
...
...
@@ -207,12 +219,17 @@ public final class MavenArtifact implements Serializable {
*
* TODO: figure out how to make this URL more discoverable to the remote API.
*/
public
HttpResponse
doFile
(
@AncestorInPath
MavenArtifactRecord
parent
)
throws
IOException
{
return
HttpResponses
.
staticResource
(
getFile
(
parent
.
parent
));
public
HttpResponse
doFile
(
final
@AncestorInPath
MavenArtifactRecord
parent
)
throws
IOException
{
return
new
HttpResponse
()
{
@Override
public
void
generateResponse
(
StaplerRequest
req
,
StaplerResponse
rsp
,
Object
node
)
throws
IOException
,
ServletException
{
rsp
.
setContentType
(
"application/octet-stream"
);
Util
.
copyStreamAndClose
(
parent
.
parent
.
getArtifactManager
().
root
().
child
(
artifactPath
()).
open
(),
rsp
.
getCompressedOutputStream
(
req
));
}
};
}
private
FilePath
getArtifactArchivePath
(
MavenBuildProxy
build
,
String
groupId
,
String
artifactId
,
String
version
)
{
return
build
.
getArtifactsDir
().
child
(
groupId
).
child
(
artifactId
).
child
(
version
).
child
(
canonicalName
)
;
private
String
artifactPath
(
)
{
return
groupId
+
'/'
+
artifactId
+
'/'
+
version
+
'/'
+
canonicalName
;
}
/**
...
...
@@ -223,58 +240,7 @@ public final class MavenArtifact implements Serializable {
LOGGER
.
fine
(
"Archiving disabled - not archiving "
+
file
);
}
else
{
FilePath
target
=
getArtifactArchivePath
(
build
,
groupId
,
artifactId
,
version
);
FilePath
origin
=
new
FilePath
(
file
);
if
(!
target
.
exists
())
{
listener
.
getLogger
().
println
(
"[JENKINS] Archiving "
+
file
+
" to "
+
target
);
origin
.
copyTo
(
target
);
}
else
if
(!
origin
.
digest
().
equals
(
target
.
digest
()))
{
listener
.
getLogger
().
println
(
"[JENKINS] Re-archiving "
+
file
);
origin
.
copyTo
(
target
);
}
else
{
LOGGER
.
fine
(
"Not actually archiving "
+
origin
+
" due to digest match"
);
}
/* debug probe to investigate "missing artifact" problem typically seen like this:
ERROR: Asynchronous execution failure
java.util.concurrent.ExecutionException: java.io.IOException: Archived artifact is missing: /files/hudson/server/jobs/glassfish-v3/modules/org.glassfish.build$maven-glassfish-extension/builds/2008-04-02_10-17-15/archive/org.glassfish.build/maven-glassfish-extension/1.0-SNAPSHOT/maven-glassfish-extension-1.0-SNAPSHOT.jar
at hudson.remoting.Channel$1.adapt(Channel.java:423)
at hudson.remoting.Channel$1.adapt(Channel.java:418)
at hudson.remoting.FutureAdapter.get(FutureAdapter.java:32)
at hudson.maven.MavenBuilder.call(MavenBuilder.java:140)
at hudson.maven.MavenModuleSetBuild$Builder.call(MavenModuleSetBuild.java:476)
at hudson.maven.MavenModuleSetBuild$Builder.call(MavenModuleSetBuild.java:422)
at hudson.remoting.UserRequest.perform(UserRequest.java:69)
at hudson.remoting.UserRequest.perform(UserRequest.java:23)
at hudson.remoting.Request$2.run(Request.java:200)
at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:417)
at java.util.concurrent.FutureTask$Sync.innerRun(FutureTask.java:269)
at java.util.concurrent.FutureTask.run(FutureTask.java:123)
at java.util.concurrent.ThreadPoolExecutor$Worker.runTask(ThreadPoolExecutor.java:650)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:675)
at java.lang.Thread.run(Thread.java:595)
Caused by: java.io.IOException: Archived artifact is missing: /files/hudson/server/jobs/glassfish-v3/modules/org.glassfish.build$maven-glassfish-extension/builds/2008-04-02_10-17-15/archive/org.glassfish.build/maven-glassfish-extension/1.0-SNAPSHOT/maven-glassfish-extension-1.0-SNAPSHOT.jar
at hudson.maven.reporters.MavenArtifact.getFile(MavenArtifact.java:147)
at hudson.maven.reporters.MavenArtifact.toArtifact(MavenArtifact.java:126)
at hudson.maven.reporters.MavenArtifactRecord.install(MavenArtifactRecord.java:115)
at hudson.maven.reporters.MavenArtifactArchiver$1.call(MavenArtifactArchiver.java:81)
at hudson.maven.reporters.MavenArtifactArchiver$1.call(MavenArtifactArchiver.java:71)
at hudson.maven.MavenBuild$ProxyImpl.execute(MavenBuild.java:255)
at hudson.maven.MavenBuildProxy$Filter$AsyncInvoker.call(MavenBuildProxy.java:177)
at hudson.remoting.UserRequest.perform(UserRequest.java:69)
at hudson.remoting.UserRequest.perform(UserRequest.java:23)
at hudson.remoting.Request$2.run(Request.java:200)
at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:441)
at java.util.concurrent.FutureTask$Sync.innerRun(FutureTask.java:303)
at java.util.concurrent.FutureTask.run(FutureTask.java:138)
at java.util.concurrent.ThreadPoolExecutor$Worker.runTask(ThreadPoolExecutor.java:885)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:907)
at java.lang.Thread.run(Thread.java:619)
*/
if
(!
target
.
exists
())
throw
new
AssertionError
(
"Just copied "
+
file
+
" to "
+
target
+
" but now I can't find it"
);
build
.
queueArchiving
(
artifactPath
(),
file
.
getAbsolutePath
());
}
}
...
...
maven-plugin/src/main/java/hudson/maven/reporters/MavenArtifactArchiver.java
浏览文件 @
b698b672
...
...
@@ -24,7 +24,6 @@
package
hudson.maven.reporters
;
import
hudson.Extension
;
import
hudson.FilePath
;
import
hudson.Util
;
import
hudson.maven.*
;
import
hudson.model.BuildListener
;
...
...
@@ -158,9 +157,9 @@ public class MavenArtifactArchiver extends MavenReporter {
for
(
File
assembly
:
assemblies
)
{
if
(
mavenArtifacts
.
contains
(
assembly
))
continue
;
// looks like this is already archived
FilePath
target
=
build
.
getArtifactsDir
().
child
(
assembly
.
getName
()
);
String
target
=
assembly
.
getName
(
);
listener
.
getLogger
().
println
(
"[JENKINS] Archiving "
+
assembly
+
" to "
+
target
);
new
FilePath
(
assembly
).
copyTo
(
target
);
build
.
queueArchiving
(
target
,
assembly
.
getAbsolutePath
()
);
// TODO: fingerprint
}
}
...
...
maven-plugin/src/main/java/hudson/maven/reporters/MavenArtifactRecord.java
浏览文件 @
b698b672
...
...
@@ -28,6 +28,7 @@ import hudson.maven.RedeployPublisher.WrappedArtifactRepository;
import
hudson.model.AbstractItem
;
import
hudson.model.Action
;
import
hudson.model.TaskListener
;
import
java.io.File
;
import
java.io.IOException
;
import
java.io.PrintStream
;
...
...
@@ -39,8 +40,6 @@ import org.apache.maven.artifact.deployer.ArtifactDeployer;
import
org.apache.maven.artifact.deployer.ArtifactDeploymentException
;
import
org.apache.maven.artifact.factory.ArtifactFactory
;
import
org.apache.maven.artifact.handler.manager.ArtifactHandlerManager
;
import
org.apache.maven.artifact.installer.ArtifactInstallationException
;
import
org.apache.maven.artifact.installer.ArtifactInstaller
;
import
org.apache.maven.artifact.repository.ArtifactRepository
;
import
org.apache.maven.artifact.repository.metadata.GroupRepositoryMetadata
;
import
org.apache.maven.plugin.descriptor.PluginDescriptor
;
...
...
@@ -171,9 +170,11 @@ public class MavenArtifactRecord extends MavenAbstractArtifactRecord<MavenBuild>
((
WrappedArtifactRepository
)
deploymentRepository
).
setUniqueVersion
(
true
);
}
Artifact
main
=
mainArtifact
.
toArtifact
(
handlerManager
,
artifactFactory
,
parent
);
if
(!
isPOM
())
main
.
addMetadata
(
new
ProjectArtifactMetadata
(
main
,
pomArtifact
.
getFile
(
parent
)));
File
pomFile
=
null
;
if
(!
isPOM
())
{
pomFile
=
pomArtifact
.
getFile
(
parent
);
main
.
addMetadata
(
new
ProjectArtifactMetadata
(
main
,
pomFile
));
}
if
(
main
.
getType
().
equals
(
"maven-plugin"
))
{
GroupRepositoryMetadata
metadata
=
new
GroupRepositoryMetadata
(
main
.
getGroupId
());
String
goalPrefix
=
PluginDescriptor
.
getGoalPrefixFromArtifactId
(
main
.
getArtifactId
());
...
...
@@ -188,30 +189,18 @@ public class MavenArtifactRecord extends MavenAbstractArtifactRecord<MavenBuild>
// deploy the main artifact. This also deploys the POM
logger
.
println
(
Messages
.
MavenArtifact_DeployingMainArtifact
(
main
.
getFile
().
getName
()));
deployer
.
deploy
(
main
.
getFile
(),
main
,
deploymentRepository
,
embedder
.
getLocalRepository
());
main
.
getFile
().
delete
();
if
(
pomFile
!=
null
)
{
pomFile
.
delete
();
}
for
(
MavenArtifact
aa
:
attachedArtifacts
)
{
Artifact
a
=
aa
.
toArtifact
(
handlerManager
,
artifactFactory
,
parent
);
logger
.
println
(
Messages
.
MavenArtifact_DeployingMainArtifact
(
a
.
getFile
().
getName
()));
deployer
.
deploy
(
a
.
getFile
(),
a
,
deploymentRepository
,
embedder
.
getLocalRepository
());
a
.
getFile
().
delete
();
}
}
/**
* Installs the artifact to the local Maven repository.
*/
public
void
install
(
MavenEmbedder
embedder
)
throws
MavenEmbedderException
,
IOException
,
ComponentLookupException
,
ArtifactInstallationException
{
ArtifactHandlerManager
handlerManager
=
embedder
.
lookup
(
ArtifactHandlerManager
.
class
);
ArtifactInstaller
installer
=
embedder
.
lookup
(
ArtifactInstaller
.
class
);
ArtifactFactory
factory
=
embedder
.
lookup
(
ArtifactFactory
.
class
);
Artifact
main
=
mainArtifact
.
toArtifact
(
handlerManager
,
factory
,
parent
);
if
(!
isPOM
())
main
.
addMetadata
(
new
ProjectArtifactMetadata
(
main
,
pomArtifact
.
getFile
(
parent
)));
installer
.
install
(
mainArtifact
.
getFile
(
parent
),
main
,
embedder
.
getLocalRepository
());
for
(
MavenArtifact
aa
:
attachedArtifacts
)
installer
.
install
(
aa
.
getFile
(
parent
),
aa
.
toArtifact
(
handlerManager
,
factory
,
parent
),
embedder
.
getLocalRepository
());
}
public
void
recordFingerprints
()
throws
IOException
{
// record fingerprints
...
...
maven-plugin/src/test/java/hudson/maven/reporters/SurefireArchiverUnitTest.java
浏览文件 @
b698b672
...
...
@@ -246,6 +246,8 @@ public class SurefireArchiverUnitTest {
return
null
;
}
@Override
public
void
queueArchiving
(
String
artifactPath
,
String
artifact
)
{}
@Override
public
void
setResult
(
Result
result
)
{
}
...
...
test/src/test/java/hudson/maven/MavenMultiModuleTest.java
浏览文件 @
b698b672
package
hudson.maven
;
import
com.gargoylesoftware.htmlunit.html.HtmlPage
;
import
hudson.FilePath
;
import
hudson.Functions
;
import
org.junit.Assert
;
import
org.jvnet.hudson.test.Bug
;
...
...
@@ -17,13 +18,21 @@ import hudson.model.BuildListener;
import
hudson.model.Job
;
import
hudson.model.PermalinkProjectAction
;
import
hudson.model.Result
;
import
hudson.model.Run
;
import
hudson.tasks.Fingerprinter.FingerprintAction
;
import
hudson.tasks.Maven.MavenInstallation
;
import
java.io.File
;
import
java.io.IOException
;
import
java.util.Collections
;
import
java.util.Map
;
import
java.util.TreeMap
;
import
jenkins.model.ArtifactManager
;
import
java.util.Set
;
import
java.util.TreeSet
;
import
jenkins.model.ArtifactManagerConfiguration
;
import
jenkins.model.ArtifactManagerFactory
;
import
jenkins.util.VirtualFile
;
import
static
org
.
junit
.
Assert
.*;
import
org.junit.Assume
;
import
org.junit.Ignore
;
...
...
@@ -464,6 +473,83 @@ public class MavenMultiModuleTest {
modulesPage
.
getAnchorByText
(
m
.
getDisplayName
()).
openLinkInNewWindow
();
}
@Bug
(
17236
)
@Test
public
void
artifactArchiving
()
throws
Exception
{
ArtifactManagerConfiguration
.
get
().
getArtifactManagerFactories
().
add
(
new
TestAMF
());
j
.
configureDefaultMaven
();
// using Maven 2 so we can test single-module builds
MavenModuleSet
mms
=
j
.
createMavenProject
();
mms
.
setScm
(
new
ExtractResourceSCM
(
getClass
().
getResource
(
"maven-multimod.zip"
)));
mms
.
setAssignedNode
(
j
.
createOnlineSlave
());
j
.
buildAndAssertSuccess
(
mms
);
// We want all the artifacts in a given module to be archived in one operation. But modules are archived separately.
Map
<
String
,
Map
<
String
,
FilePath
>>
expected
=
new
TreeMap
<
String
,
Map
<
String
,
FilePath
>>();
FilePath
ws
=
mms
.
getModule
(
"org.jvnet.hudson.main.test.multimod$multimod-top"
).
getBuildByNumber
(
1
).
getWorkspace
();
expected
.
put
(
"org.jvnet.hudson.main.test.multimod:multimod-top"
,
Collections
.
singletonMap
(
"org.jvnet.hudson.main.test.multimod/multimod-top/1.0-SNAPSHOT/multimod-top-1.0-SNAPSHOT.pom"
,
new
FilePath
(
ws
.
getChannel
(),
"…/org/jvnet/hudson/main/test/multimod/multimod-top/1.0-SNAPSHOT/multimod-top-1.0-SNAPSHOT.pom"
)));
for
(
String
module
:
new
String
[]
{
"moduleA"
,
"moduleB"
,
"moduleC"
})
{
Map
<
String
,
FilePath
>
m
=
new
TreeMap
<
String
,
FilePath
>();
ws
=
mms
.
getModule
(
"org.jvnet.hudson.main.test.multimod$"
+
module
).
getBuildByNumber
(
1
).
getWorkspace
();
m
.
put
(
"org.jvnet.hudson.main.test.multimod/"
+
module
+
"/1.0-SNAPSHOT/"
+
module
+
"-1.0-SNAPSHOT.pom"
,
ws
.
child
(
"pom.xml"
));
m
.
put
(
"org.jvnet.hudson.main.test.multimod/"
+
module
+
"/1.0-SNAPSHOT/"
+
module
+
"-1.0-SNAPSHOT.jar"
,
ws
.
child
(
"target/"
+
module
+
"-1.0-SNAPSHOT.jar"
));
expected
.
put
(
"org.jvnet.hudson.main.test.multimod:"
+
module
,
m
);
}
assertEquals
(
expected
.
toString
(),
TestAM
.
archivings
.
toString
());
// easy to read
assertEquals
(
expected
,
TestAM
.
archivings
);
// compares also FileChannel
// Also check single-module build.
expected
.
clear
();
TestAM
.
archivings
.
clear
();
MavenBuild
isolated
=
j
.
buildAndAssertSuccess
(
mms
.
getModule
(
"org.jvnet.hudson.main.test.multimod$moduleA"
));
assertEquals
(
2
,
isolated
.
number
);
Map
<
String
,
FilePath
>
m
=
new
TreeMap
<
String
,
FilePath
>();
ws
=
isolated
.
getWorkspace
();
m
.
put
(
"org.jvnet.hudson.main.test.multimod/moduleA/1.0-SNAPSHOT/moduleA-1.0-SNAPSHOT.pom"
,
ws
.
child
(
"pom.xml"
));
m
.
put
(
"org.jvnet.hudson.main.test.multimod/moduleA/1.0-SNAPSHOT/moduleA-1.0-SNAPSHOT.jar"
,
ws
.
child
(
"target/moduleA-1.0-SNAPSHOT.jar"
));
expected
.
put
(
"org.jvnet.hudson.main.test.multimod:moduleA"
,
m
);
assertEquals
(
expected
,
TestAM
.
archivings
);
}
public
static
final
class
TestAMF
extends
ArtifactManagerFactory
{
@Override
public
ArtifactManager
managerFor
(
Run
<?,?>
build
)
{
return
new
TestAM
(
build
);
}
}
public
static
final
class
TestAM
extends
ArtifactManager
{
static
final
Map
<
/* module name */
String
,
Map
<
/* archive path */
String
,
/* file in workspace */
FilePath
>>
archivings
=
new
TreeMap
<
String
,
Map
<
String
,
FilePath
>>();
transient
Run
<?,?>
build
;
TestAM
(
Run
<?,?>
build
)
{
onLoad
(
build
);
}
@Override
public
void
onLoad
(
Run
<?,
?>
build
)
{
this
.
build
=
build
;
}
@Override
public
void
archive
(
FilePath
workspace
,
Launcher
launcher
,
BuildListener
listener
,
Map
<
String
,
String
>
artifacts
)
throws
IOException
,
InterruptedException
{
String
name
=
build
.
getParent
().
getName
();
if
(
archivings
.
containsKey
(
name
))
{
// Would be legitimate only if some archived files for a given module were outside workspace, such as repository parent POM, *and* others were inside, which is not the case in this test.
throw
new
IOException
(
"repeated archiving to "
+
name
);
}
Map
<
String
,
FilePath
>
m
=
new
TreeMap
<
String
,
FilePath
>();
for
(
Map
.
Entry
<
String
,
String
>
e
:
artifacts
.
entrySet
())
{
FilePath
f
=
workspace
.
child
(
e
.
getValue
());
if
(
f
.
exists
())
{
if
(
f
.
getRemote
().
replace
(
'\\'
,
'/'
).
contains
(
"/org/jvnet/hudson/main/test/"
))
{
// Inside the local repository. Hard to know exactly what that path might be, so just mask it out.
f
=
new
FilePath
(
f
.
getChannel
(),
f
.
getRemote
().
replaceFirst
(
"^.+(?=[/\\\\]org[/\\\\]jvnet[/\\\\]hudson[/\\\\]main[/\\\\]test[/\\\\])"
,
"…"
));
}
m
.
put
(
e
.
getKey
(),
f
);
}
else
{
throw
new
IOException
(
"no such file "
+
f
);
}
}
archivings
.
put
(
name
,
m
);
}
@Override
public
boolean
delete
()
throws
IOException
,
InterruptedException
{
throw
new
IOException
();
}
@Override
public
VirtualFile
root
()
{
throw
new
UnsupportedOperationException
();
}
}
/*
@Test public void parallelMultiModMavenWsExists() throws Exception {
configureDefaultMaven();
...
...
test/src/test/java/hudson/model/RunTest.java
浏览文件 @
b698b672
...
...
@@ -26,9 +26,6 @@ package hudson.model;
import
java.net.HttpURLConnection
;
import
java.util.Collection
;
import
java.util.Collections
;
import
static
org
.
junit
.
Assert
.*;
import
java.util.List
;
import
org.junit.Rule
;
import
org.junit.Test
;
import
org.jvnet.hudson.test.Bug
;
...
...
@@ -41,37 +38,6 @@ public class RunTest {
@Rule
public
JenkinsRule
j
=
new
JenkinsRule
();
private
List
<?
extends
Run
<?,?>.
Artifact
>
createArtifactList
(
String
...
paths
)
throws
Exception
{
FreeStyleProject
prj
=
j
.
createFreeStyleProject
();
FreeStyleBuild
r
=
prj
.
scheduleBuild2
(
0
).
get
();
Run
<
FreeStyleProject
,
FreeStyleBuild
>.
ArtifactList
list
=
r
.
new
ArtifactList
();
for
(
String
p
:
paths
)
{
list
.
add
(
r
.
new
Artifact
(
p
,
p
,
p
,
String
.
valueOf
(
p
.
length
()),
"n"
+
list
.
size
()));
// Assuming all test inputs don't need urlencoding
}
list
.
computeDisplayName
();
return
list
;
}
@Test
public
void
artifactListDisambiguation1
()
throws
Exception
{
List
<?
extends
Run
<?,
?>.
Artifact
>
a
=
createArtifactList
(
"a/b/c.xml"
,
"d/f/g.xml"
,
"h/i/j.xml"
);
assertEquals
(
a
.
get
(
0
).
getDisplayPath
(),
"c.xml"
);
assertEquals
(
a
.
get
(
1
).
getDisplayPath
(),
"g.xml"
);
assertEquals
(
a
.
get
(
2
).
getDisplayPath
(),
"j.xml"
);
}
@Test
public
void
artifactListDisambiguation2
()
throws
Exception
{
List
<?
extends
Run
<?,
?>.
Artifact
>
a
=
createArtifactList
(
"a/b/c.xml"
,
"d/f/g.xml"
,
"h/i/g.xml"
);
assertEquals
(
a
.
get
(
0
).
getDisplayPath
(),
"c.xml"
);
assertEquals
(
a
.
get
(
1
).
getDisplayPath
(),
"f/g.xml"
);
assertEquals
(
a
.
get
(
2
).
getDisplayPath
(),
"i/g.xml"
);
}
@Test
public
void
artifactListDisambiguation3
()
throws
Exception
{
List
<?
extends
Run
<?,
?>.
Artifact
>
a
=
createArtifactList
(
"a.xml"
,
"a/a.xml"
);
assertEquals
(
a
.
get
(
0
).
getDisplayPath
(),
"a.xml"
);
assertEquals
(
a
.
get
(
1
).
getDisplayPath
(),
"a/a.xml"
);
}
@Bug
(
17935
)
@Test
public
void
getDynamicInvisibleTransientAction
()
throws
Exception
{
TransientBuildActionFactory
.
all
().
add
(
0
,
new
TransientBuildActionFactory
()
{
...
...
编辑
预览
Markdown
is supported
0%
请重试
或
添加新附件
.
添加附件
取消
You are about to add
0
people
to the discussion. Proceed with caution.
先完成此消息的编辑!
取消
想要评论请
注册
或
登录