The easiest way to see how they're identical is to look at what each of them looks like empty.
package.xml
<Package xmlns="http://soap.sforce.com/2006/04/metadata"> <version>29.0</version> </Package>
destructiveChanges.xml
<Package xmlns="http://soap.sforce.com/2006/04/metadata"> </Package>
The only difference between them is destructiveChanges doesn't have a <version> tag.
Let's look again after we add a class to each. In package.xml we're synchronizing a class and in destructiveChanges.xml its a class we want to remove from our org.
package.xml
<Package xmlns="http://soap.sforce.com/2006/04/metadata"> <version>29.0</version> <types> <members>TomTest</members> <name>ApexClass</name> <types> </Package>
destructiveChanges.xml
<Package xmlns="http://soap.sforce.com/2006/04/metadata"> <types> <members>TomTest</members> <name>ApexClass</name> <types> </Package>As a percentage, the two files are more similar now than they were before. The only difference between them is still the <version> tag.
Executing destructive changes
So how do we execute destructive changes? The short answer is using Salesforce's migration tool. In a few minutes we'll execute "ant undeployCode," but we've a few items to take care of first.For me, the first problem was where to put the files destructiveChanges.xml and package.xml. The former is new and the latter is NOT the same file that usually appears in the src/ directory.
At Xede, we create git repositories for our projects. Each repository is forked from xede-sf-template.
DrozBook:git tgagne$ ls -lR xede-sf-template total 16 -rw-r--r-- 1 tgagne staff 684 Aug 21 2014 README.md -rwxr-xr-x 1 tgagne staff 1430 Sep 17 2014 build.xml drwxr-xr-x 4 tgagne staff 136 Aug 21 2014 del xede-sf-template//del: total 16 -rwxr-xr-x 1 tgagne staff 563 Jan 17 2014 destructiveChanges.xml -rw-r--r-- 1 tgagne staff 136 Aug 21 2014 package.xml
The repo includes a directory named "del" (not very imaginative) and inside it are the files destructiveChanges.xml and package.xml. It seems odd to me, but the migration tool requires both the destructiveChanges.xml AND a package.xml to reside there.
The package.xml file is the same empty version as before. But the template's destructiveChanges.xml contains placeholders--but still basically does nothing.
DrozBook:xede-sf-template tgagne$ cat del/package.xml <package xmlns="http://soap.sforce.com/2006/04/metadata"> <version>29.0</version> </package> DrozBook:xede-sf-template tgagne$ cat del/destructiveChanges.xml <?xml version="1.0" encoding="UTF-8"?> <Package xmlns="http://soap.sforce.com/2006/04/metadata"> <types> <name>ApexClass</name> </types> <types> <name>ApexComponent</name> </types> <types> <name>ApexPage</name> </types> <types> <name>ApexTrigger</name> </types> <types> <name>CustomObject</name> </types> <types> <name>Flow</name> </types> <types> <name>StaticResource</name> </types> <types> <name>Workflow</name> </types> </Package>
Now that we have a directory with both files in it, and we have versions of those files that basically do nothing, let's get ready to run the tool.
There's one more file we need to create that's required by the tool, build.xml. If you're not already using it for deployments you're likely not using it at all. My version of build.xml is in the parent of del/. You can see it above in the directory listing of xede-sf-template.
DrozBook:xede-sf-template tgagne$ cat build.xml <project name="xede-sf-template" default="usage" basedir="." xmlns:sf="antlib:com.salesforce"> <property environment="env"/> <target name="undeployCode"> <sf:deploy username="${env.SFUSER}" password="${env.SFPASS}" serverurl="${env.SFURL}" maxPoll="${env.SFPOLL}" ignoreWarnings="true" checkOnly="${env.CHECKONLY}" runAllTests="${env.RUNALLTESTS}" deployRoot="del"/> </target> </project>
IfSince build.xml is in the parent directory to del/ the "deployRoot" attribute is "del," the subdirectory.
The environment property (<property environment.../>) allows operating system environment variables to be substituted inside your build.xml. In the example above, the environment variables are about what you'd expect them to be (using the bash shell):
export SFUSER=myusername
export SFPASS=mysecretpassword
export SFURL=https://login.salesforce.com (or https://test.salesforce.com)
export SFPOLL=120
export CHECKONLY=false
export RUNALLTESTS=false
Right about now you may be thinking, "Who wants to set all those environment variables?" Truthfully, I don't. That's why I created a little script to do it for me called "build." But before we get into that let's just edit our build.xml file so it doesn't need environment variables.
The build.xml below is for a production org.
DrozBook:xede-sf-template tgagne$ cat build.xml <project name="xede-sf-template" default="usage" basedir="." xmlns:sf="antlib:com.salesforce"> <target name="undeployCode"> <sf:deploy username="tgagne+customer@xede.com" password="mysupersecretpassword" serverurl="https://login.salesforce.com" maxPoll="120" ignoreWarnings="true" checkOnly="false" runAllTests="false" deployRoot="del"/> </target> </project>
So now we have our build.xml, our del directory, del/destructiveChanges.xml which lists nothing and an empty del/package.xml file. Let's run ant.
DrozBook:xede-sf-template tgagne$ ant undeployCode Buildfile: /Users/tgagne/git/xede-sf-template/build.xml undeployCode: [sf:deploy] Request for a deploy submitted successfully. [sf:deploy] Request ID for the current deploy task: 0AfU00000034k0SKAQ [sf:deploy] Waiting for server to finish processing the request... [sf:deploy] Request Status: InProgress [sf:deploy] Request Status: Succeeded [sf:deploy] *********** DEPLOYMENT SUCCEEDED *********** [sf:deploy] Finished request 0AfU00000034k0SKAQ successfully. BUILD SUCCESSFUL Total time: 15 seconds
As you can see, it did nothing. Let's give it something to do, but make it a class that doesn't exist in the target org.
DrozBook:xede-sf-template tgagne$ cat del/destructiveChanges.xml <?xml version="1.0" encoding="UTF-8"?> <Package xmlns="http://soap.sforce.com/2006/04/metadata"> <types> <members>DoesNotExist</members> <name>ApexClass</name> </types> ... same as before ... </Package>
I've added a single class, DoesNotExist, to the ApexClass types list and we'll run it again.
DrozBook:xede-sf-template tgagne$ ant undeployCode Buildfile: /Users/tgagne/git/xede-sf-template/build.xml undeployCode: [sf:deploy] Request for a deploy submitted successfully. [sf:deploy] Request ID for the current deploy task: 0AfU00000034k0mKAA [sf:deploy] Waiting for server to finish processing the request... [sf:deploy] Request Status: InProgress [sf:deploy] Request Status: Succeeded [sf:deploy] *********** DEPLOYMENT SUCCEEDED *********** [sf:deploy] All warnings: [sf:deploy] 1. destructiveChanges.xml -- Warning: No ApexClass named: DoesNotExist found [sf:deploy] *********** DEPLOYMENT SUCCEEDED *********** [sf:deploy] Finished request 0AfU00000034k0mKAA successfully. BUILD SUCCESSFUL Total time: 15 seconds
Ant (with the migration tool plugin) is telling us it tried removing the Apex class "DoesNotExist" but it didn't exist. If the class had existed before but had already been removed this is the message it would display.
As a reader exercise, go ahead and create a class "DoesNotExist" in your org. I went into Setup->Classes->New and entered "public class DoesNotExist{}". It's about as useless a class as you can create, though I've seen and perhaps written worse.
If you run ant again you'll see it doesn't report an error.
DrozBook:xede-sf-template tgagne$ ant undeployCode Buildfile: /Users/tgagne/git/xede-sf-template/build.xml undeployCode: [sf:deploy] Request for a deploy submitted successfully. [sf:deploy] Request ID for the current deploy task: 0AfU00000034k11KAA [sf:deploy] Waiting for server to finish processing the request... [sf:deploy] Request Status: InProgress [sf:deploy] Request Status: Succeeded [sf:deploy] *********** DEPLOYMENT SUCCEEDED *********** [sf:deploy] Finished request 0AfU00000034k11KAA successfully. BUILD SUCCESSFUL Total time: 15 seconds
And there you have it! For a little extra I'll share my "build" script which makes it pretty easy to extract, undeploy (what we just did) and deploy code with or without tests or verification-only.