package org.jenkinsci.plugins.p4; import hudson.EnvVars; import hudson.Extension; import hudson.FilePath; import hudson.Launcher; import hudson.model.TaskListener; import hudson.model.AbstractBuild; import hudson.model.AbstractProject; import hudson.model.Job; import hudson.model.Node; import hudson.model.Run; import hudson.scm.ChangeLogParser; import hudson.scm.PollingResult; import hudson.scm.SCMDescriptor; import hudson.scm.SCMRevisionState; import hudson.scm.SCM; import hudson.security.ACL; import hudson.util.FormValidation; import hudson.util.ListBoxModel; import java.io.File; import java.io.IOException; import java.io.PrintStream; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.logging.Logger; import jenkins.model.Jenkins; import net.sf.json.JSONObject; import org.acegisecurity.Authentication; import org.jenkinsci.plugins.p4.browsers.P4Browser; import org.jenkinsci.plugins.p4.changes.P4ChangeParser; import org.jenkinsci.plugins.p4.changes.P4ChangeSet; import org.jenkinsci.plugins.p4.client.ClientHelper; import org.jenkinsci.plugins.p4.client.ConnectionHelper; import org.jenkinsci.plugins.p4.credentials.P4StandardCredentials; import org.jenkinsci.plugins.p4.filters.Filter; import org.jenkinsci.plugins.p4.filters.FilterPathImpl; import org.jenkinsci.plugins.p4.filters.FilterUserImpl; import org.jenkinsci.plugins.p4.populate.ForceCleanImpl; import org.jenkinsci.plugins.p4.populate.Populate; import org.jenkinsci.plugins.p4.tagging.TagAction; import org.jenkinsci.plugins.p4.workspace.Workspace; import org.kohsuke.stapler.DataBoundConstructor; import org.kohsuke.stapler.DataBoundSetter; import org.kohsuke.stapler.QueryParameter; import org.kohsuke.stapler.StaplerRequest; import com.cloudbees.plugins.credentials.CredentialsProvider; import com.cloudbees.plugins.credentials.domains.DomainRequirement; import com.perforce.p4java.core.file.IFileSpec; import com.perforce.p4java.exception.AccessException; import com.perforce.p4java.exception.RequestException; import com.perforce.p4java.impl.generic.core.Changelist; public class PerforceScm extends SCM { private static Logger logger = Logger .getLogger(PerforceScm.class.getName()); private String credential; private Workspace workspace; private List<Filter> filter; private Populate populate; private P4Browser browser; @DataBoundSetter public void setCredential(String credential) { this.credential = credential; } public String getCredential() { return credential; } @DataBoundSetter public void setWorkspace(Workspace workspace) { this.workspace = workspace; } public Workspace getWorkspace() { return workspace; } @DataBoundSetter public void setFilter(List<Filter> filter) { this.filter = filter; } public List<Filter> getFilter() { return filter; } @DataBoundSetter public void setPopulate(Populate populate) { this.populate = populate; } public Populate getPopulate() { return populate; } @DataBoundSetter public void setBrowser(P4Browser browser) { this.browser = browser; } @Override public P4Browser getBrowser() { return browser; } /** * Create a constructor that takes non-transient fields, and add the * annotation @DataBoundConstructor to it. Using the annotation helps the * Stapler class to find which constructor that should be used when * automatically copying values from a web form to a class. */ @Deprecated @DataBoundConstructor public PerforceScm(String credential, Workspace workspace, List<Filter> filter, Populate populate, P4Browser browser) { this.credential = credential; this.workspace = workspace; this.filter = filter; this.populate = populate; this.browser = browser; } public PerforceScm() { super(); } /** * Calculate the state of the workspace of the given build. The returned * object is then fed into compareRemoteRevisionWith as the baseline * SCMRevisionState to determine if the build is necessary, and is added to * the build as an Action for later retrieval. */ @Override public SCMRevisionState calcRevisionsFromBuild(AbstractBuild<?, ?> build, Launcher launcher, TaskListener listener) throws IOException, InterruptedException { // Method not required by Perforce return null; } /** * This method does the actual polling and returns a PollingResult. The * change attribute of the PollingResult the significance of the changes * detected by this poll. */ @Override public PollingResult compareRemoteRevisionWith(Job<?, ?> job, Launcher launcher, FilePath buildWorkspace, TaskListener listener, SCMRevisionState baseline) throws IOException, InterruptedException { PollingResult state = PollingResult.NO_CHANGES; PrintStream log = listener.getLogger(); Run<?, ?> run = job.getLastBuild(); PerforceScm scm = (PerforceScm) ((AbstractProject<?, ?>) run .getParent()).getScm(); String scmCredential = scm.getCredential(); Populate scmPopulate = scm.getPopulate(); List<Filter> scmFilter = scm.getFilter(); // CAUTION: scmWorkspace environment will have limited access to the // environment variables (e.g. NODE_NAME is missing). Instead get the // expanded client workspace name from the last build. String client = "unset"; try { EnvVars envVars = run.getEnvironment(null); client = envVars.get("P4_CLIENT"); log.println("P4: Polling with client: " + client); } catch (Exception e) { logger.warning("P4: Unable to read P4_CLIENT"); return PollingResult.NO_CHANGES; } ClientHelper p4 = new ClientHelper(scmCredential, listener, client); try { // expand the label if required String pin = scmPopulate.getPin(); Workspace scmWorkspace = setEnvironment(run, listener); // find changes... List<Object> changes = new ArrayList<Object>(); if (pin != null && !pin.isEmpty()) { pin = scmWorkspace.expand(pin); log.println("P4: Polling with label/change: " + pin); changes = p4.listClientChanges(pin); } else { changes = p4.listClientChanges(); } // filter changes... List<Object> remainder = new ArrayList<Object>(); for (Object c : changes) { if (c instanceof Integer) { Changelist changelist = p4.getChange((Integer) c); // add unfiltered changes to remainder list if (!filterChange(changelist, scmFilter)) { remainder.add(changelist); log.println("... found change: " + changelist.getId()); } } } // if there is a remainder and the workspace is out of date if (!remainder.isEmpty() && p4.updateFiles()) { state = PollingResult.BUILD_NOW; } } catch (Exception e) { logger.severe("P4: Polling Error: " + e); e.printStackTrace(); return null; } finally { // close connection p4.disconnect(); } return state; } /** * Returns true if change should be filtered * * @param changelist * @return * @throws AccessException * @throws RequestException * @throws Exception */ private boolean filterChange(Changelist changelist, List<Filter> scmFilter) throws Exception { // exit early if no filters if (scmFilter == null) { return false; } String user = changelist.getUsername(); List<IFileSpec> files = changelist.getFiles(true); for (Filter f : scmFilter) { // Scan through User filters if (f instanceof FilterUserImpl) { // return is user matches filter String u = ((FilterUserImpl) f).getUser(); if (u.equalsIgnoreCase(user)) { return true; } } // Scan through Path filters if (f instanceof FilterPathImpl) { // add unmatched files to remainder list List<IFileSpec> remainder = new ArrayList<IFileSpec>(); String path = ((FilterPathImpl) f).getPath(); for (IFileSpec s : files) { String p = s.getDepotPathString(); if (!p.startsWith(path)) { remainder.add(s); } } // update files with remainder files = remainder; // add if all files are removed then remove change if (files.isEmpty()) { return true; } } } return false; } /** * The checkout method is expected to check out modified files into the * project workspace. In Perforce terms a 'p4 sync' on the project's * workspace. Authorisation */ @Override public void checkout(Run<?, ?> run, Launcher launcher, FilePath buildWorkspace, TaskListener listener, File changelogFile, SCMRevisionState baseline) throws IOException, InterruptedException { boolean success = true; AbstractBuild<?, ?> build = (AbstractBuild<?, ?>) run; PerforceScm scm = (PerforceScm) build.getProject().getScm(); Workspace scmWorkspace = setEnvironment(run, listener); String scmCredential = scm.getCredential(); Populate scmPopulate = scm.getPopulate(); // Create task CheckoutTask task; task = new CheckoutTask(scmCredential, scmWorkspace, listener); task.setPopulateOpts(scmPopulate); task.setBuildOpts(scmWorkspace); // Add tagging action to build, enabling label support. TagAction tag = new TagAction(run); tag.setClient(scmWorkspace.getFullName()); tag.setCredential(scmCredential); tag.setBuildChange(task.getBuildChange()); run.addAction(tag); // Invoke build. success &= buildWorkspace.act(task); // Only write change log if build succeed. if (success) { // Calculate changes prior to build (based on last build) List<Object> changes = calculateChanges(run, task); P4ChangeSet.store(changelogFile, changes); } } private List<Object> calculateChanges(Run<?, ?> build, CheckoutTask task) { List<Object> changes = new ArrayList<Object>(); Run<?, ?> lastBuild = build.getPreviousBuild(); if (lastBuild != null) { TagAction lastTag = lastBuild.getAction(TagAction.class); if (lastTag != null) { Object lastChange = lastTag.getBuildChange(); if (lastChange != null) { changes = task.getChanges(lastChange); } } } else { // No previous build, so add current changes.add(task.getBuildChange()); } return changes; } private Workspace setEnvironment(Run<?, ?> run, TaskListener listener) throws IOException, InterruptedException { AbstractBuild<?, ?> build = (AbstractBuild<?, ?>) run; Map<String, String> map = build.getBuildVariables(); EnvVars envVars = build.getEnvironment(listener); PerforceScm scm = (PerforceScm) build.getProject().getScm(); Workspace scmWorkspace = (Workspace) scm.getWorkspace().clone(); Populate scmPopulate = scm.getPopulate(); // IMPORTANT: Set workspace format map to build workspace name. AbstractProject<?, ?> project = build.getProject(); String nodename = build.getBuiltOn().getNodeName(); nodename = (nodename != "") ? nodename : "master"; String hostname = build.getBuiltOn().toComputer().getHostName(); scmWorkspace.clear(); scmWorkspace.set("node", nodename); scmWorkspace.set("hostname", hostname); scmWorkspace.set("hash", String.valueOf(nodename.hashCode())); scmWorkspace.set("project", project.getName()); // IMPORTANT: Set workspace root and hostname scmWorkspace.setHostName(null); // TODO get real hostname! scmWorkspace.setRootPath(build.getModuleRoot().getRemote()); // load environments scmWorkspace.load(map); scmWorkspace.load(envVars); // Set label in map, if pinning is used. String pin = scmPopulate.getPin(); if (pin != null && !pin.isEmpty()) { pin = scmWorkspace.expand(pin); map.put("label", pin); } return scmWorkspace; } @Override public void buildEnvVars(AbstractBuild<?, ?> build, Map<String, String> env) { super.buildEnvVars(build, env); TagAction tagAction = build.getAction(TagAction.class); if (tagAction != null) { // Set P4_CHANGELIST value if (tagAction.getBuildChange() != null) { String change = String.valueOf(tagAction.getBuildChange()); env.put("P4_CHANGELIST", change); } // Set P4_CLIENT workspace value if (tagAction.getClient() != null) { String client = tagAction.getClient(); env.put("P4_CLIENT", client); } } } /** * The checkout method should, besides checking out the modified files, * write a changelog.xml file that contains the changes for a certain build. * The changelog.xml file is specific for each SCM implementation, and the * createChangeLogParser returns a parser that can parse the file and return * a ChangeLogSet. */ @Override public ChangeLogParser createChangeLogParser() { return new P4ChangeParser(); } /** * Called before a workspace is deleted on the given node, to provide SCM an * opportunity to perform clean up. */ @Override public boolean processWorkspaceBeforeDeletion(Job<?, ?> job, FilePath buildWorkspace, Node node) { AbstractProject<?, ?> project = (AbstractProject<?, ?>) job; PerforceScm scm = (PerforceScm) project.getScm(); String scmCredential = scm.getCredential(); Run<?, ?> run = job.getLastBuild(); String client = "unset"; try { EnvVars envVars = run.getEnvironment(null); client = envVars.get("P4_CLIENT"); } catch (Exception e) { logger.warning("P4: Unable to read P4_CLIENT"); return true; } ClientHelper p4 = new ClientHelper(scmCredential, null, client); try { ForceCleanImpl forceClean = new ForceCleanImpl(false, null); logger.info("P4: unsyncing client: " + client); p4.syncFiles(0, forceClean); } catch (Exception e) { logger.warning("P4: Not able to unsync client: " + client); return true; } finally { p4.disconnect(); } return true; } /** * Returns the ScmDescriptor<?> for the SCM object. The ScmDescriptor is * used to create new instances of the SCM. */ @Override public SCMDescriptor<PerforceScm> getDescriptor() { return (DescriptorImpl) super.getDescriptor(); } public static final DescriptorImpl DESCRIPTOR = new DescriptorImpl(); /** * The relationship of Descriptor and SCM (the describable) is akin to class * and object. What this means is that the descriptor is used to create * instances of the describable. Usually the Descriptor is an internal class * in the SCM class named DescriptorImpl. The Descriptor should also contain * the global configuration options as fields, just like the SCM class * contains the configurations options for a job. * * @author pallen * */ @Extension public static class DescriptorImpl extends SCMDescriptor<PerforceScm> { /** * public no-argument constructor */ public DescriptorImpl() { super(PerforceScm.class, P4Browser.class); load(); } @Override public SCM newInstance(StaplerRequest req, JSONObject formData) throws FormException { PerforceScm scm = (PerforceScm) super.newInstance(req, formData); return scm; } /** * Returns the name of the SCM, this is the name that will show up next * to CVS and Subversion when configuring a job. */ @Override public String getDisplayName() { return "Perforce Software"; } /** * The configure method is invoked when the global configuration page is * submitted. In the method the data in the web form should be copied to * the Descriptor's fields. To persist the fields to the global * configuration XML file, the save() method must be called. Data is * defined in the global.jelly page. * */ @Override public boolean configure(StaplerRequest req, JSONObject json) throws FormException { save(); return true; } /** * Credentials list, a Jelly config method for a build job. * * @return A list of Perforce credential items to populate the jelly * Select list. */ public ListBoxModel doFillCredentialItems() { ListBoxModel list = new ListBoxModel(); Class<P4StandardCredentials> type = P4StandardCredentials.class; Jenkins scope = Jenkins.getInstance(); Authentication acl = ACL.SYSTEM; DomainRequirement domain = new DomainRequirement(); List<P4StandardCredentials> credentials; credentials = CredentialsProvider.lookupCredentials(type, scope, acl, domain); if (credentials.isEmpty()) { list.add("Select credential...", null); } for (P4StandardCredentials c : credentials) { StringBuffer sb = new StringBuffer(); sb.append(c.getDescription()); sb.append(" ("); sb.append(c.getUsername()); sb.append(":"); sb.append(c.getP4port()); sb.append(")"); list.add(sb.toString(), c.getId()); } return list; } public FormValidation doCheckCredential(@QueryParameter String value) { if (value == null) { return FormValidation.ok(); } try { ConnectionHelper p4 = new ConnectionHelper(value, null); if (!p4.login()) { return FormValidation .error("Authentication Error: Unable to login."); } if (!p4.checkVersion(20121)) { return FormValidation .error("Server version is too old (min 2012.1)"); } return FormValidation.ok(); } catch (Exception e) { return FormValidation.error(e.getMessage()); } } } /** * This methods determines if the SCM plugin can be used for polling */ @Override public boolean supportsPolling() { return true; } /** * This method should return true if the SCM requires a workspace for * polling. Perforce however can report submitted, pending and shelved * changes without needing a workspace */ @Override public boolean requiresWorkspaceForPolling() { return true; } }
# | Change | User | Description | Committed | |
---|---|---|---|---|---|
#17 | 9965 | Paul Allen |
Removed old 'node', 'hostname', 'hash' and 'project'. - These are either not used or covered in the Environment. - Updated the help. |
||
#16 | 9964 | Paul Allen | More updates to code for new 1.569 plugin version. | ||
#15 | 9854 | Paul Allen | Upgrade plugin (1.569) to support Job/Run | ||
#14 | 9842 | Paul Allen | Null protection for pin at label. | ||
#13 | 9841 | Paul Allen | Expand labels for pinned builds. | ||
#12 | 9840 | Paul Allen |
Minor improvments. - Protection against undefined workspaces - Logging improvments for Perforce - Performance for Perforce connections |
||
#11 | 9834 | Paul Allen |
Support pinned builds with SCM Polling. Supports the Populate option 'Pin build at Perforce Label' (or change), and only triggers a build if the label is changed. |
||
#10 | 9815 | Paul Allen |
Wipe Out Workspace now unsyncs revisions. Previously I tried to delete the client workspace, but now I unsync the files. Fixed bug with matrix build not finding the client name (use a P4_CLIENT env var from last build). |
||
#9 | 9798 | Paul Allen |
Clone Workspace as Build Matrix was using the same object. https://issues.jenkins-ci.org/browse/JENKINS-24027 |
||
#8 | 9796 | Paul Allen |
Fix change list reporting for first builds. Labels and Shelved reviews were not reported correctly on first build. Failed system tests. |
||
#7 | 9793 | Paul Allen |
Change list reporting. Only report the last change list for the first build; prevent a flood of changes. For later builds; reports submitted changes since the last build. |
||
#6 | 9761 | Paul Allen |
Set environment P4_CHANGELIST Using the value from TagAction set after a build. |
||
#5 | 9754 | Paul Allen | Clear Workspace environment betweeen builds. | ||
#4 | 9750 | Paul Allen |
Add support for Environment variables. Includes cleanup of Perforce exception reporting for Connections and Workspaces. |
||
#3 | 9745 | Paul Allen |
Bugfix to avoid null pointer if there are no changes after a successful build. [TPI-127] |
||
#2 | 9736 | Paul Allen |
Custom build args for Workspace names. Takes `${parameter}` and replaces it with the value if defined or uses the tag (`parameter` in this case). Includes substitution for a space ` ` with `_` as per P4 behaviour. |
||
#1 | 9672 | Paul Allen | Refactor name from 'p4_client' to 'p4'. | ||
//guest/paul_allen/dev/p4-jenkins/p4-client/src/main/java/org/jenkinsci/plugins/p4_client/PerforceScm.java | |||||
#32 | 9495 | Paul Allen |
Fix Polling issue; using the wrong client. Removed static classes and internal referenced to PerforceScm attributes, to avoid threading issues. |
||
#31 | 9472 | Paul Allen |
Added support to pin build at a label in the populate configuration. - includes help and updates to tests. |
||
#30 | 9430 | Paul Allen |
Polling SCM: skip filter check if no filters - added more logging for Polling errors. |
||
#29 | 9429 | Paul Allen |
Fix SCM Polling bug. Client workspace was not set (unless by an earlier build). - Move listChanges method to ClientHelper. |
||
#28 | 9115 | Paul Allen |
Initial implementation of workspace Cleanup and Sync options. - Includes 3 modes: Automatic Clean/Sync, Force Clean/Sync, Sync Only Automatic Clean/Sync Uses reconcile to clean up workspace and sync changes. Force Clean/Sync Force sync of all files (does not remove files yet...) Sync Only Normal sync with no cleanup TODO: - remove of files in Force Clean/Sync mode - Inline help - Update docs - Add unit/functional tests |
||
#27 | 9091 | Paul Allen |
Added Changelist build filtering for SCM polling: - Configuration uses 'repeatableHeteroProperty' - Filter on Perforce username - Filter on Perforce Depot path (no wildcard support) |
||
#26 | 9069 | Paul Allen |
Adding initial support for tagging Jenkins builds as Perforce Automatic Labels. Only implements TagAction (manual labels); TagNotifier and test cases TODO. |
||
#25 | 9055 | Paul Allen |
Label support. Build at a label using the pram 'label'. This includes adding the label to the ChangeEntry, building the change reports and Browser links to Swarm. (TPI-102) |
||
#24 | 9038 | Paul Allen |
Improved error messages for Form Validate field checks. Uses Perforce Server messages (TPI-80) |
||
#23 | 8969 | Paul Allen |
Adds all contributing change-lists for the build to the change log (using p4 cstat). - Includes exception logging for server connection to the Jenkins console. |
||
#22 | 8940 | Paul Allen |
Major refactor for the ConnectionHelper class to simplify serialisation. Fixed remote Jenkins JNLP slave connection issue. ClientHelper now extends ConnectionHelper and takes on all methods that require a client workspace. P4StandardCredentials is sent to the remote node instead of Credentials ID due to an issue accessing the Credentials store over a remote connection. For simplicity Client ID (workspace name) is serialised instead of the Workspace object. |
||
#21 | 8929 | Paul Allen |
Clean up doCheck Warnings on configuration page (TPI-80). - Added check exceptions to log as warnings. |
||
#20 | 8915 | Paul Allen |
Support for ChangeLog and RepoBrowser. - Added RepoBrowser for Swarm (porting the others should be easy) - ChangeLog XML file now only stores the changelist number all other information is fetched from Perforce |
||
#19 | 8771 | Paul Allen |
Perforce Server 12.1 min check for: Build configuration and password/ticket credentials. Includes: - Added logging for Perforce connections (fine) and set connection pool to 2. - Add 'none' to empty charset list (connection bug?) - Supress P4Java errors for syncing ubinary, xtext, unicode |
||
#18 | 8770 | Paul Allen |
Implemented SCM hook for when Jenkins deletes a workspace. The PerforceScm plugin deletes the workspace, but leaves Jenkins to clean up the local files and directories. (includes dummy 'p4 cstat' code for change reporting) |
||
#17 | 8762 | Paul Allen |
Console Ouptut logging for SCM build steps. - Removed SLF4J and used old style logger (matching Jenkins) - Set Client Host filed to null, Jenkins sometimes gives an IP address. - Test p4java setps in SCM tasks for success(true/false) |
||
#16 | 8744 | Paul Allen |
Support basic SCM Polling. When Jenkins has "Poll SCM" checked the Perforce SCM provider just runs a 'p4 sync -n' on the Workspace. Simple and Fast. |
||
#15 | 8738 | Paul Allen |
Workspace Name Formatter. For Template and Stream workspaces it allows the substitution of the following tags: ${node} The name given to the slave Jenkins node. ${hostname} The hostname for the slave Jenkins node. ${project} The name of the Jenkins build Job. ${hash} Unique hash code of the Jenkins node. |
||
#14 | 8737 | Paul Allen |
Added basic Help for SCM Configuration page. Tidy up descriptions and fix (null:null) in Credential summary. |
||
#13 | 8694 | Paul Allen | Added support for unshelve and revert -w behaviour for builds. | ||
#12 | 8688 | Paul Allen | (Work in progress) Able to call build from review Action and pass URL params. | ||
#11 | 8664 | Paul Allen | Simplified connection to Perforce to get around the SCM initilisation (or lack of). | ||
#10 | 8663 | Paul Allen | Rollout of charset for all Workspace modes. | ||
#9 | 8661 | Paul Allen | Workspace auto fill | ||
#8 | 8641 | Paul Allen | Added workspace helper (setClient) and template/stream types. | ||
#7 | 8640 | Paul Allen |
Added Workspace and Singleton descriptor. Removed old connection code. |
||
#6 | 8639 | Paul Allen |
Added hint URL to Credentials page when no Credentials are defined. Minor refactor and UX changes. |
||
#5 | 8629 | Paul Allen |
Added p4java with connection/authorisation helper classes. Included SSL support and detection of Unicode servers. |
||
#4 | 8613 | Paul Allen | Tidy up unused Credentials data and for debug added getId() to SelectList title. | ||
#3 | 8612 | Paul Allen | Job can now select and save/load Credential choice (some cleanup TODO) | ||
#2 | 8611 | Paul Allen | Basic implementation of Credentials Store | ||
#1 | 8598 | Paul Allen | Experimentation with data binding for Jelly files and ExtensionPoint/Descriptor |