// NAnt - A .NET build tool // Copyright (C) 2001-2004 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 // // Matthew Mastracci (mmastrac at users.sourceforge.net) // Gert Driesen (gert.driesen@ardatis.com) using System; using System.CodeDom.Compiler; using System.Collections.Specialized; using System.Globalization; using System.Reflection; using System.IO; using Microsoft.Win32; using NAnt.Core; using NAnt.Core.Attributes; using NAnt.Core.Tasks; using NAnt.Core.Types; using NAnt.Core.Util; using NAnt.VSNet.Types; namespace NAnt.VSNet.Tasks { /// /// Compiles VS.NET solutions (or sets of projects), automatically determining /// project dependencies from inter-project references. /// /// /// /// This task support the following projects: /// /// /// /// Visual Basic .NET /// /// /// Visual C# .NET /// /// /// Visual J# .NET /// /// /// Visual C++ .NET /// /// /// /// Right now, only Microsoft Visual Studio .NET 2002 and 2003 solutions /// and projects are supported. Support for .NET Compact Framework projects /// is also not available at this time. /// /// /// The also supports the model of referencing /// projects by their output filenames, rather than referencing them inside /// the solution. It will automatically detect the existance of a file /// reference and convert it to a project reference. For example, if project /// "A" references the file in the release output directory of /// project "B", the will automatically /// convert this to a project dependency on project "B" and will /// reference the appropriate configuration output directory at the final /// build time (ie: reference the debug version of "B" if the /// solution is built as debug). /// /// /// The expects all project files to be valid /// XML files. /// ///

Resx Files

/// /// When building a project for a down-level target framework, special care /// should be given to resx files. Resx files (can) contain references to /// a specific version of CLR types, and as such are only upward compatible. /// /// /// For example: if you want to be able to build a project both as a .NET 1.0 /// and .NET 1.1 assembly, the resx files should only contain references to /// .NET 1.0 CLR types. Failure to do this may result in a /// failure at runtime on machines with only the .NET Framework 1.0 installed. /// ///
/// /// /// Compiles all of the projects in test.sln, in release mode, in /// the proper order. /// /// /// /// ]]> /// /// /// /// /// Compiles all of the projects in projects.txt, in the proper /// order. /// /// /// /// /// /// /// /// ]]> /// /// /// /// /// Compiles projects A, B and C, using the output of project X as a /// reference. /// /// /// /// /// /// /// /// /// /// /// /// /// ]]> /// /// /// /// /// Compiles all of the projects in the solution except for project A. /// /// /// /// /// /// /// /// ]]> /// /// /// /// /// Compiles all of the projects in the solution mapping the specific project at /// http://localhost/A/A.csproj to c:\inetpub\wwwroot\A\A.csproj and any URLs under /// http://localhost/B/[remainder] to c:\other\B\[remainder]. This allows the build /// to work without WebDAV. /// /// /// /// /// /// /// /// /// ]]> /// /// /// /// /// Compiles all of the projects in the solution placing compiled outputs /// in c:\temp. /// /// /// ]]> /// /// [Serializable()] [TaskName("solution")] public class SolutionTask : Task { #region Public Instance Constructors /// /// Initializes a new instance of the class. /// public SolutionTask() { _projects = new FileSet(); _referenceProjects = new FileSet(); _excludeProjects = new FileSet(); _assemblyFolders = new FileSet(); _webMaps = new WebMapCollection(); } #endregion Public Instance Constructors #region Public Instance Properties /// /// The projects to build. /// [BuildElement("projects", Required=false)] public FileSet Projects { get { return _projects; } set { _projects = value; } } /// /// The projects to scan, but not build. /// /// /// These projects are used to resolve project references and are /// generally external to the solution being built. References to /// these project's output files are converted to use the appropriate /// solution configuration at build time. /// [BuildElement("referenceprojects", Required=false)] public FileSet ReferenceProjects { get { return _referenceProjects; } set { _referenceProjects = value; } } /// /// The name of the VS.NET solution file to build. /// /// /// /// The can be used instead to supply a list /// of Visual Studio.NET projects that should be built. /// /// [TaskAttribute("solutionfile", Required=false)] public FileInfo SolutionFile { get { return _solutionFile; } set { _solutionFile = value; } } /// /// The name of the solution configuration to build. /// /// /// /// Generally release or debug. Not case-sensitive. /// /// [TaskAttribute("configuration", Required=true)] [StringValidator(AllowEmpty=false)] public string Configuration { get { return _configuration; } set { _configuration = StringUtils.ConvertEmptyToNull(value); } } /// /// The directory where compiled targets will be placed. This /// overrides path settings contained in the solution/project. /// [TaskAttribute("outputdir", Required=false)] public DirectoryInfo OutputDir { get { return _outputDir; } set { _outputDir = value; } } /// /// WebMap of URL's to project references. /// [BuildElementCollection("webmap", "map")] public WebMapCollection WebMaps { get { return _webMaps; } } /// /// Fileset of projects to exclude. /// [BuildElement("excludeprojects", Required=false)] public FileSet ExcludeProjects { get { return _excludeProjects; } set { _excludeProjects = value; } } /// /// Set of folders where references are searched when not found in path /// from project file (HintPath). /// [BuildElement("assemblyfolders", Required=false)] public FileSet AssemblyFolders { get { return _assemblyFolders; } set { _assemblyFolders = value; } } /// /// Includes Visual Studio search folders in reference search path. /// The default is . /// [TaskAttribute("includevsfolders")] [BooleanValidator()] public bool IncludeVSFolders { get { return _includeVSFolders; } set { _includeVSFolders = value; } } /// /// Allow the task to use WebDAV for retrieving/compiling the projects within solution. Use of /// is preferred over WebDAV. The default is . /// /// /// WebDAV support requires permission changes to be made on your project server. These changes may affect /// the security of the server and should not be applied to a public installation. /// Consult your web server or the NAnt Wiki documentation for more information. /// [TaskAttribute("enablewebdav", Required = false)] [BooleanValidator()] public bool EnableWebDav { get { return _enableWebDav; } set { _enableWebDav = value; } } /// /// Gets the list of folders to scan for assembly references. /// /// /// The list of folders to scan for assembly references. /// public StringCollection AssemblyFolderList { get { if (_assemblyFolderList == null) { _assemblyFolderList = new StringCollection(); foreach (string folder in AssemblyFolders.DirectoryNames) { if (!_assemblyFolderList.Contains(folder)) { _assemblyFolderList.Add(folder); Log(Level.Debug, "Added \"{0}\" to AssemblyFolders.", folder); } } if (IncludeVSFolders) { StringCollection vsAssemblyFolders = BuildAssemblyFolders(); foreach (string folder in vsAssemblyFolders) { if (!_assemblyFolderList.Contains(folder)) { _assemblyFolderList.Add(folder); Log(Level.Debug, "Added \"{0}\" to AssemblyFolders.", folder); } } } } return _assemblyFolderList; } } #endregion Public Instance Properties #region Override implementation of Task protected override void ExecuteTask() { Log(Level.Info, "Starting solution build."); if (SolutionFile != null) { if (!SolutionFile.Exists) { throw new BuildException(string.Format(CultureInfo.InvariantCulture, "Couldn't find solution file '{0}'.", SolutionFile.FullName), Location); } } if (Projects.FileNames.Count > 0) { Log(Level.Verbose, "Included projects:" ); foreach (string projectFile in Projects.FileNames) { Log(Level.Verbose, " - " + projectFile); } } if (ReferenceProjects.FileNames.Count > 0) { Log(Level.Verbose, "Reference projects:"); foreach (string projectFile in ReferenceProjects.FileNames) { Log(Level.Verbose, " - " + projectFile); } } string basePath = null; try { using (TempFileCollection tfc = new TempFileCollection()) { // store the temp dir so we can clean it up later basePath = tfc.BasePath; // ensure temp directory exists if (!Directory.Exists(tfc.BasePath)) { Directory.CreateDirectory(tfc.BasePath); } // create temporary domain AppDomain temporaryDomain = AppDomain.CreateDomain("temporaryDomain", AppDomain.CurrentDomain.Evidence, AppDomain.CurrentDomain.SetupInformation); try { ReferencesResolver referencesResolver = ((ReferencesResolver) temporaryDomain.CreateInstanceFrom(Assembly.GetExecutingAssembly().Location, typeof(ReferencesResolver).FullName).Unwrap()); using (GacCache gacCache = new GacCache(this.Project)) { SolutionBase sln = SolutionFactory.LoadSolution(this, tfc, gacCache, referencesResolver); if (!sln.Compile(Configuration)) { throw new BuildException("Project build failed.", Location); } } } finally { // unload temporary domain AppDomain.Unload(temporaryDomain); } } } finally { if (basePath != null && Directory.Exists(basePath)) { Log(Level.Debug, "Cleaning up temp folder '{0}'.", basePath); // delete temporary directory and all files in it DeleteTask deleteTask = new DeleteTask(); deleteTask.Project = Project; deleteTask.Parent = this; deleteTask.InitializeTaskConfiguration(); deleteTask.Directory = new DirectoryInfo(basePath); deleteTask.Threshold = Level.None; // no output in build log deleteTask.Execute(); } } } #endregion Override implementation of Task #region Internal Instance Methods /// /// Expands the given macro. /// /// The macro to expand. /// /// The expanded macro or if the macro is not /// supported. /// /// The macro cannot be expanded. internal string ExpandMacro(string macro) { // perform case-insensitive expansion of macros switch (macro.ToLower(CultureInfo.InvariantCulture)) { case "solutionfilename": // E.g. WindowsApplication1.sln if (SolutionFile != null) { return Path.GetFileName(SolutionFile.FullName); } else { throw new BuildException(string.Format(CultureInfo.InvariantCulture, "Macro \"{0}\" cannot be expanded, no solution file specified.", macro), Location.UnknownLocation); } case "solutionpath": // Absolute path for SolutionFileName if (SolutionFile != null) { return SolutionFile.FullName; } else { throw new BuildException(string.Format(CultureInfo.InvariantCulture, "Macro \"{0}\" cannot be expanded, no solution file specified.", macro), Location.UnknownLocation); } case "solutiondir": // SolutionPath without SolutionFileName appended if (SolutionFile != null) { return Path.GetDirectoryName(SolutionFile.FullName) + Path.DirectorySeparatorChar; } else { throw new BuildException(string.Format(CultureInfo.InvariantCulture, "Macro \"{0}\" cannot be expanded, no solution file specified.", macro), Location.UnknownLocation); } case "solutionname": // E.g. WindowsApplication1 if (SolutionFile != null) { return Path.GetFileNameWithoutExtension( SolutionFile.FullName); } else { throw new BuildException(string.Format(CultureInfo.InvariantCulture, "Macro \"{0}\" cannot be expanded, no solution file specified.", macro), Location.UnknownLocation); } case "solutionext": // Is this ever anything but .sln? if (SolutionFile != null) { return Path.GetExtension(SolutionFile.FullName); } else { throw new BuildException(string.Format(CultureInfo.InvariantCulture, "Macro \"{0}\" cannot be expanded, no solution file specified.", macro), Location.UnknownLocation); } default: return null; } } #endregion Internal Instance Methods #region Private Instance Methods /// /// Builds the list of folders that should be scanned for assembly /// references. /// /// /// The list of folders that should be scanned for assembly references. /// private StringCollection BuildAssemblyFolders() { StringCollection folderList = new StringCollection(); // determine version of Visual Studio .NET corresponding with // current target framework Version visualStudioVersion = Project.TargetFramework.VisualStudioVersion; // check HKCU BuildVisualStudioAssemblyFolders(folderList, Registry.CurrentUser, visualStudioVersion.ToString(2)); // check HKLM BuildVisualStudioAssemblyFolders(folderList, Registry.LocalMachine, visualStudioVersion.ToString(2)); // check HKCU for .NET Framework AssemblyFolders BuildDotNetAssemblyFolders(folderList, Registry.CurrentUser); // check HKLM for .NET Framework AssemblyFolders BuildDotNetAssemblyFolders(folderList, Registry.LocalMachine); return folderList; } private void BuildVisualStudioAssemblyFolders(StringCollection folderList, RegistryKey hive, string visualStudioVersion) { RegistryKey assemblyFolders = hive.OpenSubKey(@"SOFTWARE\Microsoft\VisualStudio\" + visualStudioVersion + @"\AssemblyFolders"); if (assemblyFolders == null) { return; } string[] subKeyNames = assemblyFolders.GetSubKeyNames(); foreach (string subKeyName in subKeyNames) { RegistryKey subKey = assemblyFolders.OpenSubKey(subKeyName); string folder = subKey.GetValue(string.Empty) as string; if (folder != null && !folderList.Contains(folder)) { folderList.Add(folder); } } } private void BuildDotNetAssemblyFolders(StringCollection folderList, RegistryKey hive) { RegistryKey assemblyFolders = hive.OpenSubKey(@"SOFTWARE\Microsoft\" + @".NETFramework\AssemblyFolders"); if (assemblyFolders == null) { return; } string[] subKeyNames = assemblyFolders.GetSubKeyNames(); foreach (string subKeyName in subKeyNames) { RegistryKey subKey = assemblyFolders.OpenSubKey(subKeyName); string folder = subKey.GetValue(string.Empty) as string; if (folder != null && !folderList.Contains(folder)) { folderList.Add(folder); } } } #endregion Private Instance Methods #region Private Instance Fields private FileInfo _solutionFile; private string _configuration; private DirectoryInfo _outputDir; private FileSet _projects; private FileSet _referenceProjects; private FileSet _excludeProjects; private FileSet _assemblyFolders; private StringCollection _assemblyFolderList; private WebMapCollection _webMaps; private bool _includeVSFolders = true; private bool _enableWebDav; #endregion Private Instance Fields } }