/* * The MIT License * * Copyright (c) 2004-2010, Sun Microsystems, Inc., Alan Harder * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ package hudson.plugins.copyartifact; import com.thoughtworks.xstream.converters.UnmarshallingContext; import hudson.DescriptorExtensionList; import hudson.EnvVars; import hudson.Extension; import hudson.FilePath; import hudson.Launcher; import hudson.Util; import hudson.diagnosis.OldDataMonitor; import hudson.matrix.MatrixProject; import hudson.model.AbstractBuild; import hudson.model.AbstractProject; import hudson.model.BuildListener; import hudson.model.Descriptor; import hudson.model.Hudson; import hudson.model.Job; import hudson.model.Item; import hudson.model.Project; import hudson.model.Run; import hudson.model.listeners.ItemListener; import hudson.security.AccessControlled; import hudson.tasks.BuildStepDescriptor; import hudson.tasks.Builder; import hudson.util.DescribableList; import hudson.util.FormValidation; import hudson.util.XStream2; import java.io.File; import java.io.IOException; import java.io.PrintStream; import java.net.URL; import java.util.Collections; import java.util.List; import java.util.logging.Level; import java.util.logging.Logger; import org.kohsuke.stapler.AncestorInPath; import org.kohsuke.stapler.DataBoundConstructor; import org.kohsuke.stapler.QueryParameter; /** * Build step to copy artifacts from another project. * @author Alan Harder */ public class CopyArtifact extends Builder { private String projectName; private final String filter, target; private /*almost final*/ BuildSelector selector; @Deprecated private transient Boolean stable; private final Boolean flatten, optional; @DataBoundConstructor public CopyArtifact(String projectName, BuildSelector selector, String filter, String target, boolean flatten, boolean optional) { this.projectName = projectName; this.selector = selector; this.filter = Util.fixNull(filter).trim(); this.target = Util.fixNull(target).trim(); this.flatten = flatten ? Boolean.TRUE : null; this.optional = optional ? Boolean.TRUE : null; } // Upgrade data from old format public static class ConverterImpl extends XStream2.PassthruConverter<CopyArtifact> { public ConverterImpl(XStream2 xstream) { super(xstream); } @Override protected void callback(CopyArtifact obj, UnmarshallingContext context) { if (obj.selector == null) { obj.selector = new StatusBuildSelector(obj.stable != null && obj.stable.booleanValue()); OldDataMonitor.report(context, "1.355"); // Hudson version# when CopyArtifact 1.2 released } } } public String getProjectName() { return projectName; } public BuildSelector getBuildSelector() { return selector; } public String getFilter() { return filter; } public String getTarget() { return target; } public boolean isFlatten() { return flatten != null && flatten.booleanValue(); } public boolean isOptional() { return optional != null && optional.booleanValue(); } @Override public boolean perform(AbstractBuild<?,?> build, Launcher launcher, BuildListener listener) throws InterruptedException { PrintStream console = listener.getLogger(); String expandedProject = projectName, expandedFilter = filter; try { EnvVars env = build.getEnvironment(listener); env.overrideAll(build.getBuildVariables()); // Add in matrix axes.. expandedProject = env.expand(projectName); Job job = Hudson.getInstance().getItemByFullName(expandedProject, Job.class); if (job == null) { console.println(Messages.CopyArtifact_MissingProject(expandedProject)); return false; } Run run = selector.getBuild(job, env); if (run == null) { console.println(Messages.CopyArtifact_MissingBuild(expandedProject)); return isOptional(); // Fail build unless copy is optional } File srcDir = run.getArtifactsDir(); FilePath targetDir = build.getWorkspace(); if (targetDir == null) { console.println(Messages.CopyArtifact_MissingWorkspace()); // (see HUDSON-3330) return isOptional(); // Fail build unless copy is optional } // Workaround for HUDSON-5977.. this block can be removed whenever // copyartifact plugin raises its minimum Hudson version to whatever // release fixes #5977. // Make a call to copy a small file, to get all class-loading to happen. // When we copy the real stuff there won't be any classloader requests // coming the other direction, which due to full-buffer-deadlock problem // can cause slave to hang. URL base = Hudson.getInstance().getPluginManager() .getPlugin("copyartifact").baseResourceURL; if (base!=null && "file".equals(base.getProtocol())) { FilePath tmp = targetDir.createTempDir("copyartifact", ".dir"); new FilePath(new File(base.getPath())).copyRecursiveTo("HUDSON-5977/**", tmp); tmp.deleteRecursive(); } // End workaround if (target.length() > 0) targetDir = new FilePath(targetDir, env.expand(target)); expandedFilter = env.expand(filter); if (expandedFilter.trim().length() == 0) expandedFilter = "**"; int cnt; if (!isFlatten()) cnt = new FilePath(srcDir).copyRecursiveTo(expandedFilter, targetDir); else { targetDir.mkdirs(); // Create target if needed FilePath[] list = new FilePath(srcDir).list(expandedFilter); for (FilePath file : list) file.copyTo(new FilePath(targetDir, file.getName())); cnt = list.length; } listener.getLogger().println( Messages.CopyArtifact_Copied(cnt, run.getFullDisplayName())); // Fail build if 0 files copied unless copy is optional return cnt > 0 || isOptional(); } catch (IOException ex) { Util.displayIOException(ex, listener); ex.printStackTrace(listener.error( Messages.CopyArtifact_FailedToCopy(expandedProject, expandedFilter))); return false; } } @Extension public static final class DescriptorImpl extends BuildStepDescriptor<Builder> { public FormValidation doCheckProjectName( @AncestorInPath AccessControlled anc, @QueryParameter String value) { // Require CONFIGURE permission on this project if (!anc.hasPermission(Item.CONFIGURE)) return FormValidation.ok(); if (Hudson.getInstance().getItemByFullName(value, Job.class) != null) return FormValidation.ok(); if (value.indexOf('$') >= 0) return FormValidation.warning(Messages.CopyArtifact_ParameterizedName()); return FormValidation.error( hudson.tasks.Messages.BuildTrigger_NoSuchProject( value, AbstractProject.findNearest(value).getName())); } public boolean isApplicable(Class<? extends AbstractProject> clazz) { return true; } public String getDisplayName() { return Messages.CopyArtifact_DisplayName(); } public DescriptorExtensionList<BuildSelector,Descriptor<BuildSelector>> getBuildSelectors() { return Hudson.getInstance().getDescriptorList(BuildSelector.class); } } // Listen for project renames and update property here if needed. @Extension public static final class ListenerImpl extends ItemListener { @Override public void onRenamed(Item item, String oldName, String newName) { for (AbstractProject<?,?> project : Hudson.getInstance().getAllItems(AbstractProject.class)) { for (CopyArtifact ca : getCopiers(project)) try { if (ca.getProjectName().equals(oldName)) ca.projectName = newName; else if (ca.getProjectName().startsWith(oldName + '/')) // Support rename for "MatrixJobName/AxisName=value" type of name ca.projectName = newName + ca.projectName.substring(oldName.length()); else continue; project.save(); } catch (IOException ex) { Logger.getLogger(ListenerImpl.class.getName()).log(Level.WARNING, "Failed to resave project " + project.getName() + " for project rename in CopyArtifact build step (" + oldName + " =>" + newName + ")", ex); } } } private static List<CopyArtifact> getCopiers(AbstractProject project) { DescribableList<Builder,Descriptor<Builder>> list = project instanceof Project ? ((Project<?,?>)project).getBuildersList() : (project instanceof MatrixProject ? ((MatrixProject)project).getBuildersList() : null); if (list == null) return Collections.emptyList(); return (List<CopyArtifact>)list.getAll(CopyArtifact.class); } } }