/******************************************************************************* * Copyright (c) 2004, 2006 * Thomas Hallgren, Kenneth Olwing, Mitch Sonies * Pontus Rydin, Nils Unden, Peer Torngren * The code, documentation and other materials contained herein have been * licensed under the Eclipse Public License - v 1.0 by the individual * copyright holders listed above, as Initial Contributors under such license. * The text of such license is available at www.eclipse.org. *******************************************************************************/ package org.eclipse.buckminster.core.build; import java.io.BufferedReader; import java.io.File; import java.io.FileReader; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.eclipse.buckminster.core.CorePlugin; import org.eclipse.buckminster.core.Messages; import org.eclipse.buckminster.core.helpers.StreamPump; import org.eclipse.buckminster.runtime.BuckminsterException; import org.eclipse.buckminster.runtime.Logger; import org.eclipse.buckminster.runtime.Trivial; import org.eclipse.core.resources.IProject; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IPath; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.Path; import org.eclipse.core.variables.IStringVariableManager; import org.eclipse.core.variables.IValueVariable; import org.eclipse.core.variables.VariablesPlugin; import org.eclipse.osgi.util.NLS; /** * @author kolwing */ public class ExternalCommandBuilder extends AbstractBuckminsterBuilder implements ExternalCommandBuilderConstants { private static Pattern extractQuotedItemPattern = Pattern.compile("^\"(.*?)(?<!\\\\)(\"|$)"); //$NON-NLS-1$ private static Pattern whitespaceAndQuotationMarkPattern = Pattern.compile("\\s\""); //$NON-NLS-1$ public static String getCommandLine(LauncherDefinition[] launcherDefs, String defToUse, String addArgs, IProject project, int kind) throws CoreException { IStringVariableManager svm = VariablesPlugin.getDefault().getStringVariableManager(); IValueVariable projLocVar = svm.newValueVariable("buckminster.project.location", ""); //$NON-NLS-1$ //$NON-NLS-2$ projLocVar.setValue(project.getLocation().toOSString()); IValueVariable buildTypeVar = svm.newValueVariable("buckminster.build.type", ""); //$NON-NLS-1$ //$NON-NLS-2$ buildTypeVar.setValue(AbstractBuckminsterBuilder.kindToString(kind)); IValueVariable[] variables = new IValueVariable[] { projLocVar, buildTypeVar }; svm.addVariables(variables); try { String rawCommandLine = null; try { if (defToUse == null || defToUse.length() == 0) throw BuckminsterException.fromMessage(Messages.Missing_value_for_definition_to_use); String resolvedDefsToUse = svm.performStringSubstitution(defToUse); Logger logger = CorePlugin.getLogger(); if (logger.isDebugEnabled()) { logger.debug("Definition to use, before: '%s'", defToUse); //$NON-NLS-1$ logger.debug("Definition to use, after: '%s'", resolvedDefsToUse); //$NON-NLS-1$ } for (LauncherDefinition ld : launcherDefs) { if (Pattern.matches(ld.getPattern(), resolvedDefsToUse)) { StringBuilder sb = new StringBuilder(ld.getCommandLine()); if (addArgs != null && addArgs.length() > 0) sb.append(" ").append(addArgs); //$NON-NLS-1$ rawCommandLine = sb.toString(); break; } } String commandLine = null; if (rawCommandLine != null) { commandLine = svm.performStringSubstitution(rawCommandLine); if (logger.isDebugEnabled()) { logger.debug("Command line, before: '" + rawCommandLine + '\''); //$NON-NLS-1$ logger.debug("Command line, after: '" + commandLine + '\''); //$NON-NLS-1$ } } return commandLine; } catch (CoreException ce) { throw ce; } catch (Exception e) { throw BuckminsterException.wrap(e); } } finally { svm.removeVariables(variables); } } public static String getDefinitionToUse(Map<String, String> args) { String s = AbstractBuckminsterBuilder.getValue(args, LAUNCHERDEFINITION_TO_USE_KEY); if (s == null) s = DEFAULT_LAUNCHERDEFINITION_TO_USE; return s; } public static LauncherDefinition[] getLauncherDefinitions(File launcherDefinitionsFile) throws CoreException { List<LauncherDefinition> defs = new ArrayList<LauncherDefinition>(); if (launcherDefinitionsFile.exists()) { BufferedReader br = null; try { br = new BufferedReader(new FileReader(launcherDefinitionsFile)); String pattern; while ((pattern = br.readLine()) != null) { String commandLine = br.readLine(); if (commandLine == null) break; defs.add(new LauncherDefinition(pattern, commandLine)); } br.close(); } catch (Throwable t) { throw BuckminsterException.wrap(t); } finally { if (br != null) try { br.close(); } catch (Exception e) { // noop } } } return defs.toArray(new LauncherDefinition[0]); } public static File getLauncherDefinitionsFile(Map<String, String> args, IProject project) throws CoreException { String launcherDefinitionsFile = AbstractBuckminsterBuilder.getValue(args, LAUNCHERDEFINITIONS_FILE_KEY); if (launcherDefinitionsFile == null || launcherDefinitionsFile.length() == 0) launcherDefinitionsFile = DEFAULT_LAUNCHERDEFINITIONS_FILE; // file name must always be relative to project root // IPath relativePath = new Path(launcherDefinitionsFile); if (relativePath.isAbsolute()) throw BuckminsterException.fromMessage(NLS.bind(Messages.Launcher_definitions_file_name_not_relative_to_project_root_0, launcherDefinitionsFile)); IPath fullPath = project.getLocation().append(relativePath); return fullPath.toFile(); } @Override protected IProject[] doBuild(int kind, Map<String, String> args, IProgressMonitor monitor) throws CoreException { File launcherDefinitionsFile = getLauncherDefinitionsFile(args, this.getProject()); LauncherDefinition[] launcherDefinitions = getLauncherDefinitions(launcherDefinitionsFile); String defToUse = getDefinitionToUse(args); String addArgs = AbstractBuckminsterBuilder.getValue(args, ADDITIONAL_ARGUMENTS_KEY); String fullCommandLine = getCommandLine(launcherDefinitions, defToUse, addArgs, this.getProject(), kind); if (fullCommandLine == null) throw BuckminsterException.fromMessage(Messages.Could_not_resolve_to_a_command_line); Logger logger = CorePlugin.getLogger(); logger.info(NLS.bind(Messages.Command_line_0, fullCommandLine)); String[] splitCommandLine = this.splitCommandLine(fullCommandLine); if (logger.isDebugEnabled()) { logger.debug("Split cmd line:"); //$NON-NLS-1$ for (String s : splitCommandLine) logger.debug("=> " + s); //$NON-NLS-1$ } ProcessBuilder pb = new ProcessBuilder(splitCommandLine); pb.redirectErrorStream(true); pb.directory(launcherDefinitionsFile.getParentFile()); try { Process p = pb.start(); StreamPump thrStdOut = new StreamPump(p.getInputStream(), this.getOutStream()); thrStdOut.start(); thrStdOut.join(); int exitValue = p.waitFor(); if (exitValue != 0) throw BuckminsterException.fromMessage(NLS.bind(Messages.External_command_0_exited_with_1, fullCommandLine, Integer .valueOf(exitValue))); } catch (Exception e) { throw BuckminsterException.wrap(e); } return null; } private String[] splitCommandLine(String arg) { String trimmedArg = arg.trim(); if (trimmedArg.length() == 0) return Trivial.EMPTY_STRING_ARRAY; Matcher wsAndQMmatcher = whitespaceAndQuotationMarkPattern.matcher(trimmedArg); List<String> parsed = new ArrayList<String>(); if (trimmedArg.startsWith("\"")) //$NON-NLS-1$ { // now extract the first item from the string (skipping the citation // marks) // recursively parse the rest of the line (if any) // Matcher extractQIMatcher = extractQuotedItemPattern.matcher(trimmedArg); if (extractQIMatcher.find()) { parsed.add(extractQIMatcher.group(1)); int end = extractQIMatcher.end(1); if (trimmedArg.length() > end) Collections.addAll(parsed, this.splitCommandLine(trimmedArg.substring(end + 1))); } else throw new RuntimeException(Messages.Unexpected_non_match); } else if (wsAndQMmatcher.find()) { // divvy it up into two parts // the left part can be split on simple whitespace only // the right part should be recursively parsed // int end = wsAndQMmatcher.end(); String left = trimmedArg.substring(0, wsAndQMmatcher.end() - 1); String right = trimmedArg.substring(end - 1); Collections.addAll(parsed, left.split("\\s")); //$NON-NLS-1$ Collections.addAll(parsed, this.splitCommandLine(right)); } else { // no attempts to guard embedded whitespace exists, just split it // all // Collections.addAll(parsed, trimmedArg.split("\\s")); //$NON-NLS-1$ } return parsed.toArray(new String[parsed.size()]); } }