// Copyright (c) 2005-2009, Stefan Bodewig // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are // met: // // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // // * Redistributions in binary form must reproduce the above // copyright notice, this list of conditions and the following // disclaimer in the documentation and/or other materials provided // with the distribution. // // * The name Stefan Bodewig must not be used to endorse or // promote products derived from this software without specific // prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. using System; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Text; #if MSBUILD using Microsoft.Build.Framework; #endif #if NANT using NAnt.Core; using NAnt.Core.Attributes; #endif namespace de.samaflost.AntTask { public enum BuildTool { NAnt, MSBuild } /// /// Base class for NAnt and MSBuild tasks running Ant . /// This is the NAnt version of the task at the same time. /// #if NANT [TaskName("ant")] public class BaseAnt : NAnt.Core.Task #else public class BaseAnt #endif { #region constructors public BaseAnt() : this(BuildTool.NAnt) { } protected BaseAnt(BuildTool t) { currentBuildTool = t; } #endregion #region task attributes #region the build file private const string DEFAULT_BUILD_FILE = "build.xml"; private string buildFile = DEFAULT_BUILD_FILE; /// /// The build file - defaults to build.xml. /// #if NANT [TaskAttribute("buildFile")] #endif public string BuildFile { get { return buildFile; } set { buildFile = value; } } #endregion #region targets to execute private readonly List targetNames = new List(); /// /// The target(s) to execute /// /// This setter will only be used by MSBuild public string AntTargets { get { return string.Join(",", targetNames.ToArray()); } set { foreach (string t in value.Split(',')) { targetNames.Add(t); } } } #if NANT /// /// The target(s) to execute /// [BuildElementArray("antTarget")] public AntTarget[] AntTargetElements { set { foreach (AntTarget t in value) { targetNames.Add(t.TargetName); } } get { AntTarget[] r = new AntTarget[targetNames.Count]; for (int i = 0; i < r.Length; i++) { r[i] = new AntTarget(); r[i].TargetName = targetNames[i]; } return r; } } #endif #endregion #region properties to set private readonly IDictionary propertyTable = new Dictionary(); /// /// The properties(s) to define /// /// This setter will only be used by MSBuild public string AntProperties { set { foreach (string pair in value.Split(';')) { string[] p = pair.Split('='); if (p.Length == 2) { propertyTable[p[0]] = p[1]; } else { preConditionsFailed = true; Warn(string.Format("Property {0} is not in the correct format", p)); } } } get { StringBuilder sb = new StringBuilder(); foreach (string p in propertyTable.Keys) { if (sb.Length > 0) { sb.Append(';'); } sb.Append(p).Append('=').Append(propertyTable[p]); } return sb.ToString(); } } #if NANT /// /// The properties(s) to define /// [BuildElementArray("antProperty")] public AntProperty[] AntPropertyElements { set { foreach (AntProperty p in value) { propertyTable[p.PropertyName] = p.Value; } } get { AntProperty[] p = new AntProperty[propertyTable.Count]; int i = 0; foreach (string key in propertyTable.Keys) { p[i] = new AntProperty(); p[i].PropertyName = key; p[i++].Value = propertyTable[key]; } return p; } } #endif #endregion #region -lib path private string libPath; /// /// The path to use for the -lib argument /// #if NANT [TaskAttribute("libPath")] #endif public string LibPath { get { return libPath; } set { libPath = value; } } #endregion #region ANT_HOME private string antHome; /// /// Where to find Ant. /// #if NANT [TaskAttribute("antHome")] #endif public string AntHome { get { return antHome ?? Environment.GetEnvironmentVariable("ANT_HOME"); } set { antHome = value; } } #endregion #region JAVA_HOME private string javaHome; /// /// Where to find Java. /// #if NANT [TaskAttribute("javaHome")] #endif public string JavaHome { get { return javaHome ?? Environment.GetEnvironmentVariable("JAVA_HOME"); } set { javaHome = value; } } #endregion #endregion #region personality checks private readonly BuildTool currentBuildTool; protected bool RunningInMSBuild { get { return currentBuildTool == BuildTool.MSBuild; } } protected bool RunningInNAnt { get { return currentBuildTool == BuildTool.NAnt; } } #endregion #region Logging protected void Debug(string p) { #if MSBUILD if (RunningInMSBuild) { GenericMSBuildLog(p, MessageImportance.Low); } #endif #if NANT if (RunningInNAnt) { GenericNAntLog(p, Level.Debug); } #endif } protected void Log(string p) { #if MSBUILD if (RunningInMSBuild) { GenericMSBuildLog(p, MessageImportance.Normal); } #endif #if NANT if (RunningInNAnt) { GenericNAntLog(p, Level.Info); } #endif } protected void Warn(string p) { #if MSBUILD if (RunningInMSBuild) { GenericMSBuildLog(p, MessageImportance.High); } #endif #if NANT if (RunningInNAnt) { GenericNAntLog(p, Level.Error); } #endif } #endregion #region Build Tool specific stuff #if MSBUILD #region MSBuild ITask Members private IBuildEngine buildEngine; public IBuildEngine BuildEngine { get { return buildEngine; } set { buildEngine = value; } } private ITaskHost hostObject; public ITaskHost HostObject { get { return hostObject; } set { hostObject = value; } } #endregion #region support stuff private static readonly string HELP_KEYWORD = string.Empty; private const string MESSAGE_SENDER = "ant task"; private void GenericMSBuildLog(string message, MessageImportance i) { BuildEngine.LogMessageEvent(new BuildMessageEventArgs(message, HELP_KEYWORD, MESSAGE_SENDER, i)); } #endregion #endif #if NANT #region NAnt Task abstract method protected override void ExecuteTask() { if (!RunAnt()) { throw new BuildException("ant task failed"); } } #endregion #region support stuff private void GenericNAntLog(string message, Level l) { Log(l, message); } #endregion #endif #endregion #region do the real work private bool preConditionsFailed = false; protected bool RunAnt() { if (preConditionsFailed) { return false; } if (AntHome == null) { Warn(string.Format("Can't determine ANT_HOME, please set the {0} attribute or the ANT_HOME environment variable", RunningInMSBuild ? "AntHome" : "antHome")); return false; } FileInfo antLauncherJar = new FileInfo(Path.Combine(Path.Combine(AntHome, "lib"), "ant-launcher.jar")); if (!antLauncherJar.Exists) { Warn(string.Format("'{0}' calculated from ANT_HOME '{1}' doesn't exist", antLauncherJar.FullName, AntHome)); return false; } string javaExecutable = LocateJava(); return Run(javaExecutable, antLauncherJar.FullName) == 0; } /// /// tries to locate the Java executable, replicates quite a bit of Ant's wrapper script logic /// /// the absolute path of the Java executable, if found, "java" or "java.exe" otherwise private string LocateJava() { bool isDos = Path.PathSeparator == ';'; string java = null; if (JavaHome != null) { if (!isDos) { // AIX likes to hide IBM's JDK in strange places FileInfo fi = new FileInfo(Path.Combine(Path.Combine(Path.Combine(JavaHome, "jre"), "sh"), "java")); if (fi.Exists) { java = fi.FullName; } else { fi = new FileInfo(Path.Combine(Path.Combine(JavaHome, "bin"), "java")); if (fi.Exists) { java = fi.FullName; } } } else { FileInfo fi = new FileInfo(Path.Combine(Path.Combine(JavaHome, "bin"), "java.exe")); if (fi.Exists) { java = fi.FullName; } } if (java == null) { Warn(string.Format("Couldn't locate java in '{0}', will rely on your PATH environment variable", JavaHome)); } } else { Log("Can't determine JAVA_HOME, will rely on your PATH environment variable"); } if (java == null) { java = "java" + (isDos ? ".exe" : ""); } return java; } private int Run(string java, string antJar) { try { ProcessStartInfo pi = new ProcessStartInfo(java); StringBuilder sb = new StringBuilder("-jar "); sb.Append(MaybeQuote(antJar)); if (LibPath != null) { sb.Append(" -lib ").Append(MaybeQuote(LibPath)); } sb.Append(" -buildfile ").Append(MaybeQuote(BuildFile)); foreach (string key in propertyTable.Keys) { sb.Append(" ").Append(MaybeQuote(string.Format("-D{0}={1}", key, propertyTable[key]))); } foreach (string target in targetNames) { sb.Append(" ").Append(MaybeQuote(target)); } pi.Arguments = sb.ToString(); pi.UseShellExecute = false; pi.RedirectStandardOutput = true; Debug(string.Format("running {0} with args {1}", java, pi.Arguments)); using (Process p = Process.Start(pi)) { Log(p.StandardOutput.ReadToEnd()); p.WaitForExit(); return p.ExitCode; } } catch (Exception e) { Warn(string.Format("Ant execution failed because of: '{0}'", e.ToString())); return -1; } } private static readonly char[] BAD_CHARS = new char[] { ' ', '\t', '\"' }; /// /// puts quote around arg if arg contains whitespace /// /// command line argument /// arg or "arg" private string MaybeQuote(string arg) { if (arg.IndexOfAny(BAD_CHARS) >= 0) { if (arg.IndexOf('"') >= 0) { return "'" + arg + "'"; } else { return "\"" + arg + "\""; } } else { return arg; } } #endregion } #if MSBUILD /// /// MSBuild task to run Ant. /// public class Ant : BaseAnt, ITask { public Ant() : base(BuildTool.MSBuild) { } public new bool Execute() { return RunAnt(); } } #endif #if NANT /// /// Nested element that specifies targets in the NAnt task running Ant. /// [ElementName("antTarget")] public class AntTarget : Element { private string name; /// /// target name. /// [TaskAttribute("name")] public string TargetName { get { return name; } set { name = value; } } } /// /// Nested element that specifies properties in the NAnt task running Ant. /// [ElementName("antProperty")] public class AntProperty : Element { private string name; /// /// property name. /// [TaskAttribute("name")] public string PropertyName { get { return name; } set { name = value; } } private string value; /// /// property value /// [TaskAttribute("value")] public string Value { get { return value; } set { this.value = value; } } } #endif }