// 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
}
}