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