/******************************************************************************* | |
* Copyright (c) 2010 AGETO Service GmbH and others. | |
* All rights reserved. This program and the accompanying materials | |
* are made available under the terms of the Eclipse Public License v1.0 | |
* which accompanies this distribution, and is available at | |
* http://www.eclipse.org/legal/epl-v10.html | |
* | |
* Contributors: | |
* IBM Corporation - initial API and implementation of CVS factory | |
* Gunnar Wagenknecht - initial API and implementation | |
*******************************************************************************/ | |
package org.eclipse.egit.internal.fetchfactory; | |
import java.util.ArrayList; | |
import java.util.HashMap; | |
import java.util.List; | |
import java.util.Map; | |
import java.util.Properties; | |
import org.eclipse.core.runtime.CoreException; | |
import org.eclipse.core.runtime.IPath; | |
import org.eclipse.core.runtime.IStatus; | |
import org.eclipse.core.runtime.Path; | |
import org.eclipse.core.runtime.Status; | |
import org.eclipse.osgi.util.NLS; | |
import org.eclipse.pde.build.Constants; | |
import org.eclipse.pde.build.IAntScript; | |
import org.eclipse.pde.build.IFetchFactory; | |
import org.eclipse.pde.internal.build.IPDEBuildConstants; | |
import org.eclipse.pde.internal.build.Utils; | |
/** | |
* An <code>FetchTaskFactory</code> for building fetch scripts that will fetch | |
* content from a Git repository (id: <code>GIT</code>). | |
* <p> | |
* Map file format: <code><pre> | |
* type@id,[version]=GIT,args | |
* </pre></code> <code>args</code> is a comma-separated list of key/value pairs | |
* Accepted args include: | |
* <ul> | |
* <li><code>tag</code> - mandatory Git tag</li> | |
* <li><code>repo</code> - mandatory repo location</li> | |
* <li><code>path</code> - optional path relative to <code>repo</code> which | |
* points to the element (otherwise it's assumed that the element is at the repo | |
* root)</li> | |
* <li><code>prebuilt</code> - optional boolean value indicating that the path | |
* points to a pre-built bundle in the repository</li> | |
* </ul> | |
* </p> | |
* <p> | |
* Fetching is implemented as a three-step process. | |
* <ol> | |
* <li>The repository is cloned to local disc. If it already exists, it is | |
* assumed that it was previously cloned and just new commits will be fetched.</li> | |
* <li>The specified tag will be checked out in the local clone.</li> | |
* <li>The content of the path will be copied to the final build location.</li> | |
* </ol> | |
* </p> | |
*/ | |
@SuppressWarnings("restriction") | |
public class GITFetchTaskFactory implements IFetchFactory { | |
public static final String ID = "GIT"; //$NON-NLS-1$ | |
public static final String OVERRIDE_TAG = ID; | |
private static final String TARGET_GET_ELEMENT_FROM_REPO = "GitFetchElementFromLocalRepo"; //$NON-NLS-1$ | |
private static final String TARGET_GET_FILE_FROM_REPO = "GitFetchFileFromLocalRepo"; //$NON-NLS-1$ | |
private static final String TARGET_CLONE_REPO = "GitCloneRepoToLocalRepo"; //$NON-NLS-1$ | |
private static final String TARGET_UPDATE_REPO = "GitUpdateLocalRepo"; //$NON-NLS-1$ | |
private static final String TARGET_CHECKOUT_TAG = "GitCheckoutTagInLocalRepo"; //$NON-NLS-1$ | |
private static final String SEPARATOR = ","; //$NON-NLS-1$ | |
// Git specific keys used in the map being passed around. | |
private static final String KEY_REPO = "repo"; //$NON-NLS-1$ | |
private static final String KEY_PATH = "path"; //$NON-NLS-1$ | |
private static final String KEY_PREBUILT = "prebuilt"; //$NON-NLS-1$ | |
// Properties used in the Git part of the scripts | |
private static final String PROP_DESTINATIONFOLDER = "destinationFolder"; //$NON-NLS-1$ | |
private static final String PROP_GITREPO = "gitRepo"; //$NON-NLS-1$ | |
private static final String PROP_GITREPO_LOCAL_PATH = "gitRepoLocalPath"; //$NON-NLS-1$ | |
private static final String PROP_PATH = "path"; //$NON-NLS-1$ | |
private static final String PROP_FILE = "file"; //$NON-NLS-1$ | |
private static final String PROP_TAG = "tag"; //$NON-NLS-1$ | |
private static final String PROP_FILETOCHECK = "fileToCheck"; //$NON-NLS-1$ | |
// copied from FetchScriptGenerator to be independent from changes there | |
public static String PROP_FETCH_CACHE_LOCATION = "fetchCacheLocation"; //$NON-NLS-1$ | |
public static String DEFAULT_FETCH_CACHE_LOCATION = "${basedir}/scmCache"; //$NON-NLS-1$ | |
private static void printArg(IAntScript script, String value) { | |
final Map<String, String> params = new HashMap<String, String>(1); | |
params.put("value", value); //$NON-NLS-1$ | |
script.printElement("arg", params); //$NON-NLS-1$ | |
} | |
private void addProjectReference(Map<String, String> entryInfos) { | |
final String repoLocation = entryInfos.get(KEY_REPO); | |
final String path = entryInfos.get(KEY_PATH); | |
final String projectName = entryInfos.get(KEY_ELEMENT_NAME); | |
final String tag = entryInfos.get(IFetchFactory.KEY_ELEMENT_TAG); | |
if (repoLocation != null && projectName != null) { | |
final String sourceUrl = asReference(repoLocation, path, | |
projectName, tag); | |
if (sourceUrl != null) { | |
entryInfos.put(Constants.KEY_SOURCE_REFERENCES, sourceUrl); | |
} | |
} | |
} | |
@Override | |
public void addTargets(IAntScript script) { | |
final Map<String, String> params = new HashMap<String, String>(3); | |
final List<String> args = new ArrayList<String>(5); | |
script.printComment("Start of common Git fetch factory targets."); //$NON-NLS-1$ | |
// determine if clone git operation should be skipped | |
script.printTargetDeclaration( | |
"GitCheckSkipClone", null, null, null, null); //$NON-NLS-1$ | |
printGitRepoBaseLocationDefault(script); | |
printConditionStart(script, "skipClone", null, null); //$NON-NLS-1$ | |
script.printStartTag("or"); //$NON-NLS-1$ | |
script.incrementIdent(); | |
printAvailableFile(script, | |
Utils.getPropertyFormat(PROP_GITREPO_LOCAL_PATH)); | |
printAvailableFile(script, Utils.getPropertyFormat(PROP_FILETOCHECK)); | |
script.decrementIdent(); | |
script.printEndTag("or"); //$NON-NLS-1$ | |
printConditionEnd(script); | |
script.printTargetEnd(); | |
// clone repo task | |
script.printTargetDeclaration(TARGET_CLONE_REPO, | |
"GitCheckSkipClone", null, "skipClone", null); //$NON-NLS-1$ //$NON-NLS-2$ | |
printGitRepoBaseLocationDefault(script); | |
params.put("dir", Utils.getPropertyFormat(PROP_GITREPO_LOCAL_PATH)); //$NON-NLS-1$ | |
script.printElement("mkdir", params); //$NON-NLS-1$ | |
args.add(Utils.getPropertyFormat(PROP_GITREPO)); | |
args.add("."); //$NON-NLS-1$ | |
printGitTask(script, "clone", args); //$NON-NLS-1$ | |
script.printTargetEnd(); | |
// pull repo task | |
script.printTargetDeclaration(TARGET_UPDATE_REPO, null, | |
Utils.getPropertyFormat(PROP_GITREPO_LOCAL_PATH), | |
"${fileToCheck}", null); //$NON-NLS-1$ | |
printGitRepoBaseLocationDefault(script); | |
args.clear(); | |
args.add("--all"); //$NON-NLS-1$ | |
printGitTask(script, "fetch", null); //$NON-NLS-1$ | |
script.printTargetEnd(); | |
// checkout tag task | |
script.printTargetDeclaration(TARGET_CHECKOUT_TAG, null, | |
Utils.getPropertyFormat(PROP_GITREPO_LOCAL_PATH), | |
"${fileToCheck}", null); //$NON-NLS-1$ | |
printGitRepoBaseLocationDefault(script); | |
args.clear(); | |
args.add("--force"); //$NON-NLS-1$ | |
args.add(Utils.getPropertyFormat(PROP_TAG)); | |
printGitTask(script, "checkout", args); //$NON-NLS-1$ | |
script.printTargetEnd(); | |
// copy an elements from repo to the destination | |
script.printTargetDeclaration(TARGET_GET_ELEMENT_FROM_REPO, null, | |
Utils.getPropertyFormat(PROP_GITREPO_LOCAL_PATH), | |
"${fileToCheck}", null); //$NON-NLS-1$ | |
printGitRepoBaseLocationDefault(script); | |
params.clear(); | |
params.put("todir", Utils.getPropertyFormat(PROP_DESTINATIONFOLDER)); //$NON-NLS-1$ | |
script.printStartTag("copy", params); //$NON-NLS-1$ | |
script.incrementIdent(); | |
params.clear(); | |
params.put( | |
"dir", Utils.getPropertyFormat(PROP_GITREPO_LOCAL_PATH) + "/" + Utils.getPropertyFormat(PROP_PATH)); //$NON-NLS-1$ //$NON-NLS-2$ | |
script.printElement("fileset", params); //$NON-NLS-1$ | |
script.decrementIdent(); | |
script.printEndTag("copy"); //$NON-NLS-1$ | |
script.printTargetEnd(); | |
// copy a file from repo to the destination | |
script.printTargetDeclaration(TARGET_GET_FILE_FROM_REPO, null, | |
Utils.getPropertyFormat(PROP_GITREPO_LOCAL_PATH), | |
"${fileToCheck}", null); //$NON-NLS-1$ | |
printGitRepoBaseLocationDefault(script); | |
params.clear(); | |
params.put( | |
"file", Utils.getPropertyFormat(PROP_GITREPO_LOCAL_PATH) + "/" + Utils.getPropertyFormat(PROP_PATH)); //$NON-NLS-1$ //$NON-NLS-2$ | |
params.put( | |
"tofile", Utils.getPropertyFormat(PROP_DESTINATIONFOLDER) + "/" + Utils.getPropertyFormat(PROP_FILE)); //$NON-NLS-1$ //$NON-NLS-2$ | |
params.put("failOnError", "false"); //$NON-NLS-1$ //$NON-NLS-2$ | |
script.printElement("copy", params); //$NON-NLS-1$ | |
script.printTargetEnd(); | |
script.printComment("End of common Git fetch factory targets."); //$NON-NLS-1$ | |
} | |
/** | |
* Generates a path where the specified repository should be cloned to. | |
* | |
* @param repoLocation | |
* @return local file system path | |
*/ | |
private String asLocalRepo(String repoLocation) { | |
final StringBuffer b = new StringBuffer(repoLocation.length()); | |
b.append(Utils.getPropertyFormat(PROP_FETCH_CACHE_LOCATION)) | |
.append('/'); | |
for (int i = 0; i < repoLocation.length(); i++) { | |
final char c = repoLocation.charAt(i); | |
if (Character.isLetterOrDigit(c)) { | |
b.append(c); | |
} else { | |
// replace with '_' | |
b.append('_'); | |
} | |
} | |
if (b.charAt(b.length() - 1) == '/') | |
return b.substring(0, b.length() - 1); | |
return b.toString(); | |
} | |
/** | |
* Creates an SCMURL reference to the associated source. | |
* | |
* @param repoLocation | |
* @param path | |
* @param projectName | |
* @return project reference string or <code>null</code> if none | |
*/ | |
private String asReference(String repoLocation, String path, | |
String projectName, String tagName) { | |
final StringBuffer buffer = new StringBuffer(); | |
buffer.append("scm:git:"); //$NON-NLS-1$ | |
// use repoLocation as is (Git support many URLs) | |
buffer.append(repoLocation); | |
// path | |
if (path != null) { | |
final Path projectPath = new Path(path); | |
buffer.append(";path=\""); //$NON-NLS-1$ | |
buffer.append(projectPath.toString()); | |
buffer.append('"'); | |
// project name if different then last path segment | |
if (!projectPath.lastSegment().equals(projectName)) { | |
buffer.append(";project=\""); //$NON-NLS-1$ | |
buffer.append(projectName); | |
buffer.append('"'); | |
} | |
} | |
if (tagName != null && !tagName.equals("master")) { //$NON-NLS-1$ | |
buffer.append(";tag="); //$NON-NLS-1$ | |
buffer.append(tagName); | |
} | |
return buffer.toString(); | |
} | |
@Override | |
public void generateRetrieveElementCall(Map entryInfos, IPath destination, | |
IAntScript script) { | |
final String type = (String) entryInfos.get(KEY_ELEMENT_TYPE); | |
final boolean prebuilt = Boolean.valueOf( | |
(String) entryInfos.get(KEY_PREBUILT)).booleanValue(); | |
final String gitRepo = (String) entryInfos.get(KEY_REPO); | |
final String localGitRepo = asLocalRepo(gitRepo); | |
final String path = (String) entryInfos.get(KEY_PATH); | |
final String tag = (String) entryInfos | |
.get(IFetchFactory.KEY_ELEMENT_TAG); | |
// set required property defaults | |
printGitRepoBaseLocationDefault(script); | |
final String gitCopyTarget; | |
IPath locationToCheck = null; | |
final Map<String, String> params = new HashMap<String, String>(5); | |
params.put(PROP_GITREPO_LOCAL_PATH, localGitRepo); | |
if (prebuilt) { | |
// if we have a pre-built JAR then we want to put it right in the | |
// plugins/features directory | |
// and not a sub-directory so strip off last segment | |
params.put(PROP_DESTINATIONFOLDER, destination | |
.removeLastSegments(1).toString()); | |
params.put(PROP_PATH, path); | |
// extract file name from path | |
final String prebuiltJarFile = new Path(path).lastSegment(); | |
params.put(PROP_FILE, prebuiltJarFile); | |
// if we have a pre-built plug-in then we want to check the | |
// existence of the JAR file | |
// rather than the plug-in manifest. | |
locationToCheck = destination.removeLastSegments(1).append( | |
prebuiltJarFile); | |
// get single file | |
gitCopyTarget = TARGET_GET_FILE_FROM_REPO; | |
} else { | |
params.put(PROP_DESTINATIONFOLDER, destination.toString()); | |
if (path != null) { | |
params.put(PROP_PATH, new Path(path).makeRelative().toString()); | |
} | |
// check for existence of element descriptor | |
if (type.equals(ELEMENT_TYPE_FEATURE)) { | |
locationToCheck = destination | |
.append(Constants.FEATURE_FILENAME_DESCRIPTOR); | |
} else if (type.equals(ELEMENT_TYPE_PLUGIN)) { | |
locationToCheck = destination | |
.append(Constants.PLUGIN_FILENAME_DESCRIPTOR); | |
} else if (type.equals(ELEMENT_TYPE_FRAGMENT)) { | |
locationToCheck = destination | |
.append(Constants.FRAGMENT_FILENAME_DESCRIPTOR); | |
} else if (type.equals(ELEMENT_TYPE_BUNDLE)) { | |
locationToCheck = destination | |
.append(Constants.BUNDLE_FILENAME_DESCRIPTOR); | |
} | |
// copy complete element | |
gitCopyTarget = TARGET_GET_ELEMENT_FROM_REPO; | |
} | |
// check for availability of element in destination | |
if (locationToCheck != null) { | |
params.put(PROP_FILETOCHECK, locationToCheck.toString()); | |
printAvailableTask(locationToCheck.toString(), | |
locationToCheck.toString(), script); | |
// plug-ins/fragments may not have an xml descriptor anymore, thus | |
// also check for MANIFEST.MF | |
if (!prebuilt | |
&& (type.equals(IFetchFactory.ELEMENT_TYPE_PLUGIN) || type | |
.equals(IFetchFactory.ELEMENT_TYPE_FRAGMENT))) { | |
printAvailableTask(locationToCheck.toString(), destination | |
.append(Constants.BUNDLE_FILENAME_DESCRIPTOR) | |
.toString(), script); | |
} | |
} | |
// clone the Git repo to a local repo and checkout the tag | |
printCloneRepoAndCheckoutTagTasks(script, gitRepo, localGitRepo, tag, | |
locationToCheck); | |
// copy the content into the destination | |
script.printAntCallTask(gitCopyTarget, true, params); | |
} | |
@Override | |
public void generateRetrieveFilesCall(final Map entryInfos, | |
IPath destination, final String[] files, IAntScript script) { | |
final String gitRepo = (String) entryInfos.get(KEY_REPO); | |
final String localGitRepo = asLocalRepo(gitRepo); | |
final String path = (String) entryInfos.get(KEY_PATH); | |
final String tag = (String) entryInfos | |
.get(IFetchFactory.KEY_ELEMENT_TAG); | |
// set required property defaults | |
printGitRepoBaseLocationDefault(script); | |
// clone the Git repo to a local repo and checkout the tag | |
printCloneRepoAndCheckoutTagTasks(script, gitRepo, localGitRepo, tag, | |
null); | |
// copy files to destination | |
final Map<String, String> params = new HashMap<String, String>(4); | |
for (int i = 0; i < files.length; i++) { | |
final String file = files[i]; | |
IPath filePath; | |
if (path != null) { | |
filePath = new Path(path).append(file); | |
} else { | |
filePath = new Path((String) entryInfos.get(KEY_ELEMENT_NAME)) | |
.append(file); | |
} | |
params.clear(); | |
params.put(PROP_GITREPO_LOCAL_PATH, localGitRepo); | |
params.put(PROP_DESTINATIONFOLDER, destination.toString()); | |
params.put(PROP_PATH, filePath.toString()); | |
params.put(PROP_FILE, file); | |
script.printAntCallTask(TARGET_GET_FILE_FROM_REPO, true, params); | |
} | |
} | |
@SuppressWarnings("unchecked") | |
@Override | |
public void parseMapFileEntry(String repoSpecificentry, | |
Properties overrideTags, Map entryInfos) throws CoreException { | |
// build up the table of arguments in the map file entry | |
final String[] arguments = Utils.getArrayFromStringWithBlank( | |
repoSpecificentry, SEPARATOR); | |
final Map<String, String> table = new HashMap<String, String>(); | |
for (int i = 0; i < arguments.length; i++) { | |
final String arg = arguments[i]; | |
// if we have at least one arg without an equals sign, then | |
// revert back to the legacy parsing | |
final int index = arg.indexOf('='); | |
if (index == -1) { | |
final String message = NLS.bind( | |
Messages.error_incorrectDirectoryEntryKeyValue, | |
entryInfos.get(KEY_ELEMENT_NAME)); | |
throw new CoreException(new Status(IStatus.ERROR, | |
IPDEBuildConstants.PI_PDEBUILD, 1, message, null)); | |
} | |
final String key = arg.substring(0, index); | |
final String value = arg.substring(index + 1); | |
table.put(key, value); | |
} | |
// sanity check that all required attributes are present | |
if (!table.containsKey(KEY_REPO)) { | |
final String message = NLS.bind( | |
Messages.error_directoryEntryRequiresRepo, | |
entryInfos.get(KEY_ELEMENT_NAME)); | |
throw new CoreException(new Status(IStatus.ERROR, | |
IPDEBuildConstants.PI_PDEBUILD, 1, message, null)); | |
} | |
// add entries to the entryInfo map here instead of inside the loop | |
// to avoid contaminating entryInfos | |
final String overrideTag = overrideTags != null ? overrideTags | |
.getProperty(OVERRIDE_TAG) : null; | |
entryInfos | |
.put(IFetchFactory.KEY_ELEMENT_TAG, (overrideTag != null | |
&& overrideTag.trim().length() != 0 ? overrideTag | |
: table.get(IFetchFactory.KEY_ELEMENT_TAG))); | |
entryInfos.put(KEY_REPO, table.get(KEY_REPO)); | |
if (table.get(KEY_PATH) != null) | |
entryInfos.put(KEY_PATH, new Path(table.get(KEY_PATH)) | |
.makeRelative().removeTrailingSeparator().toString()); // sanitize | |
// path | |
entryInfos.put(KEY_PREBUILT, table.get(KEY_PREBUILT)); | |
addProjectReference(entryInfos); | |
} | |
private void printAvailableFile(IAntScript script, String file) { | |
script.println("<available file=\"" + file + "\"/>"); //$NON-NLS-1$ //$NON-NLS-2$ | |
} | |
/** | |
* Print the <code>available</code> Ant task to this script. This task sets | |
* a property value if the given file exists at runtime. | |
* | |
* @param property | |
* the property to set | |
* @param file | |
* the file to look for | |
*/ | |
private void printAvailableTask(String property, String file, | |
IAntScript script) { | |
final Map<String, String> params = new HashMap<String, String>(2); | |
params.put("property", property); //$NON-NLS-1$ | |
params.put("file", file); //$NON-NLS-1$ | |
script.printElement("available", params); //$NON-NLS-1$ | |
} | |
private void printCloneRepoAndCheckoutTagTasks(IAntScript script, | |
String gitRepo, String localGitRepo, String tag, | |
IPath locationToCheckIfPluginLocal) { | |
// determine availability of local repo (done to avoid unnecessary Git | |
// operations) | |
printAvailableTask(localGitRepo, localGitRepo, script); | |
// pull if already cloned | |
final Map<String, String> params = new HashMap<String, String>(5); | |
params.put(PROP_GITREPO_LOCAL_PATH, localGitRepo); | |
if (locationToCheckIfPluginLocal != null) | |
params.put(PROP_FILETOCHECK, | |
locationToCheckIfPluginLocal.toString()); | |
script.printAntCallTask(TARGET_UPDATE_REPO, true, params); | |
// clone if not cloned | |
params.put(PROP_GITREPO, gitRepo); | |
params.put(PROP_GITREPO_LOCAL_PATH, localGitRepo); | |
if (locationToCheckIfPluginLocal != null) | |
params.put(PROP_FILETOCHECK, | |
locationToCheckIfPluginLocal.toString()); | |
script.printAntCallTask(TARGET_CLONE_REPO, true, params); | |
// re-determine availability of local repo (done to avoid unnecessary | |
// Git operations) | |
printAvailableTask(localGitRepo, localGitRepo, script); | |
// checkout the tag | |
params.clear(); | |
params.put(PROP_GITREPO_LOCAL_PATH, localGitRepo); | |
params.put(PROP_TAG, tag); | |
if (locationToCheckIfPluginLocal != null) | |
params.put(PROP_FILETOCHECK, | |
locationToCheckIfPluginLocal.toString()); | |
script.printAntCallTask(TARGET_CHECKOUT_TAG, true, params); | |
} | |
private void printConditionEnd(IAntScript script) { | |
script.decrementIdent(); | |
script.printEndTag("condition"); //$NON-NLS-1$ | |
} | |
private void printConditionStart(IAntScript script, String property, | |
String value, String elseValue) { | |
script.printTabs(); | |
script.print("<condition"); //$NON-NLS-1$ | |
script.printAttribute("property", property, true); //$NON-NLS-1$ | |
script.printAttribute("value", value, false); //$NON-NLS-1$ | |
script.printAttribute("else", elseValue, false); //$NON-NLS-1$ | |
script.print(">"); //$NON-NLS-1$ | |
script.println(); | |
script.incrementIdent(); | |
} | |
private void printGitRepoBaseLocationDefault(IAntScript script) { | |
script.println("<property name=\"" + PROP_FETCH_CACHE_LOCATION + "\" value=\"" + DEFAULT_FETCH_CACHE_LOCATION + "\" />"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ | |
} | |
private void printGitTask(IAntScript script, String commandName, List args) { | |
// print command | |
final StringBuffer m = new StringBuffer(); | |
m.append("[GIT] "); //$NON-NLS-1$ | |
m.append(Utils.getPropertyFormat(PROP_GITREPO_LOCAL_PATH)); | |
m.append(" >> git ").append(commandName); //$NON-NLS-1$ | |
if (args != null) { | |
for (int i = 0; i < args.size(); i++) { | |
m.append(" ").append(args.get(i)); //$NON-NLS-1$ | |
} | |
} | |
script.printEchoTask(null, m.toString(), "info"); //$NON-NLS-1$ | |
final Map<String, String> params = new HashMap<String, String>(3); | |
params.put("executable", "git"); //$NON-NLS-1$ //$NON-NLS-2$ | |
params.put("dir", Utils.getPropertyFormat(PROP_GITREPO_LOCAL_PATH)); //$NON-NLS-1$ | |
params.put("failOnError", "true"); //$NON-NLS-1$ //$NON-NLS-2$ | |
script.printStartTag("exec", params); //$NON-NLS-1$ | |
script.incrementIdent(); | |
// cmd | |
printArg(script, commandName); | |
// append arguments | |
if (args != null) { | |
for (int i = 0; i < args.size(); i++) { | |
final String arg = (String) args.get(i); | |
printArg(script, arg); | |
} | |
} | |
script.decrementIdent(); | |
script.printEndTag("exec"); //$NON-NLS-1$ | |
} | |
} |