// 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 // original author unknown // Ian MacLean (ian_maclean@another.com) // Hani Atassi (haniatassi@users.sourceforge.net) using System; using System.IO; using System.Text; using NAnt.Core; using NAnt.Core.Attributes; using NAnt.Core.Tasks; using NAnt.Core.Types; using NAnt.VisualCpp.Util; namespace NAnt.VisualCpp.Tasks { /// /// This tasks allows you to run MIDL.exe. /// /// /// /// This task only supports a small subset of the MIDL.EXE command line /// switches, but you can use the options element to specify any other /// unsupported commands you want to specify. /// /// /// /// /// /// /// /// /// /// /// /// /// ]]> /// /// [TaskName("midl")] public class MidlTask : ExternalProgramBase { #region Private Instance Fields private string _responseFileName; private string _acf; private string _align; private bool _appConfig; private string _char; private string _client; private string _cstub; // TODO: /D!!!!! private FileInfo _dlldata; private string _env = "win32"; // TODO: /error private string _Oi; private FileInfo _header; private FileInfo _iid; private FileInfo _proxy; private FileInfo _tlb; private FileInfo _filename; private OptionCollection _options = new OptionCollection(); private OptionCollection _defines = new OptionCollection(); private OptionCollection _undefines = new OptionCollection(); private FileSet _includeDirs = new FileSet(); #endregion Private Instance Fields #region Private Static Fields private const string PROG_FILE_NAME = "midl.exe"; #endregion Private Static Fields #region Public Instance Properties /// /// The /acf switch allows the user to supply an /// explicit ACF file name. The switch also /// allows the use of different interface names in /// the IDL and ACF files. /// [TaskAttribute("acf")] public string Acf { get { return _acf; } set { _acf = value; } } /// /// The /align switch is functionally the same as the /// MIDL /Zp option and is recognized by the MIDL compiler /// solely for backward compatibility with MkTypLib. /// /// The alignment value can be 1, 2, 4, or 8. [TaskAttribute("align")] public string Align { get { return _align; } set { _align = value; } } /// /// The /app_config switch selects application-configuration /// mode, which allows you to use some ACF keywords in the /// IDL file. With this MIDL compiler switch, you can omit /// the ACF and specify an interface in a single IDL file. /// [TaskAttribute("app_config"), BooleanValidator()] public bool AppConfig { get { return _appConfig; } set { _appConfig = value; } } /// /// The /char switch helps to ensure that the MIDL compiler /// and C compiler operate together correctly for all char /// and small types. /// /// Can be one of signed | unsigned | ascii7 [TaskAttribute("char")] public string Char { get { return _char; } set { _char = value; } } /// /// The /client switch directs the MIDL compiler to generate /// client-side C source files for an RPC interface /// /// can be one of stub | none [TaskAttribute("client")] public string Client { get { return _client; } set { _client = value; } } /// /// The /cstub switch specifies the name of the client /// stub file for an RPC interface. /// [TaskAttribute("cstub")] public string CStub { get { return _cstub; } set { _cstub = value; } } /// /// Specifies the file name for the generated dlldata file for a proxy /// DLL. The default file name Dlldata.c is used if /// is not specified. /// [TaskAttribute("dlldata")] public FileInfo DllData { get { return _dlldata; } set { _dlldata = value; } } /// /// The /env switch selects the /// environment in which the application runs. /// /// It can take the values win32 and win64 [TaskAttribute("env")] public string Env { get { return _env; } set { _env = value; } } /// /// The /Oi switch directs the MIDL compiler to /// use a fully-interpreted marshaling method. /// The /Oic and /Oicf switches provide additional /// performance enhancements. /// /// /// If you specify the Oi attribute, you must set it to /// one of the values: /// - Oi="" /// - Oi="c" /// - Oi="f" /// - Oi="cf" /// [TaskAttribute("Oi")] public string Oi { get { return _Oi; } set { _Oi = value; } } /// /// Specifies a file name for the type library generated by the MIDL /// compiler. /// [TaskAttribute("tlb")] public FileInfo Tlb { get { return _tlb; } set { _tlb = value; } } /// /// Specifies the name of the header file. /// [TaskAttribute("header")] public FileInfo Header { get { return _header; } set { _header = value; } } /// /// Specifies the name of the interface identifier file for a COM /// interface, overriding the default name obtained by adding _i.c /// to the IDL file name. /// [TaskAttribute("iid")] public FileInfo Iid { get { return _iid; } set { _iid = value; } } /// /// Specifies the name of the interface proxy file for a COM interface. /// [TaskAttribute("proxy")] public FileInfo Proxy { get { return _proxy; } set { _proxy = value; } } /// /// Name of .IDL file to process. /// [TaskAttribute("filename", Required=true)] public FileInfo Filename { get { return _filename; } set { _filename = value; } } /// /// Additional options to pass to midl.exe. /// [BuildElementCollection("options", "option")] public OptionCollection Options { get { return _options; } } /// /// Macro definitions to pass to mdil.exe. /// Each entry will generate a /D /// [BuildElementCollection("defines", "define")] public OptionCollection Defines { get { return _defines; } } /// /// Macro undefines (/U) to pass to mdil. /// [BuildElementCollection("undefines", "undefine")] public OptionCollection Undefines { get { return _undefines; } } /// /// The list of directories in which to search for include files. /// [BuildElement("includedirs")] public FileSet IncludeDirs { get { return _includeDirs; } set { _includeDirs = value; } } #endregion Public Instance Properties #region Override implementation of ExternalProgramBase /// /// Filename of program to execute /// public override string ProgramFileName { get { return PROG_FILE_NAME; } } /// /// Gets the command-line arguments for the external program. /// /// /// The command-line arguments for the external program. /// public override string ProgramArguments { get { return "@" + "\"" + _responseFileName + "\""; } } /// /// This is where the work is done. /// protected override void ExecuteTask() { if (IncludeDirs.BaseDirectory == null) { IncludeDirs.BaseDirectory = new DirectoryInfo(Project.BaseDirectory); } if (NeedsCompiling()) { // create temp response file to hold compiler options _responseFileName = Path.GetTempFileName(); try { using (StreamWriter writer = new StreamWriter(_responseFileName)) { WriteResponseFile(writer); } if (Verbose) { // display response file contents Log(Level.Info, "Contents of " + _responseFileName); StreamReader reader = File.OpenText(_responseFileName); Log(Level.Info, reader.ReadToEnd()); reader.Close(); } base.ExecuteTask(); } finally { // make sure we delete the response file File.Delete(_responseFileName); _responseFileName = null; } } } #endregion Override implementation of ExternalProgramBase #region Private Instance Methods /// /// Check output files to see if we need rebuilding. /// /// if a rebuild is needed; otherwise, /// . private bool NeedsCompiling() { if (Tlb != null && NeedsCompiling(Tlb)) { return true; } else if (Header != null && NeedsCompiling(Header)) { return true; } else if (Iid != null && NeedsCompiling(Iid)) { return true; } // up-to-date check for proxy and dlldata should not be performed, // as, even though specified, the compiler might not output these // files (why ? no idea) return false; } /// /// Check output files to see if we need rebuilding. /// /// /// if a rebuild is needed; otherwise, /// . /// private bool NeedsCompiling(FileInfo outputFile) { if (!outputFile.Exists) { Log(Level.Verbose, "Output file '{0}' does not exist, recompiling.", outputFile.FullName); return true; } string fileName = FileSet.FindMoreRecentLastWriteTime(Filename.FullName, outputFile.LastWriteTime); if (fileName != null) { Log(Level.Verbose, "'{0}' is out of date, recompiling.", fileName); return true; } return false; } /// /// Writes the response file for midl.exe. /// private void WriteResponseFile(TextWriter writer) { // suppresses display of the sign-on banner writer.WriteLine("/nologo"); writer.WriteLine("/env " + _env); if (_acf != null) writer.WriteLine("/acf {0}", _acf); if (_align != null) writer.WriteLine("/align {0}", _align); if (_appConfig) writer.WriteLine("/app_config"); if (_char != null) writer.WriteLine("/char {0}", _char); if (_client != null) writer.WriteLine("/client {0}", _client); if (_cstub != null) writer.WriteLine("/cstub {0}", _cstub); if (_dlldata != null) { writer.WriteLine("/dlldata \"{0}\"", DllData.FullName); } if (_Oi != null) writer.WriteLine("/Oi" + _Oi); if (Tlb != null) writer.WriteLine("/tlb \"{0}\"", Tlb.FullName); if (_header != null) writer.WriteLine("/header \"{0}\"", Header.FullName); if (Iid != null) writer.WriteLine("/iid \"{0}\"", Iid.FullName); if (Proxy != null) writer.WriteLine("/proxy \"{0}\"", Proxy.FullName); foreach (Option define in Defines) { if (!define.IfDefined || define.UnlessDefined) { continue; } if (define.Value == null) { writer.WriteLine("/D " + ArgumentUtils.FixTrailingBackslash(define.OptionName)); } else { writer.WriteLine("/D " + define.OptionName + "=" + ArgumentUtils.FixTrailingBackslash(define.Value)); } } foreach (Option undefine in Undefines) { if (!undefine.IfDefined || undefine.UnlessDefined) { continue; } writer.WriteLine("/U " + ArgumentUtils.QuoteArgumentValue( undefine.OptionName, BackslashProcessingMethod.Fix)); } foreach (Option option in _options) { if (IfDefined && !UnlessDefined) { if (option.Value == null) { writer.WriteLine(option.OptionName); } else { writer.WriteLine(option.OptionName + " " + option.Value); } } } // append user provided include directories foreach (string include in IncludeDirs.DirectoryNames) { writer.WriteLine("/I {0}", ArgumentUtils.QuoteArgumentValue( include, BackslashProcessingMethod.Clean)); } writer.WriteLine("\"{0}\"", Filename.FullName); } #endregion Private Instance Methods } }