context: // NAnt - A .NET build tool
  // Copyright (C) 2001-2003 Gerry Shaw
  //
  // This program is free software; you can redistribute it and/or modify
  // it under the terms of the GNU General Public License as published by
  // the Free Software Foundation; either version 2 of the License, or
  // (at your option) any later version.
  //
  // This program is distributed in the hope that it will be useful,
  // but WITHOUT ANY WARRANTY; without even the implied warranty of
  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  // GNU General Public License for more details.
  //
  // You should have received a copy of the GNU General Public License
  // along with this program; if not, write to the Free Software
  // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
  //
  // Gerry Shaw (gerry_shaw@yahoo.com)
  // Scott Hernandez (ScottHernandez@hotmail.com)
  // Gert Driesen (drieseng@users.sourceforge.net)
  
  using System;
  using System.Collections;
  using System.Collections.Specialized;
  using System.Diagnostics;
  using System.Globalization;
  using System.IO;
  using System.Text;
  using System.Threading;
  using System.Xml;
  
  using NAnt.Core.Attributes;
  using NAnt.Core.Configuration;
  using NAnt.Core.Types;
  using NAnt.Core.Util;
  
  namespace NAnt.Core.Tasks {
      /// <summary>
      /// Provides the abstract base class for tasks that execute external applications.
      /// </summary>
      /// <remarks>
      ///   <para>
      ///   When a <see cref="ProgramLocationAttribute" /> is applied to the
      ///   deriving class and <see cref="ExeName" /> does not return an
      ///   absolute path, then the program to execute will first be searched for
      ///   in the location specified by <see cref="ProgramLocationAttribute.LocationType" />.
      ///   </para>
      ///   <para>
      ///   If the program does not exist in that location, then the list of tool
      ///   paths of the current target framework will be scanned in the order in
      ///   which they are defined in the NAnt configuration file.
      ///   </para>
      /// </remarks>
      [Serializable()]
      public abstract class ExternalProgramBase : Task {
          #region Private Instance Fields
          
          private StreamReader _stdError;
          private StreamReader _stdOut;
          private ArgumentCollection _arguments = new ArgumentCollection();
          private ManagedExecution _managed = ManagedExecution.Default;
          private string _exeName;
          private int _timeout = Int32.MaxValue;
          private TextWriter _outputWriter;
          private TextWriter _errorWriter;
          private int _exitCode = UnknownExitCode;
          private bool _spawn;
          private int _processId = 0;
          private bool _useRuntimeEngine;
  
          #endregion Private Instance Fields
  
          #region Public Static Fields
  
          /// <summary>
          /// Defines the exit code that will be returned by <see cref="ExitCode" />
          /// if the process could not be started, or did not exit (in time).
          /// </summary>
          public const int UnknownExitCode = -1000;
  
          #endregion Public Static Fields
  
          #region Private Static Fields
  
          private static readonly log4net.ILog logger = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);
  
          /// <summary>
          /// Will be used to ensure thread-safe operations.
          /// </summary>
          private static object _lockObject = new object();
  
          #endregion Private Static Fields
  
          #region Public Instance Properties
  
          /// <summary>
          /// The name of the executable that should be used to launch the 
          /// external program.
          /// </summary>
          /// <value>
          /// The name of the executable that should be used to launch the external
          /// program, or <see langword="null" /> if no name is specified.
          /// </value>
          /// <remarks>
          /// If available, the configured value in the NAnt configuration
          /// file will be used if no name is specified.
          /// </remarks>
          [FrameworkConfigurable("exename")]
          public virtual string ExeName {
              get { return (_exeName != null) ? _exeName : Name; }
              set { _exeName = value; }
          }
  
          /// <summary>
          /// Gets the filename of the external program to start.
          /// </summary>
          /// <value>
          /// The filename of the external program.
          /// </value>
          /// <remarks>
          /// Override in derived classes to explicitly set the location of the 
          /// external tool.
          /// </remarks>
          public virtual string ProgramFileName { 
              get { return DetermineFilePath(); }
          }
  
          /// <summary>
          /// Gets the command-line arguments for the external program.
          /// </summary>
          /// <value>
          /// The command-line arguments for the external program.
          /// </value>
          public abstract string ProgramArguments { get; }
  
          /// <summary>
          /// Gets the file to which the standard output should be redirected.
          /// </summary>
          /// <value>
          /// The file to which the standard output should be redirected, or 
          /// <see langword="null" /> if the standard output should not be
          /// redirected.
          /// </value>
          /// <remarks>
          /// The default implementation will never allow the standard output
          /// to be redirected to a file.  Deriving classes should override this 
          /// property to change this behaviour.
          /// </remarks>
          public virtual FileInfo Output {
              get { return null; } 
              set {} //so that it can be overriden.
          }
          
          /// <summary>
          /// Gets a value indicating whether output will be appended to the 
          /// <see cref="Output" />.
          /// </summary>
          /// <value>
          /// <see langword="true" /> if output should be appended to the <see cref="Output" />; 
          /// otherwise, <see langword="false" />.
          /// </value>
          public virtual bool OutputAppend {
              get { return false; } 
              set {} //so that it can be overriden.
          }
        
          /// <summary>
          /// Gets the working directory for the application.
          /// </summary>
          /// <value>
          /// The working directory for the application.
          /// </value>
          public virtual DirectoryInfo BaseDirectory {
              get { return new DirectoryInfo(Project.BaseDirectory); }
              set {} // so that it can be overriden.
          }
  
          /// <summary>
          /// The maximum amount of time the application is allowed to execute, 
          /// expressed in milliseconds.  Defaults to no time-out.
          /// </summary>
          [TaskAttribute("timeout")]
          [Int32Validator()]
          public int TimeOut {
              get { return _timeout; }
              set { _timeout = value; }
          }
  
          /// <summary>
          /// The command-line arguments for the external program.
          /// </summary>
          [BuildElementArray("arg")]
          public virtual ArgumentCollection Arguments {
              get { return _arguments; }
          }
  
          /// <summary>
          /// Specifies whether the external program is a managed application
          /// which should be executed using a runtime engine, if configured. 
          /// The default is <see langword="false" />.
          /// </summary>
          /// <value>
          /// <see langword="true" /> if the external program should be executed 
          /// using a runtime engine; otherwise, <see langword="false" />.
          /// </value>
          /// <remarks>
          ///   <para>
          ///   The value of <see cref="UseRuntimeEngine" /> is only used from
          ///   <see cref="Managed" />, and then only if its value is set to
          ///   <see cref="ManagedExecution.Default" />. In which case
          ///   <see cref="Managed" /> returns <see cref="ManagedExecution.Auto" />
          ///   if <see cref="UseRuntimeEngine" /> is <see langword="true" />.
          ///   </para>
          ///   <para>
          ///   In all other cases, the value of <see cref="UseRuntimeEngine" />
          ///   is ignored.
          ///   </para>
          /// </remarks>
          [FrameworkConfigurable("useruntimeengine")]
          [Obsolete("Use the managed attribute and Managed property instead.", false)]
          public virtual bool UseRuntimeEngine {
              get { return _useRuntimeEngine; }
              set { _useRuntimeEngine = value; }
          }
  
          /// <summary>
          /// Specifies whether the external program should be treated as a managed
          /// application, possibly forcing it to be executed under the currently
          /// targeted version of the CLR.
          /// </summary>
          /// <value>
          /// A <see cref="ManagedExecution" /> indicating how the program should
          /// be treated.
          /// </value>
          /// <remarks>
          ///   <para>
          ///   If <see cref="Managed" /> is set to <see cref="ManagedExecution.Default" />,
          ///   which is the default value, and <see cref="UseRuntimeEngine" /> is
          ///   <see langword="true" /> then <see cref="ManagedExecution.Auto" />
          ///   is returned.
          ///   </para>
          ///   <para>
          ///   When the changing <see cref="Managed" /> to <see cref="ManagedExecution.Default" />,
          ///   then <see cref="UseRuntimeEngine" /> is set to <see langword="false" />;
          ///   otherwise, it is changed to <see langword="true" />.
          ///   </para>
          /// </remarks>
          [FrameworkConfigurable("managed")]
          public virtual ManagedExecution Managed {
              get {
                  // deal with cases where UseRuntimeEngine is overridden to
                  // return true by default
                  if (UseRuntimeEngine && _managed == ManagedExecution.Default) {
                      return ManagedExecution.Auto;
                  }
  
                  return _managed;
              }
              set {
                  _managed = value;
                  UseRuntimeEngine = (value != ManagedExecution.Default);
              }
          }
  
          /// <summary>
          /// Gets or sets the <see cref="TextWriter" /> to which standard output
          /// messages of the external program will be written.
          /// </summary>
          /// <value>
          /// The <see cref="TextWriter" /> to which standard output messages of 
          /// the external program will be written.
          /// </value>
          /// <remarks>
          /// By default, standard output messages wil be written to the build log
          /// with level <see cref="Level.Info" />.
          /// </remarks>
          public virtual TextWriter OutputWriter {
              get { 
                  if (_outputWriter == null) {
                      _outputWriter = new LogWriter(this, Level.Info, 
                          CultureInfo.InvariantCulture);
                  }
                  return _outputWriter;
              }
              set { _outputWriter = value; }
          }
  
          /// <summary>
          /// Gets or sets the <see cref="TextWriter" /> to which error output
          /// of the external program will be written.
          /// </summary>
          /// <value>
          /// The <see cref="TextWriter" /> to which error output of the external 
          /// program will be written.
          /// </value>
          /// <remarks>
          /// By default, error output wil be written to the build log with level 
          /// <see cref="Level.Warning" />.
          /// </remarks>
          public virtual TextWriter ErrorWriter {
              get { 
                  if (_errorWriter == null) {
                      _errorWriter = new LogWriter(this, Level.Warning, 
                          CultureInfo.InvariantCulture);
                  }
                  return _errorWriter;
              }
              set { _errorWriter = value; }
          }
  
          /// <summary>
          /// Gets the value that the process specified when it terminated.
          /// </summary>
          /// <value>
          /// The code that the associated process specified when it terminated, 
          /// or <c>-1000</c> if the process could not be started or did not 
          /// exit (in time).
          /// </value>
          public int ExitCode {
              get { return _exitCode; }
          }
  
          /// <summary>
          /// Gets the unique identifier for the spawned application.
          /// </summary>
          protected int ProcessId {
              get {
                  if (!Spawn) {
                      throw new InvalidOperationException ("The unique identifier" +
                          " only applies to spawned applications.");
                  }
                  if (_processId == 0) {
                      throw new InvalidOperationException ("The application was not started.");
                  }
                  return _processId;
              }
          }
  
          /// <summary>
          /// Gets or sets a value indicating whether the application should be
          /// spawned. If you spawn an application, its output will not be logged
          /// by NAnt. The default is <see langword="false" />.
          /// </summary>
          public virtual bool Spawn {
              get { return _spawn; }
              set { _spawn = value; }
          }
  
          #endregion Public Instance Properties
  
          #region Override implementation of Task
  
          /// <summary>
          /// Starts the external process and captures its output.
          /// </summary>
          /// <exception cref="BuildException">
          ///   <para>The external process did not finish within the configured timeout.</para>
          ///   <para>-or-</para>
          ///   <para>The exit code of the external process indicates a failure.</para>
          /// </exception>
          protected override void ExecuteTask() {
              Thread outputThread = null;
              Thread errorThread = null;
  
              try {
                  // Start the external process
                  Process process = StartProcess();
  
                  if (Spawn) {
                      _processId = process.Id;
                      return;
                  }
  
                  outputThread = new Thread(new ThreadStart(StreamReaderThread_Output));
                  errorThread = new Thread(new ThreadStart(StreamReaderThread_Error));
  
                  _stdOut = process.StandardOutput;
                  _stdError = process.StandardError;
  
                  outputThread.Start();
                  errorThread.Start();
  
                  // Wait for the process to terminate
                  process.WaitForExit(TimeOut);
  
                  // Wait for the threads to terminate
                  outputThread.Join(2000);
                  errorThread.Join(2000); 
  
                  if (!process.HasExited) {
                      try {
                          process.Kill();
                      } catch {
                          // ignore possible exceptions that are thrown when the
                          // process is terminated
                      }
  
                      throw new BuildException(
                          String.Format(CultureInfo.InvariantCulture, 
                          ResourceUtils.GetString("NA1118"), 
                          ProgramFileName, 
                          TimeOut), 
                          Location);
                  }
  
                  _exitCode = process.ExitCode;
  
  
gt:                 if (process.ExitCode != 0) {