// CustomCommand.cs // // Author: // Lluis Sanchez Gual // // Copyright (c) 2007 Novell, Inc (http://www.novell.com) // // 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. // // using System; using System.IO; using System.ComponentModel; using MonoDevelop.Core.Serialization; using MonoDevelop.Core; using MonoDevelop.Core.Execution; using MonoDevelop.Core.StringParsing; using System.Collections.Generic; using MonoDevelop.Core.ProgressMonitoring; using System.Threading.Tasks; using System.Threading; namespace MonoDevelop.Projects { public class CustomCommand { [ItemProperty ()] CustomCommandType type; [ItemProperty (DefaultValue = "")] string name = ""; [ItemProperty ()] string command; [ItemProperty ()] string workingdir; [ItemProperty (DefaultValue = false)] bool externalConsole; [ItemProperty (DefaultValue = false)] bool pauseExternalConsole; [ItemProperty("EnvironmentVariables", SkipEmpty = true)] [ItemProperty("Variable", Scope = "item")] [ItemProperty("name", Scope = "key")] [ItemProperty("value", Scope = "value")] Dictionary environmentVariables = new Dictionary (); public Dictionary EnvironmentVariables { get { return environmentVariables; } } public CustomCommandType Type { get { return type; } set { type = value; } } public string Command { get { return command; } set { command = value ?? string.Empty; } } public string WorkingDir { get { return workingdir; } set { workingdir = value; } } public string Name { get { return name; } set { name = value; } } public bool ExternalConsole { get { return externalConsole; } set { externalConsole = value; } } public bool PauseExternalConsole { get { return pauseExternalConsole; } set { pauseExternalConsole = value; } } public string TypeLabel { get { switch (type) { case CustomCommandType.BeforeBuild: return GettextCatalog.GetString ("Before Build"); case CustomCommandType.Build: return GettextCatalog.GetString ("Build"); case CustomCommandType.AfterBuild: return GettextCatalog.GetString ("After Build"); case CustomCommandType.BeforeExecute: return GettextCatalog.GetString ("Before Execute"); case CustomCommandType.Execute: return GettextCatalog.GetString ("Execute"); case CustomCommandType.AfterExecute: return GettextCatalog.GetString ("After Execute"); case CustomCommandType.BeforeClean: return GettextCatalog.GetString ("Before Clean"); case CustomCommandType.Clean: return GettextCatalog.GetString ("Clean"); case CustomCommandType.AfterClean: return GettextCatalog.GetString ("After Clean"); case CustomCommandType.Custom: return GettextCatalog.GetString ("Custom Command"); default: return type.ToString (); } } } public string GetCommandFile (WorkspaceObject entry, ConfigurationSelector configuration) { string exe, args; StringTagModel tagSource = GetTagModel (entry, configuration); ParseCommand (tagSource, out exe, out args); return exe; } public string GetCommandArgs (WorkspaceObject entry, ConfigurationSelector configuration) { string exe, args; StringTagModel tagSource = GetTagModel (entry, configuration); ParseCommand (tagSource, out exe, out args); return args; } public FilePath GetCommandWorkingDir (WorkspaceObject entry, ConfigurationSelector configuration) { StringTagModel tagSource = GetTagModel (entry, configuration); if (string.IsNullOrEmpty (workingdir)) return entry.BaseDirectory; FilePath dir = StringParserService.Parse (workingdir, tagSource); return dir.ToAbsolute (entry.BaseDirectory); } public CustomCommand Clone () { CustomCommand cmd = new CustomCommand (); cmd.command = command; cmd.workingdir = workingdir; cmd.name = name; cmd.externalConsole = externalConsole; cmd.pauseExternalConsole = pauseExternalConsole; cmd.type = type; foreach (var variable in environmentVariables) { cmd.environmentVariables.Add (variable.Key, variable.Value); } return cmd; } public bool Equals (CustomCommand cmd) { if (command != cmd.command || workingdir != cmd.workingdir || externalConsole != cmd.externalConsole || pauseExternalConsole != cmd.pauseExternalConsole || type != cmd.type) return false; if (environmentVariables.Count != cmd.environmentVariables.Count) return false; foreach (var e in environmentVariables) { string val; if (!cmd.environmentVariables.TryGetValue (e.Key, out val) || e.Value != val) return false; } return true; } StringTagModel GetTagModel (WorkspaceObject entry, ConfigurationSelector configuration) { if (entry is SolutionFolderItem) return ((SolutionFolderItem)entry).GetStringTagModel (configuration); else if (entry is WorkspaceItem) return ((WorkspaceItem)entry).GetStringTagModel (); else return new StringTagModel (); } void ParseCommand (StringTagModel tagSource, out string cmd, out string args) { ParseCommand (out cmd, out args); cmd = StringParserService.Parse (cmd, tagSource); args = StringParserService.Parse (args, tagSource); } internal void ParseCommand (out string cmd, out string args) { if (command.Length > 0 && command [0] == '"') { int n = command.IndexOf ('"', 1); if (n != -1) { cmd = command.Substring (1, n - 1); args = command.Substring (n + 1).Trim (); } else { cmd = command; args = string.Empty; } } else { int i = command.IndexOf (' '); if (i != -1) { cmd = command.Substring (0, i); args = command.Substring (i + 1).Trim (); } else { cmd = command; args = string.Empty; } } } public ProcessExecutionCommand CreateExecutionCommand (WorkspaceObject entry, ConfigurationSelector configuration) { if (string.IsNullOrEmpty (command)) throw new UserException (GettextCatalog.GetString ("Invalid custom command for '{0}' step: the path to the command to execute has not been provided.", TypeLabel)); string exe, args; StringTagModel tagSource = GetTagModel (entry, configuration); ParseCommand (tagSource, out exe, out args); //if the executable name matches an executable in the project directory, use that, for back-compat //else fall back and let the execution handler handle it via PATH, working directory, etc. if (!Path.IsPathRooted (exe)) { string localPath = ((FilePath) exe).ToAbsolute (entry.BaseDirectory).FullPath; if (File.Exists (localPath)) exe = localPath; } ProcessExecutionCommand cmd = Runtime.ProcessService.CreateCommand (exe); cmd.Arguments = args; FilePath workingDir = this.workingdir; if (!workingDir.IsNullOrEmpty) workingDir = StringParserService.Parse (workingDir, tagSource); cmd.WorkingDirectory = workingDir.IsNullOrEmpty ? entry.BaseDirectory : workingDir.ToAbsolute (entry.BaseDirectory); if (environmentVariables != null) { var vars = new Dictionary (); foreach (var v in environmentVariables) vars [v.Key] = StringParserService.Parse (v.Value, tagSource); cmd.EnvironmentVariables = vars; } return cmd; } public Task Execute (ProgressMonitor monitor, WorkspaceObject entry, ConfigurationSelector configuration) { return Execute (monitor, entry, null, configuration); } public bool CanExecute (WorkspaceObject entry, ExecutionContext context, ConfigurationSelector configuration) { if (string.IsNullOrEmpty (command)) return false; if (context == null) return true; var cmd = CreateExecutionCommand (entry, configuration); return context.ExecutionHandler.CanExecute (cmd); } public async Task Execute (ProgressMonitor monitor, WorkspaceObject entry, ExecutionContext context, ConfigurationSelector configuration) { ProcessExecutionCommand cmd = CreateExecutionCommand (entry, configuration); monitor.Log.WriteLine (GettextCatalog.GetString ("Executing: {0} {1}", cmd.Command, cmd.Arguments)); if (!Directory.Exists (cmd.WorkingDirectory)) { monitor.ReportError (GettextCatalog.GetString ("Custom command working directory does not exist"), null); return false; } ProcessAsyncOperation oper = null; OperationConsole console = null; var result = true; try { if (context != null) { if (externalConsole) console = context.ExternalConsoleFactory.CreateConsole (!pauseExternalConsole, monitor.CancellationToken); else console = context.ConsoleFactory.CreateConsole (monitor.CancellationToken); oper = context.ExecutionHandler.Execute (cmd, console); } else { if (externalConsole) { console = ExternalConsoleFactory.Instance.CreateConsole (!pauseExternalConsole, monitor.CancellationToken); oper = Runtime.ProcessService.StartConsoleProcess (cmd.Command, cmd.Arguments, cmd.WorkingDirectory, console, null); } else { oper = Runtime.ProcessService.StartProcess (cmd.Command, cmd.Arguments, cmd.WorkingDirectory, monitor.Log, monitor.Log, null, false).ProcessAsyncOperation; } } using (var stopper = monitor.CancellationToken.Register (oper.Cancel)) { await oper.Task; } if (oper.ExitCode != 0) { monitor.ReportError (GettextCatalog.GetString ("Custom command failed (exit code: {0})", oper.ExitCode), null); } } catch (Win32Exception w32ex) { monitor.ReportError (GettextCatalog.GetString ("Failed to execute custom command '{0}': {1}", cmd.Command, w32ex.Message), null); return false; } catch (Exception ex) { LoggingService.LogError ("Command execution failed", ex); throw new UserException (GettextCatalog.GetString ("Command execution failed: {0}", ex.Message)); } finally { result = oper != null && oper.ExitCode == 0; if (console != null) { console.Dispose (); } } return result; } } }