提交 a92245a5 编写于 作者: K Kohsuke Kawaguchi

[FIXED JENKINS-23713] Allow BuildStep to work with non-AbstractProject

Merged pull request #1330
...@@ -58,6 +58,9 @@ Upcoming changes</a> ...@@ -58,6 +58,9 @@ Upcoming changes</a>
<li class="major rfe"> <li class="major rfe">
Moved JUnit reporting functionality to a plugin. Moved JUnit reporting functionality to a plugin.
(<a href="https://issues.jenkins-ci.org/browse/JENKINS-23263">issue 23263</a>) (<a href="https://issues.jenkins-ci.org/browse/JENKINS-23263">issue 23263</a>)
<li class=rfe>
Allow BuildStep to work with non-AbstractProject
(<a href="https://issues.jenkins-ci.org/browse/JENKINS-23713">issue 23713</a>)
</ul> </ul>
</div><!--=TRUNK-END=--> </div><!--=TRUNK-END=-->
......
...@@ -119,7 +119,7 @@ public class Fingerprint implements ModelObject, Saveable { ...@@ -119,7 +119,7 @@ public class Fingerprint implements ModelObject, Saveable {
* Gets the {@link Job} that this pointer points to, * Gets the {@link Job} that this pointer points to,
* or null if such a job no longer exists. * or null if such a job no longer exists.
*/ */
public AbstractProject getJob() { public AbstractProject getJob() { // TODO add variant returning Job
return Jenkins.getInstance().getItemByFullName(name,AbstractProject.class); return Jenkins.getInstance().getItemByFullName(name,AbstractProject.class);
} }
...@@ -894,7 +894,15 @@ public class Fingerprint implements ModelObject, Saveable { ...@@ -894,7 +894,15 @@ public class Fingerprint implements ModelObject, Saveable {
return r; return r;
} }
@Deprecated
public synchronized void add(AbstractBuild b) throws IOException { public synchronized void add(AbstractBuild b) throws IOException {
addFor((Run) b);
}
/**
* @since 1.577
*/
public synchronized void addFor(Run b) throws IOException {
add(b.getParent().getFullName(), b.getNumber()); add(b.getParent().getFullName(), b.getNumber());
} }
......
...@@ -27,10 +27,10 @@ import hudson.FilePath; ...@@ -27,10 +27,10 @@ import hudson.FilePath;
import hudson.Launcher; import hudson.Launcher;
import hudson.Util; import hudson.Util;
import hudson.Extension; import hudson.Extension;
import hudson.model.AbstractBuild;
import hudson.model.AbstractProject; import hudson.model.AbstractProject;
import hudson.model.BuildListener;
import hudson.model.Result; import hudson.model.Result;
import hudson.model.Run;
import hudson.model.TaskListener;
import hudson.model.listeners.ItemListener; import hudson.model.listeners.ItemListener;
import hudson.remoting.VirtualChannel; import hudson.remoting.VirtualChannel;
import hudson.util.FormValidation; import hudson.util.FormValidation;
...@@ -52,6 +52,8 @@ import net.sf.json.JSONObject; ...@@ -52,6 +52,8 @@ import net.sf.json.JSONObject;
import javax.annotation.Nonnull; import javax.annotation.Nonnull;
import jenkins.model.BuildDiscarder; import jenkins.model.BuildDiscarder;
import jenkins.model.Jenkins; import jenkins.model.Jenkins;
import jenkins.tasks.SimpleBuildStep;
import jenkins.util.BuildListenerAdapter;
import org.kohsuke.stapler.DataBoundSetter; import org.kohsuke.stapler.DataBoundSetter;
/** /**
...@@ -59,7 +61,7 @@ import org.kohsuke.stapler.DataBoundSetter; ...@@ -59,7 +61,7 @@ import org.kohsuke.stapler.DataBoundSetter;
* *
* @author Kohsuke Kawaguchi * @author Kohsuke Kawaguchi
*/ */
public class ArtifactArchiver extends Recorder { public class ArtifactArchiver extends Recorder implements SimpleBuildStep {
private static final Logger LOG = Logger.getLogger(ArtifactArchiver.class.getName()); private static final Logger LOG = Logger.getLogger(ArtifactArchiver.class.getName());
...@@ -185,7 +187,7 @@ public class ArtifactArchiver extends Recorder { ...@@ -185,7 +187,7 @@ public class ArtifactArchiver extends Recorder {
this.defaultExcludes = defaultExcludes; this.defaultExcludes = defaultExcludes;
} }
private void listenerWarnOrError(BuildListener listener, String message) { private void listenerWarnOrError(TaskListener listener, String message) {
if (allowEmptyArchive) { if (allowEmptyArchive) {
listener.getLogger().println(String.format("WARN: %s", message)); listener.getLogger().println(String.format("WARN: %s", message));
} else { } else {
...@@ -194,32 +196,27 @@ public class ArtifactArchiver extends Recorder { ...@@ -194,32 +196,27 @@ public class ArtifactArchiver extends Recorder {
} }
@Override @Override
public boolean perform(AbstractBuild<?,?> build, Launcher launcher, BuildListener listener) throws InterruptedException { public void perform(Run<?,?> build, FilePath ws, Launcher launcher, TaskListener listener) throws InterruptedException {
if(artifacts.length()==0) { if(artifacts.length()==0) {
listener.error(Messages.ArtifactArchiver_NoIncludes()); listener.error(Messages.ArtifactArchiver_NoIncludes());
build.setResult(Result.FAILURE); build.setResult(Result.FAILURE);
return true; return;
} }
if (onlyIfSuccessful && build.getResult() != null && build.getResult().isWorseThan(Result.UNSTABLE)) { if (onlyIfSuccessful && build.getResult() != null && build.getResult().isWorseThan(Result.UNSTABLE)) {
listener.getLogger().println(Messages.ArtifactArchiver_SkipBecauseOnlyIfSuccessful()); listener.getLogger().println(Messages.ArtifactArchiver_SkipBecauseOnlyIfSuccessful());
return true; return;
} }
listener.getLogger().println(Messages.ArtifactArchiver_ARCHIVING_ARTIFACTS()); listener.getLogger().println(Messages.ArtifactArchiver_ARCHIVING_ARTIFACTS());
try { try {
FilePath ws = build.getWorkspace();
if (ws==null) { // #3330: slave down?
return true;
}
String artifacts = build.getEnvironment(listener).expand(this.artifacts); String artifacts = build.getEnvironment(listener).expand(this.artifacts);
Map<String,String> files = ws.act(new ListFiles(artifacts, excludes, defaultExcludes)); Map<String,String> files = ws.act(new ListFiles(artifacts, excludes, defaultExcludes));
if (!files.isEmpty()) { if (!files.isEmpty()) {
build.pickArtifactManager().archive(ws, launcher, listener, files); build.pickArtifactManager().archive(ws, launcher, BuildListenerAdapter.wrap(listener), files);
if (fingerprint) { if (fingerprint) {
new Fingerprinter(artifacts).perform(build, launcher, listener); new Fingerprinter(artifacts).perform(build, ws, launcher, listener);
} }
} else { } else {
Result result = build.getResult(); Result result = build.getResult();
...@@ -239,17 +236,15 @@ public class ArtifactArchiver extends Recorder { ...@@ -239,17 +236,15 @@ public class ArtifactArchiver extends Recorder {
if (!allowEmptyArchive) { if (!allowEmptyArchive) {
build.setResult(Result.FAILURE); build.setResult(Result.FAILURE);
} }
return true; return;
} }
} catch (IOException e) { } catch (IOException e) {
Util.displayIOException(e,listener); Util.displayIOException(e,listener);
e.printStackTrace(listener.error( e.printStackTrace(listener.error(
Messages.ArtifactArchiver_FailedToArchive(artifacts))); Messages.ArtifactArchiver_FailedToArchive(artifacts)));
build.setResult(Result.FAILURE); build.setResult(Result.FAILURE);
return true; return;
} }
return true;
} }
private static final class ListFiles implements FilePath.FileCallable<Map<String,String>> { private static final class ListFiles implements FilePath.FileCallable<Map<String,String>> {
......
...@@ -23,6 +23,8 @@ ...@@ -23,6 +23,8 @@
*/ */
package hudson.tasks; package hudson.tasks;
import hudson.AbortException;
import hudson.FilePath;
import hudson.model.Build; import hudson.model.Build;
import hudson.model.BuildListener; import hudson.model.BuildListener;
import hudson.model.Action; import hudson.model.Action;
...@@ -35,6 +37,10 @@ import java.io.IOException; ...@@ -35,6 +37,10 @@ import java.io.IOException;
import java.util.Collection; import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import hudson.model.Run;
import hudson.model.TaskListener;
import jenkins.tasks.SimpleBuildStep;
/** /**
* Provides compatibility with {@link BuildStep} before 1.150 * Provides compatibility with {@link BuildStep} before 1.150
* so that old plugin binaries can continue to function with new Hudson. * so that old plugin binaries can continue to function with new Hudson.
...@@ -54,11 +60,25 @@ public abstract class BuildStepCompatibilityLayer implements BuildStep { ...@@ -54,11 +60,25 @@ public abstract class BuildStepCompatibilityLayer implements BuildStep {
return true; return true;
} }
/**
* @inheritDoc
* @return Delegates to {@link SimpleBuildStep#perform(Run, FilePath, Launcher, TaskListener)} if possible, always returning true or throwing an error.
*/
public boolean perform(AbstractBuild<?,?> build, Launcher launcher, BuildListener listener) throws InterruptedException, IOException { public boolean perform(AbstractBuild<?,?> build, Launcher launcher, BuildListener listener) throws InterruptedException, IOException {
if (build instanceof Build) if (this instanceof SimpleBuildStep) {
// delegate to the overloaded version defined in SimpleBuildStep
FilePath workspace = build.getWorkspace();
if (workspace == null) {
throw new AbortException("no workspace for " + build);
}
((SimpleBuildStep) this).perform(build, workspace, launcher, listener);
return true;
} else if (build instanceof Build) {
// delegate to the legacy signature deprecated in 1.312
return perform((Build)build,launcher,listener); return perform((Build)build,launcher,listener);
else } else {
return true; return true;
}
} }
public Action getProjectAction(AbstractProject<?, ?> project) { public Action getProjectAction(AbstractProject<?, ?> project) {
......
...@@ -33,7 +33,6 @@ import hudson.Util; ...@@ -33,7 +33,6 @@ import hudson.Util;
import hudson.model.AbstractBuild; import hudson.model.AbstractBuild;
import hudson.model.AbstractProject; import hudson.model.AbstractProject;
import hudson.model.Action; import hudson.model.Action;
import hudson.model.BuildListener;
import jenkins.model.DependencyDeclarer; import jenkins.model.DependencyDeclarer;
import hudson.model.DependencyGraph; import hudson.model.DependencyGraph;
import hudson.model.DependencyGraph.Dependency; import hudson.model.DependencyGraph.Dependency;
...@@ -74,13 +73,14 @@ import java.util.TreeMap; ...@@ -74,13 +73,14 @@ import java.util.TreeMap;
import java.util.logging.Level; import java.util.logging.Level;
import java.util.logging.Logger; import java.util.logging.Logger;
import jenkins.model.RunAction2; import jenkins.model.RunAction2;
import jenkins.tasks.SimpleBuildStep;
/** /**
* Records fingerprints of the specified files. * Records fingerprints of the specified files.
* *
* @author Kohsuke Kawaguchi * @author Kohsuke Kawaguchi
*/ */
public class Fingerprinter extends Recorder implements Serializable, DependencyDeclarer { public class Fingerprinter extends Recorder implements Serializable, DependencyDeclarer, SimpleBuildStep {
public static boolean enableFingerprintsInDependencyGraph = Boolean.getBoolean(Fingerprinter.class.getName() + ".enableFingerprintsInDependencyGraph"); public static boolean enableFingerprintsInDependencyGraph = Boolean.getBoolean(Fingerprinter.class.getName() + ".enableFingerprintsInDependencyGraph");
/** /**
...@@ -111,7 +111,7 @@ public class Fingerprinter extends Recorder implements Serializable, DependencyD ...@@ -111,7 +111,7 @@ public class Fingerprinter extends Recorder implements Serializable, DependencyD
} }
@Override @Override
public boolean perform(AbstractBuild<?,?> build, Launcher launcher, BuildListener listener) throws InterruptedException { public void perform(Run<?,?> build, FilePath workspace, Launcher launcher, TaskListener listener) throws InterruptedException {
try { try {
listener.getLogger().println(Messages.Fingerprinter_Recording()); listener.getLogger().println(Messages.Fingerprinter_Recording());
...@@ -120,7 +120,7 @@ public class Fingerprinter extends Recorder implements Serializable, DependencyD ...@@ -120,7 +120,7 @@ public class Fingerprinter extends Recorder implements Serializable, DependencyD
EnvVars environment = build.getEnvironment(listener); EnvVars environment = build.getEnvironment(listener);
if(targets.length()!=0) { if(targets.length()!=0) {
String expandedTargets = environment.expand(targets); String expandedTargets = environment.expand(targets);
record(build, listener, record, expandedTargets); record(build, workspace, listener, record, expandedTargets);
} }
FingerprintAction fingerprintAction = build.getAction(FingerprintAction.class); FingerprintAction fingerprintAction = build.getAction(FingerprintAction.class);
...@@ -139,7 +139,6 @@ public class Fingerprinter extends Recorder implements Serializable, DependencyD ...@@ -139,7 +139,6 @@ public class Fingerprinter extends Recorder implements Serializable, DependencyD
} }
// failing to record fingerprints is an error but not fatal // failing to record fingerprints is an error but not fatal
return true;
} }
public BuildStepMonitor getRequiredMonitorService() { public BuildStepMonitor getRequiredMonitorService() {
...@@ -185,7 +184,7 @@ public class Fingerprinter extends Recorder implements Serializable, DependencyD ...@@ -185,7 +184,7 @@ public class Fingerprinter extends Recorder implements Serializable, DependencyD
} }
} }
private void record(AbstractBuild<?,?> build, BuildListener listener, Map<String,String> record, final String targets) throws IOException, InterruptedException { private void record(Run<?,?> build, FilePath ws, TaskListener listener, Map<String,String> record, final String targets) throws IOException, InterruptedException {
final class Record implements Serializable { final class Record implements Serializable {
final boolean produced; final boolean produced;
final String relativePath; final String relativePath;
...@@ -199,7 +198,7 @@ public class Fingerprinter extends Recorder implements Serializable, DependencyD ...@@ -199,7 +198,7 @@ public class Fingerprinter extends Recorder implements Serializable, DependencyD
this.md5sum = md5sum; this.md5sum = md5sum;
} }
Fingerprint addRecord(AbstractBuild build) throws IOException { Fingerprint addRecord(Run build) throws IOException {
FingerprintMap map = Jenkins.getInstance().getFingerprintMap(); FingerprintMap map = Jenkins.getInstance().getFingerprintMap();
return map.getOrCreate(produced?build:null, fileName, md5sum); return map.getOrCreate(produced?build:null, fileName, md5sum);
} }
...@@ -209,13 +208,6 @@ public class Fingerprinter extends Recorder implements Serializable, DependencyD ...@@ -209,13 +208,6 @@ public class Fingerprinter extends Recorder implements Serializable, DependencyD
final long buildTimestamp = build.getTimeInMillis(); final long buildTimestamp = build.getTimeInMillis();
FilePath ws = build.getWorkspace();
if(ws==null) {
listener.error(Messages.Fingerprinter_NoWorkspace());
build.setResult(Result.FAILURE);
return;
}
List<Record> records = ws.act(new FileCallable<List<Record>>() { List<Record> records = ws.act(new FileCallable<List<Record>>() {
public List<Record> invoke(File baseDir, VirtualChannel channel) throws IOException { public List<Record> invoke(File baseDir, VirtualChannel channel) throws IOException {
List<Record> results = new ArrayList<Record>(); List<Record> results = new ArrayList<Record>();
...@@ -250,7 +242,7 @@ public class Fingerprinter extends Recorder implements Serializable, DependencyD ...@@ -250,7 +242,7 @@ public class Fingerprinter extends Recorder implements Serializable, DependencyD
listener.error(Messages.Fingerprinter_FailedFor(r.relativePath)); listener.error(Messages.Fingerprinter_FailedFor(r.relativePath));
continue; continue;
} }
fp.add(build); fp.addFor(build);
record.put(r.relativePath,fp.getHashString()); record.put(r.relativePath,fp.getHashString());
} }
} }
...@@ -285,7 +277,7 @@ public class Fingerprinter extends Recorder implements Serializable, DependencyD ...@@ -285,7 +277,7 @@ public class Fingerprinter extends Recorder implements Serializable, DependencyD
*/ */
public static final class FingerprintAction implements RunAction2 { public static final class FingerprintAction implements RunAction2 {
private transient AbstractBuild build; private transient Run build;
private static final Random rand = new Random(); private static final Random rand = new Random();
...@@ -296,12 +288,17 @@ public class Fingerprinter extends Recorder implements Serializable, DependencyD ...@@ -296,12 +288,17 @@ public class Fingerprinter extends Recorder implements Serializable, DependencyD
private transient WeakReference<Map<String,Fingerprint>> ref; private transient WeakReference<Map<String,Fingerprint>> ref;
public FingerprintAction(AbstractBuild build, Map<String, String> record) { public FingerprintAction(Run build, Map<String, String> record) {
this.build = build; this.build = build;
this.record = PackedMap.of(record); this.record = PackedMap.of(record);
compact(); compact();
} }
@Deprecated
public FingerprintAction(AbstractBuild build, Map<String, String> record) {
this((Run) build, record);
}
public void add(Map<String,String> moreRecords) { public void add(Map<String,String> moreRecords) {
Map<String,String> r = new HashMap<String, String>(record); Map<String,String> r = new HashMap<String, String>(record);
r.putAll(moreRecords); r.putAll(moreRecords);
...@@ -322,10 +319,15 @@ public class Fingerprinter extends Recorder implements Serializable, DependencyD ...@@ -322,10 +319,15 @@ public class Fingerprinter extends Recorder implements Serializable, DependencyD
return "fingerprints"; return "fingerprints";
} }
public AbstractBuild getBuild() { public Run getRun() {
return build; return build;
} }
@Deprecated
public AbstractBuild getBuild() {
return build instanceof AbstractBuild ? (AbstractBuild) build : null;
}
/** /**
* Obtains the raw data. * Obtains the raw data.
*/ */
...@@ -334,7 +336,7 @@ public class Fingerprinter extends Recorder implements Serializable, DependencyD ...@@ -334,7 +336,7 @@ public class Fingerprinter extends Recorder implements Serializable, DependencyD
} }
@Override public void onLoad(Run<?,?> r) { @Override public void onLoad(Run<?,?> r) {
build = (AbstractBuild) r; build = r;
compact(); compact();
} }
......
...@@ -24,6 +24,7 @@ ...@@ -24,6 +24,7 @@
package hudson.util; package hudson.util;
import hudson.model.AbstractBuild; import hudson.model.AbstractBuild;
import hudson.model.Run;
import org.jfree.chart.JFreeChart; import org.jfree.chart.JFreeChart;
import org.jfree.chart.axis.NumberAxis; import org.jfree.chart.axis.NumberAxis;
import org.jfree.data.category.CategoryDataset; import org.jfree.data.category.CategoryDataset;
...@@ -46,31 +47,52 @@ public class ChartUtil { ...@@ -46,31 +47,52 @@ public class ChartUtil {
* Can be used as a graph label. Only displays numbers. * Can be used as a graph label. Only displays numbers.
*/ */
public static final class NumberOnlyBuildLabel implements Comparable<NumberOnlyBuildLabel> { public static final class NumberOnlyBuildLabel implements Comparable<NumberOnlyBuildLabel> {
private final Run<?,?> run;
@Deprecated
public final AbstractBuild build; public final AbstractBuild build;
/**
* @since 1.577
*/
public NumberOnlyBuildLabel(Run<?,?> run) {
this.run = run;
this.build = run instanceof AbstractBuild ? (AbstractBuild) run : null;
}
@Deprecated
public NumberOnlyBuildLabel(AbstractBuild build) { public NumberOnlyBuildLabel(AbstractBuild build) {
this.run = build;
this.build = build; this.build = build;
} }
/**
* @since 1.577
*/
public Run<?, ?> getRun() {
return run;
}
public int compareTo(NumberOnlyBuildLabel that) { public int compareTo(NumberOnlyBuildLabel that) {
return this.build.number-that.build.number; return this.run.number-that.run.number;
} }
@Override @Override
public boolean equals(Object o) { public boolean equals(Object o) {
if(!(o instanceof NumberOnlyBuildLabel)) return false; if(!(o instanceof NumberOnlyBuildLabel)) return false;
NumberOnlyBuildLabel that = (NumberOnlyBuildLabel) o; NumberOnlyBuildLabel that = (NumberOnlyBuildLabel) o;
return build==that.build; return run==that.run;
} }
@Override @Override
public int hashCode() { public int hashCode() {
return build.hashCode(); return run.hashCode();
} }
@Override @Override
public String toString() { public String toString() {
return build.getDisplayName(); return run.getDisplayName();
} }
} }
......
...@@ -29,6 +29,7 @@ import hudson.Launcher; ...@@ -29,6 +29,7 @@ import hudson.Launcher;
import hudson.model.AbstractBuild; import hudson.model.AbstractBuild;
import hudson.model.BuildListener; import hudson.model.BuildListener;
import hudson.model.Run; import hudson.model.Run;
import hudson.model.TaskListener;
import hudson.tasks.ArtifactArchiver; import hudson.tasks.ArtifactArchiver;
import java.io.IOException; import java.io.IOException;
import java.util.Map; import java.util.Map;
...@@ -61,7 +62,7 @@ public abstract class ArtifactManager { ...@@ -61,7 +62,7 @@ public abstract class ArtifactManager {
* @param artifacts map from paths in the archive area to paths relative to {@code workspace} (all paths {@code /}-separated) * @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 IOException if transfer or copying failed in any way
* @throws InterruptedException if transfer was interrupted * @throws InterruptedException if transfer was interrupted
* @see ArtifactArchiver#perform(AbstractBuild, Launcher, BuildListener) * @see ArtifactArchiver#perform(Run, FilePath, Launcher, TaskListener)
*/ */
public abstract void archive(FilePath workspace, Launcher launcher, BuildListener listener, Map<String,String> artifacts) throws IOException, InterruptedException; public abstract void archive(FilePath workspace, Launcher launcher, BuildListener listener, Map<String,String> artifacts) throws IOException, InterruptedException;
......
/*
* The MIT License
*
* Copyright 2014 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.tasks;
import hudson.AbortException;
import hudson.Extension;
import hudson.FilePath;
import hudson.Launcher;
import hudson.model.AbstractBuild;
import hudson.model.AbstractProject;
import hudson.model.Action;
import hudson.model.BuildListener;
import hudson.model.InvisibleAction;
import hudson.model.Job;
import hudson.model.Run;
import hudson.model.TaskListener;
import hudson.tasks.BuildStep;
import hudson.tasks.BuildStepDescriptor;
import hudson.tasks.BuildStepMonitor;
import hudson.tasks.Builder;
import hudson.tasks.Publisher;
import java.io.IOException;
import java.util.Collection;
import java.util.LinkedList;
import java.util.List;
import javax.annotation.Nonnull;
import jenkins.model.DependencyDeclarer;
import jenkins.model.RunAction2;
import jenkins.model.TransientActionFactory;
import org.kohsuke.accmod.Restricted;
import org.kohsuke.accmod.restrictions.DoNotUse;
/**
* A build step (like a {@link Builder} or {@link Publisher}) which may be called at an arbitrary time during a build (or multiple times), run, and be done.
* <p>Such a build step would typically be written according to some guidelines that ensure it makes few assumptions about how it is being used:
* <ul>
* <li>Do not implement {@link BuildStep#prebuild}, since this presupposes a particular execution order.
* <li>Do not implement {@link BuildStep#getProjectActions}, since this might never be called
* if the step is not part of the static configuration of a project; instead, add a {@link LastBuildAction} to a build when run.
* <li>Implement {@link BuildStep#getRequiredMonitorService} to be {@link BuildStepMonitor#NONE}, since this facility
* only makes sense for a step called exactly once per build.
* <li>Do not implement {@link DependencyDeclarer} since this would be limited to use in {@link AbstractProject}.
* <li>Return true unconditionally from {@link BuildStepDescriptor#isApplicable} (there is currently no filtering for other {@link Job} types).
* </ul>
* @see hudson.tasks.BuildStepCompatibilityLayer#perform(AbstractBuild, Launcher, BuildListener)
* @since 1.577
*/
public interface SimpleBuildStep extends BuildStep {
/**
* Run this step.
* @param run a build this is running as a part of
* @param workspace a workspace to use for any file operations
* @param launcher a way to start processes
* @param listener a place to send output
* @throws InterruptedException if the step is interrupted
* @throws IOException if something goes wrong; use {@link AbortException} for a polite error
*/
void perform(@Nonnull Run<?,?> run, @Nonnull FilePath workspace, @Nonnull Launcher launcher, @Nonnull TaskListener listener) throws InterruptedException, IOException;
/**
* Marker for explicitly added build actions (as {@link Run#addAction}) which should imply a transient project action ({@link Job#getActions}) when present on the {@link Job#getLastSuccessfulBuild}.
* This can serve as a substitute for {@link BuildStep#getProjectActions} which does not assume that the project can enumerate the steps it would run before they are actually run.
* (Use {@link InvisibleAction} as a base class if you do not need to show anything in the build itself.)
*/
interface LastBuildAction extends Action {
/**
* Optionally add some actions to the project owning this build.
* @return zero or more transient actions; if you need to know the {@link Job}, implement {@link RunAction2} and use {@link Run#getParent}
*/
Collection<? extends Action> getProjectActions();
}
@SuppressWarnings("rawtypes")
@Restricted(DoNotUse.class)
@Extension public static final class LastBuildActionFactory extends TransientActionFactory<Job> {
@Override public Class<Job> type() {
return Job.class;
}
@Override public Collection<? extends Action> createFor(Job j) {
List<Action> actions = new LinkedList<Action>();
Run r = j.getLastSuccessfulBuild();
if (r != null) {
for (LastBuildAction a : r.getActions(LastBuildAction.class)) {
actions.addAll(a.getProjectActions());
}
}
// TODO should there be an option to check lastCompletedBuild even if it failed?
// Not useful for, say, TestResultAction, since if you have a build that fails before recording test results, the job would then have no TestResultProjectAction.
return actions;
}
}
}
/*
* The MIT License
*
* Copyright 2014 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.console.ConsoleNote;
import hudson.model.BuildListener;
import hudson.model.Cause;
import hudson.model.Result;
import hudson.model.TaskListener;
import java.io.IOException;
import java.io.PrintStream;
import java.io.PrintWriter;
import java.util.List;
/**
* Wraps a {@link TaskListener} as a {@link BuildListener} for compatibility with APIs which historically expected the latter.
* Does not support {@link BuildListener#started} or {@link BuildListener#finished}.
*
* @since 1.577
*/
public final class BuildListenerAdapter implements BuildListener {
private final TaskListener delegate;
public BuildListenerAdapter(TaskListener delegate) {
this.delegate = delegate;
}
@Override public void started(List<Cause> causes) {
throw new UnsupportedOperationException();
}
@Override public void finished(Result result) {
throw new UnsupportedOperationException();
}
@Override public PrintStream getLogger() {
return delegate.getLogger();
}
@SuppressWarnings("rawtypes")
@Override public void annotate(ConsoleNote ann) throws IOException {
delegate.annotate(ann);
}
@Override public void hyperlink(String url, String text) throws IOException {
delegate.hyperlink(url, text);
}
@Override public PrintWriter error(String msg) {
return delegate.error(msg);
}
@Override public PrintWriter error(String format, Object... args) {
return delegate.error(format, args);
}
@Override public PrintWriter fatalError(String msg) {
return delegate.fatalError(msg);
}
@Override public PrintWriter fatalError(String format, Object... args) {
return delegate.fatalError(format, args);
}
public static BuildListener wrap(TaskListener l) {
if (l instanceof BuildListener) {
return (BuildListener) l;
} else {
return new BuildListenerAdapter(l);
}
}
}
...@@ -30,7 +30,7 @@ THE SOFTWARE. ...@@ -30,7 +30,7 @@ THE SOFTWARE.
<?jelly escape-by-default='true'?> <?jelly escape-by-default='true'?>
<j:jelly xmlns:j="jelly:core" xmlns:st="jelly:stapler" xmlns:d="jelly:define" xmlns:l="/lib/layout" xmlns:t="/lib/hudson" xmlns:f="/lib/form"> <j:jelly xmlns:j="jelly:core" xmlns:st="jelly:stapler" xmlns:d="jelly:define" xmlns:l="/lib/layout" xmlns:t="/lib/hudson" xmlns:f="/lib/form">
<l:layout> <l:layout>
<st:include it="${it.build}" page="sidepanel.jelly"/> <st:include it="${it.run}" page="sidepanel.jelly"/>
<l:main-panel> <l:main-panel>
<h1> <h1>
<l:icon class="icon-fingerprint icon-xlg"/> <l:icon class="icon-fingerprint icon-xlg"/>
...@@ -54,7 +54,7 @@ THE SOFTWARE. ...@@ -54,7 +54,7 @@ THE SOFTWARE.
<j:when test="${f.original==null}"> <j:when test="${f.original==null}">
${%outside Jenkins} ${%outside Jenkins}
</j:when> </j:when>
<j:when test="${f.original.run==it.build}"> <j:when test="${f.original.run==it.run}">
${%this build} ${%this build}
</j:when> </j:when>
<j:otherwise> <j:otherwise>
......
...@@ -62,7 +62,6 @@ Fingerprinter.DigestFailed=Failed to compute digest for {0} ...@@ -62,7 +62,6 @@ Fingerprinter.DigestFailed=Failed to compute digest for {0}
Fingerprinter.DisplayName=Record fingerprints of files to track usage Fingerprinter.DisplayName=Record fingerprints of files to track usage
Fingerprinter.Failed=Failed to record fingerprints Fingerprinter.Failed=Failed to record fingerprints
Fingerprinter.FailedFor=failed to record fingerprint for {0} Fingerprinter.FailedFor=failed to record fingerprint for {0}
Fingerprinter.NoWorkspace=Unable to record fingerprints because there\u2019s no workspace
Fingerprinter.Recording=Recording fingerprints Fingerprinter.Recording=Recording fingerprints
InstallFromApache=Install from Apache InstallFromApache=Install from Apache
......
...@@ -62,5 +62,4 @@ Maven.NotADirectory={0} er ikke et direktorie ...@@ -62,5 +62,4 @@ Maven.NotADirectory={0} er ikke et direktorie
BuildTrigger.Disabled={0} er sl\u00e5et fra, starter ikke BuildTrigger.Disabled={0} er sl\u00e5et fra, starter ikke
Fingerprinter.Recording=Opsamler filfingeraftryk Fingerprinter.Recording=Opsamler filfingeraftryk
CommandInterpreter.CommandFailed=Kommandoeksekvering fejlede CommandInterpreter.CommandFailed=Kommandoeksekvering fejlede
Fingerprinter.NoWorkspace=Ude af stand til at opsamle filfingeraftryk, da der ikke er et arbejdsomr\u00e5de
Fingerprinter.Failed=Kunne ikke opsamle filfingeraftryk Fingerprinter.Failed=Kunne ikke opsamle filfingeraftryk
...@@ -54,7 +54,6 @@ Fingerprinter.DigestFailed=Berechnung der Pr\u00fcfsumme f\u00fcr {0} fehlgeschl ...@@ -54,7 +54,6 @@ Fingerprinter.DigestFailed=Berechnung der Pr\u00fcfsumme f\u00fcr {0} fehlgeschl
Fingerprinter.DisplayName=Fingerabdr\u00fccke von Dateien aufzeichnen, um deren Verwendung zu verfolgen Fingerprinter.DisplayName=Fingerabdr\u00fccke von Dateien aufzeichnen, um deren Verwendung zu verfolgen
Fingerprinter.Failed=Aufzeichnen der Fingerabdr\u00fccke fehlgeschlagen Fingerprinter.Failed=Aufzeichnen der Fingerabdr\u00fccke fehlgeschlagen
Fingerprinter.FailedFor=Aufzeichnen des Fingerabdrucks f\u00fcr {0} fehlgeschlagen Fingerprinter.FailedFor=Aufzeichnen des Fingerabdrucks f\u00fcr {0} fehlgeschlagen
Fingerprinter.NoWorkspace=Fingerabdr\u00fccke k\u00f6nnen nicht aufgezeichnet werden, weil der Arbeitsbereich fehlt.
Fingerprinter.Recording=Zeichne Fingerabr\u00fccke auf Fingerprinter.Recording=Zeichne Fingerabr\u00fccke auf
InstallFromApache=Installiere von Apache InstallFromApache=Installiere von Apache
......
...@@ -53,7 +53,6 @@ Fingerprinter.DigestFailed=Imposible de calcular la firma para {0} ...@@ -53,7 +53,6 @@ Fingerprinter.DigestFailed=Imposible de calcular la firma para {0}
Fingerprinter.DisplayName=Almacenar firma de ficheros para poder hacer seguimiento Fingerprinter.DisplayName=Almacenar firma de ficheros para poder hacer seguimiento
Fingerprinter.Failed=Imposible de grabar firmas Fingerprinter.Failed=Imposible de grabar firmas
Fingerprinter.FailedFor=fallo al grabar la firma de {0} Fingerprinter.FailedFor=fallo al grabar la firma de {0}
Fingerprinter.NoWorkspace=No se pueden guardar firmas porque no hay o no se ha creado el espacio de trabajo
Fingerprinter.Recording=Almacenando firmas Fingerprinter.Recording=Almacenando firmas
InstallFromApache=Instalar desde Apache InstallFromApache=Instalar desde Apache
......
...@@ -55,7 +55,6 @@ Fingerprinter.DigestFailed=Impossible de calculer le r\u00e9sum\u00e9 pour {0} ...@@ -55,7 +55,6 @@ Fingerprinter.DigestFailed=Impossible de calculer le r\u00e9sum\u00e9 pour {0}
Fingerprinter.DisplayName=Enregistrer les empreintes num\u00e9riques des fichiers pour en suivre l''utilisation Fingerprinter.DisplayName=Enregistrer les empreintes num\u00e9riques des fichiers pour en suivre l''utilisation
Fingerprinter.Failed=Impossible d''enregistrer les empreintes num\u00e9riques Fingerprinter.Failed=Impossible d''enregistrer les empreintes num\u00e9riques
Fingerprinter.FailedFor=Impossible d''enregistrer les empreintes num\u00e9riques pour {0} Fingerprinter.FailedFor=Impossible d''enregistrer les empreintes num\u00e9riques pour {0}
Fingerprinter.NoWorkspace=Impossible d''enregistrer les empreintes num\u00e9riques, parce qu''il n''y a pas de r\u00e9pertoire de travail
Fingerprinter.Recording=Enregistrement des empreintes num\u00e9riques Fingerprinter.Recording=Enregistrement des empreintes num\u00e9riques
JavadocArchiver.DisplayName=Publier les Javadocs JavadocArchiver.DisplayName=Publier les Javadocs
......
...@@ -55,7 +55,6 @@ Fingerprinter.DigestFailed={0} \u306e\u30c0\u30a4\u30b8\u30a7\u30b9\u30c8\u3092\ ...@@ -55,7 +55,6 @@ Fingerprinter.DigestFailed={0} \u306e\u30c0\u30a4\u30b8\u30a7\u30b9\u30c8\u3092\
Fingerprinter.DisplayName=\u30d5\u30a1\u30a4\u30eb\u6307\u7d0b\u3092\u8a18\u9332\u3057\u3066\u30d5\u30a1\u30a4\u30eb\u306e\u5229\u7528\u72b6\u6cc1\u3092\u8ffd\u8de1 Fingerprinter.DisplayName=\u30d5\u30a1\u30a4\u30eb\u6307\u7d0b\u3092\u8a18\u9332\u3057\u3066\u30d5\u30a1\u30a4\u30eb\u306e\u5229\u7528\u72b6\u6cc1\u3092\u8ffd\u8de1
Fingerprinter.Failed=\u30d5\u30a1\u30a4\u30eb\u6307\u7d0b\u306e\u8a18\u9332\u306b\u5931\u6557\u3057\u307e\u3057\u305f Fingerprinter.Failed=\u30d5\u30a1\u30a4\u30eb\u6307\u7d0b\u306e\u8a18\u9332\u306b\u5931\u6557\u3057\u307e\u3057\u305f
Fingerprinter.FailedFor={0} \u306e\u30d5\u30a1\u30a4\u30eb\u6307\u7d0b\u306e\u8a18\u9332\u306b\u5931\u6557\u3057\u307e\u3057\u305f Fingerprinter.FailedFor={0} \u306e\u30d5\u30a1\u30a4\u30eb\u6307\u7d0b\u306e\u8a18\u9332\u306b\u5931\u6557\u3057\u307e\u3057\u305f
Fingerprinter.NoWorkspace=\u30ef\u30fc\u30af\u30b9\u30da\u30fc\u30b9\u304c\u306a\u3044\u306e\u3067\u3001\u30d5\u30a1\u30a4\u30eb\u6307\u7d0b\u3092\u8a18\u9332\u3067\u304d\u307e\u305b\u3093
Fingerprinter.Recording=\u30d5\u30a1\u30a4\u30eb\u6307\u7d0b\u306e\u8a18\u9332 Fingerprinter.Recording=\u30d5\u30a1\u30a4\u30eb\u6307\u7d0b\u306e\u8a18\u9332
InstallFromApache=Apache\u304b\u3089\u30a4\u30f3\u30b9\u30c8\u30fc\u30eb InstallFromApache=Apache\u304b\u3089\u30a4\u30f3\u30b9\u30c8\u30fc\u30eb
......
...@@ -52,7 +52,6 @@ Fingerprinter.DigestFailed=Kon de "digest" niet berekenen voor {0} ...@@ -52,7 +52,6 @@ Fingerprinter.DigestFailed=Kon de "digest" niet berekenen voor {0}
Fingerprinter.DisplayName=Leg de vingerafdrukken van bestanden vast om hun gebruik te volgen. Fingerprinter.DisplayName=Leg de vingerafdrukken van bestanden vast om hun gebruik te volgen.
Fingerprinter.Failed=Kon de vingerafdrukken niet vastleggen. Fingerprinter.Failed=Kon de vingerafdrukken niet vastleggen.
Fingerprinter.FailedFor=Kon de vingerafdrukken niet vastleggen voor {0} Fingerprinter.FailedFor=Kon de vingerafdrukken niet vastleggen voor {0}
Fingerprinter.NoWorkspace=Kon de vingerafdrukken niet vastleggen omdat er geen werkplaats is!
Fingerprinter.Recording=Vingerafdrukken vastleggen Fingerprinter.Recording=Vingerafdrukken vastleggen
JavadocArchiver.DisplayName=Publiceer Javadoc JavadocArchiver.DisplayName=Publiceer Javadoc
......
...@@ -51,7 +51,6 @@ Fingerprinter.DigestFailed=Falhou ao computar resumo para {0} ...@@ -51,7 +51,6 @@ Fingerprinter.DigestFailed=Falhou ao computar resumo para {0}
Fingerprinter.DisplayName=Gravar fingerprints de arquivos para trilhar o uso Fingerprinter.DisplayName=Gravar fingerprints de arquivos para trilhar o uso
Fingerprinter.Failed=Falhou ao gravar fingerprints Fingerprinter.Failed=Falhou ao gravar fingerprints
Fingerprinter.FailedFor=Falhou ao gravar fingerprint para {0} Fingerprinter.FailedFor=Falhou ao gravar fingerprint para {0}
Fingerprinter.NoWorkspace=N\u00e3o foi poss\u00edvel gravar fingerprints porque n\u00e3o h\u00e1 nenhum workspace
Fingerprinter.Recording=Gravando fingerprints Fingerprinter.Recording=Gravando fingerprints
JavadocArchiver.DisplayName=Publicar Javadoc JavadocArchiver.DisplayName=Publicar Javadoc
......
...@@ -54,7 +54,6 @@ Fingerprinter.DigestFailed=\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u04 ...@@ -54,7 +54,6 @@ Fingerprinter.DigestFailed=\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u04
Fingerprinter.DisplayName=\u0421\u043e\u0445\u0440\u0430\u043d\u044f\u0442\u044c \u043e\u0442\u043f\u0435\u0447\u0430\u0442\u043a\u0438 \u0444\u0430\u0439\u043b\u043e\u0432 (fingerprints) \u0434\u043b\u044f \u043e\u0442\u0441\u043b\u0435\u0436\u0438\u0432\u0430\u043d\u0438\u044f \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u044f Fingerprinter.DisplayName=\u0421\u043e\u0445\u0440\u0430\u043d\u044f\u0442\u044c \u043e\u0442\u043f\u0435\u0447\u0430\u0442\u043a\u0438 \u0444\u0430\u0439\u043b\u043e\u0432 (fingerprints) \u0434\u043b\u044f \u043e\u0442\u0441\u043b\u0435\u0436\u0438\u0432\u0430\u043d\u0438\u044f \u0438\u0441\u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u043d\u0438\u044f
Fingerprinter.Failed=\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u0441\u043e\u0445\u0440\u0430\u043d\u0438\u0442\u044c \u043e\u0442\u043f\u0435\u0447\u0430\u0442\u043a\u0438 Fingerprinter.Failed=\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u0441\u043e\u0445\u0440\u0430\u043d\u0438\u0442\u044c \u043e\u0442\u043f\u0435\u0447\u0430\u0442\u043a\u0438
Fingerprinter.FailedFor=\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u0441\u043e\u0445\u0440\u0430\u043d\u0438\u0442\u044c \u043e\u0442\u043f\u0435\u0447\u0430\u0442\u043a\u0438 {0} Fingerprinter.FailedFor=\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u0441\u043e\u0445\u0440\u0430\u043d\u0438\u0442\u044c \u043e\u0442\u043f\u0435\u0447\u0430\u0442\u043a\u0438 {0}
Fingerprinter.NoWorkspace=\u041d\u0435\u0432\u043e\u0437\u043c\u043e\u0436\u043d\u043e \u0441\u043e\u0445\u0440\u0430\u043d\u0438\u0442\u044c \u043e\u0442\u043f\u0435\u0447\u0430\u0442\u043a\u0438, \u0442.\u043a. \u043d\u0435 \u0441\u043e\u0437\u0434\u0430\u043d\u0430 \u0441\u0431\u043e\u0440\u043e\u0447\u043d\u0430\u044f \u0434\u0438\u0440\u0435\u043a\u0442\u043e\u0440\u0438\u044f
Fingerprinter.Recording=\u0421\u043e\u0445\u0440\u0430\u043d\u044f\u044e \u043e\u0442\u043f\u0435\u0447\u0430\u0442\u043a\u0438 (fingerprints) Fingerprinter.Recording=\u0421\u043e\u0445\u0440\u0430\u043d\u044f\u044e \u043e\u0442\u043f\u0435\u0447\u0430\u0442\u043a\u0438 (fingerprints)
JavadocArchiver.DisplayName=\u041e\u043f\u0443\u0431\u043b\u0438\u043a\u043e\u0432\u0430\u0442\u044c Javadoc JavadocArchiver.DisplayName=\u041e\u043f\u0443\u0431\u043b\u0438\u043a\u043e\u0432\u0430\u0442\u044c Javadoc
......
...@@ -54,7 +54,6 @@ Fingerprinter.DigestFailed={0} i\u00e7in digest olu\u015fturma ba\u015far\u0131s ...@@ -54,7 +54,6 @@ Fingerprinter.DigestFailed={0} i\u00e7in digest olu\u015fturma ba\u015far\u0131s
Fingerprinter.DisplayName=Takip ama\u00e7l\u0131 dosyalar\u0131n parmakizlerini kaydet Fingerprinter.DisplayName=Takip ama\u00e7l\u0131 dosyalar\u0131n parmakizlerini kaydet
Fingerprinter.Failed=Parmakizi kayd\u0131 ba\u015far\u0131s\u0131z oldu Fingerprinter.Failed=Parmakizi kayd\u0131 ba\u015far\u0131s\u0131z oldu
Fingerprinter.FailedFor={0} i\u00e7in parmakizi kayd\u0131 ba\u015far\u0131s\u0131z oldu Fingerprinter.FailedFor={0} i\u00e7in parmakizi kayd\u0131 ba\u015far\u0131s\u0131z oldu
Fingerprinter.NoWorkspace=Herhangi bir \u00e7al\u0131\u015fma alan\u0131 olmad\u0131\u011f\u0131 i\u00e7in parmakzi kaydedilemiyor.
Fingerprinter.Recording=Parmakizi kaydediliyor Fingerprinter.Recording=Parmakizi kaydediliyor
JavadocArchiver.DisplayName=Javadoc yay\u0131nla JavadocArchiver.DisplayName=Javadoc yay\u0131nla
......
...@@ -58,7 +58,6 @@ Fingerprinter.DigestFailed=\u7121\u6cd5\u8a08\u7b97 {0} \u7684 Digest \u503c ...@@ -58,7 +58,6 @@ Fingerprinter.DigestFailed=\u7121\u6cd5\u8a08\u7b97 {0} \u7684 Digest \u503c
Fingerprinter.DisplayName=\u8a18\u9304\u6a94\u6848\u6307\u7d0b\uff0c\u4ee5\u4fbf\u8ffd\u8e64\u4f7f\u7528\u72c0\u6cc1 Fingerprinter.DisplayName=\u8a18\u9304\u6a94\u6848\u6307\u7d0b\uff0c\u4ee5\u4fbf\u8ffd\u8e64\u4f7f\u7528\u72c0\u6cc1
Fingerprinter.Failed=\u7121\u6cd5\u8a18\u9304\u6307\u7d0b Fingerprinter.Failed=\u7121\u6cd5\u8a18\u9304\u6307\u7d0b
Fingerprinter.FailedFor=\u7121\u6cd5\u8a18\u9304 {0} \u7684\u6307\u7d0b Fingerprinter.FailedFor=\u7121\u6cd5\u8a18\u9304 {0} \u7684\u6307\u7d0b
Fingerprinter.NoWorkspace=\u6c92\u6709\u5de5\u4f5c\u5340\uff0c\u7121\u6cd5\u8a18\u9304\u6307\u7d0b
Fingerprinter.Recording=\u8a18\u9304\u6307\u7d0b Fingerprinter.Recording=\u8a18\u9304\u6307\u7d0b
InstallFromApache=\u5f9e Apache \u5b89\u88dd InstallFromApache=\u5f9e Apache \u5b89\u88dd
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册