// 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
//
// Matthew Mastracci (matt@aclaro.com)
using System;
using System.CodeDom.Compiler;
using System.Collections;
using System.Collections.Specialized;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Text;
using System.Xml;
using NAnt.Core;
using NAnt.Core.Tasks;
using NAnt.Core.Types;
using NAnt.Core.Util;
using NAnt.DotNet.Tasks;
using NAnt.DotNet.Types;
using NAnt.Win32.Tasks;
using NAnt.VSNet.Tasks;
namespace NAnt.VSNet {
public abstract class ManagedProjectBase : ProjectBase {
#region Public Instance Constructors
protected ManagedProjectBase(SolutionBase solution, string projectPath, XmlElement xmlDefinition, SolutionTask solutionTask, TempFileCollection tfc, GacCache gacCache, ReferencesResolver refResolver, DirectoryInfo outputDir) : base(xmlDefinition, solutionTask, tfc, gacCache, refResolver, outputDir) {
if (projectPath == null) {
throw new ArgumentNullException("projectPath");
}
if (xmlDefinition == null) {
throw new ArgumentNullException("xmlDefinition");
}
_references = new ArrayList();
_neutralResources = new ArrayList();
_localizedResources = new ArrayList();
_sourceFiles = CollectionsUtil.CreateCaseInsensitiveHashtable();
_projectPath = projectPath;
_projectLocation = DetermineProjectLocation(xmlDefinition);
if (!IsWebProject) {
_projectDirectory = new FileInfo(projectPath).Directory;
} else {
string projectDirectory = projectPath.Replace(":", "_");
projectDirectory = projectDirectory.Replace("/", "_");
projectDirectory = projectDirectory.Replace("\\", "_");
_projectDirectory = new DirectoryInfo(FileUtils.CombinePaths(
TemporaryFiles.BasePath, projectDirectory));
// ensure project directory exists
if (!_projectDirectory.Exists) {
_projectDirectory.Create();
_projectDirectory.Refresh();
}
_webProjectBaseUrl = projectPath.Substring(0, projectPath.LastIndexOf("/"));
}
_projectSettings = new ProjectSettings(xmlDefinition, (XmlElement)
xmlDefinition.SelectSingleNode("//Build/Settings"), this);
XmlNodeList nlConfigurations = xmlDefinition.SelectNodes("//Config");
foreach (XmlElement elemConfig in nlConfigurations) {
ConfigurationSettings cs = new ConfigurationSettings(this, elemConfig, OutputDir);
ProjectConfigurations[elemConfig.Attributes["Name"].Value] = cs;
}
XmlNodeList nlReferences = xmlDefinition.SelectNodes("//References/Reference");
foreach (XmlElement elemReference in nlReferences) {
ReferenceBase reference = CreateReference(solution, elemReference);
_references.Add(reference);
}
XmlNodeList nlFiles = xmlDefinition.SelectNodes("//Files/Include/File");
foreach (XmlElement elemFile in nlFiles) {
string buildAction = StringUtils.ConvertEmptyToNull(elemFile.GetAttribute("BuildAction"));
string sourceFile;
if (!StringUtils.IsNullOrEmpty(elemFile.GetAttribute("Link"))) {
sourceFile = FileUtils.GetFullPath(FileUtils.CombinePaths(
ProjectDirectory.FullName, elemFile.GetAttribute("Link")));
} else {
sourceFile = FileUtils.GetFullPath(FileUtils.CombinePaths(
ProjectDirectory.FullName, elemFile.GetAttribute("RelPath")));
}
if (IsWebProject) {
WebDavClient wdc = new WebDavClient(new Uri(_webProjectBaseUrl));
wdc.DownloadFile(sourceFile, elemFile.Attributes["RelPath"].Value);
switch (buildAction) {
case "Compile":
_sourceFiles[sourceFile] = null;
break;
case "EmbeddedResource":
RegisterEmbeddedResource(sourceFile, elemFile);
break;
case null:
if (string.Compare(Path.GetExtension(sourceFile), FileExtension, true, CultureInfo.InvariantCulture) == 0) {
_sourceFiles[sourceFile] = null;
}
break;
}
} else {
switch (buildAction) {
case "Compile":
_sourceFiles[sourceFile] = null;
break;
case "EmbeddedResource":
RegisterEmbeddedResource(sourceFile, elemFile);
break;
case null:
if (string.Compare(Path.GetExtension(sourceFile), FileExtension, true, CultureInfo.InvariantCulture) == 0) {
_sourceFiles[sourceFile] = null;
}
break;
}
// check if file is "App.config" (using case-insensitive comparison)
if (string.Compare("App.config", elemFile.GetAttribute("RelPath"), true, CultureInfo.InvariantCulture) == 0) {
// App.config is only an output file for executable projects
if (ProjectSettings.OutputType == ManagedOutputType.Executable || ProjectSettings.OutputType == ManagedOutputType.WindowsExecutable) {
ExtraOutputFiles[sourceFile] = ProjectSettings.OutputFileName
+ ".config";
}
}
}
}
}
#endregion Public Instance Constructors
#region Public Instance Properties
public ProjectSettings ProjectSettings {
get { return _projectSettings; }
}
#endregion Public Instance Properties
#region Protected Instance Properties
///
/// Gets the default file extension of sources for this project.
///
///
/// The default file extension of sources for this project.
///
protected abstract string FileExtension {
get;
}
#endregion Protected Instance Properties
#region Private Instance Properties
///
/// Gets a value indicating if this is a web project.
///
///
/// if this is a web project; otherwise,
/// .
///
///
/// If the url of a web project has been mapped to a local path
/// (using the <webmap> element), then this property will return
/// for a
/// project.
///
private bool IsWebProject {
get { return ProjectFactory.IsUrl(_projectPath); }
}
#endregion Private Instance Properties
#region Override implementation of ProjectBase
///
/// Gets the name of the VS.NET project.
///
public override string Name {
get {
string projectPath;
if (ProjectFactory.IsUrl(_projectPath)) {
// construct uri for project path
Uri projectUri = new Uri(_projectPath);
// get last segment of the uri (which should be the
// project file itself)
projectPath = projectUri.LocalPath;
} else {
projectPath = ProjectPath;
}
// return file part without extension
return Path.GetFileNameWithoutExtension(projectPath);
}
}
///
/// Gets the path of the VS.NET project.
///
public override string ProjectPath {
get {
if (ProjectFactory.IsUrl(_projectPath)) {
return _projectPath;
} else {
return FileUtils.GetFullPath(_projectPath);
}
}
}
///
/// Gets the directory containing the VS.NET project.
///
public override DirectoryInfo ProjectDirectory {
get { return _projectDirectory; }
}
///
/// Get the location of the project.
///
public override ProjectLocation ProjectLocation {
get { return _projectLocation; }
}
///
/// Gets or sets the unique identifier of the VS.NET project.
///
public override string Guid {
get { return ProjectSettings.Guid; }
set { throw new InvalidOperationException( "It is not allowed to change the GUID of a C#/VB.NET project" ); }
}
public override ArrayList References {
get { return _references; }
}
///
/// Gets a value indicating whether building the project for the specified
/// build configuration results in managed output.
///
/// The solution configuration that is built.
///
/// .
///
public override bool IsManaged(string solutionConfiguration) {
return true;
}
///
/// Prepares the project for being built.
///
/// The solution configuration that is built.
///
/// Ensures the configuration-level object directory exists and ensures
/// that none of the output files are marked read-only.
///
protected override void Prepare(string solutionConfiguration) {
// obtain project configuration (corresponding with solution configuration)
ConfigurationBase config = (ConfigurationBase) BuildConfigurations[solutionConfiguration];
// ensure configuration-level object directory exists
if (!config.ObjectDir.Exists) {
config.ObjectDir.Create();
config.ObjectDir.Refresh();
}
// ensure that none of the output files in the configuration-level
// object directory are marked read-only
base.Prepare(solutionConfiguration);
}
public override void GetOutputFiles(string solutionConfiguration, Hashtable outputFiles) {
base.GetOutputFiles (solutionConfiguration, outputFiles);
// obtain project configuration (corresponding with solution configuration)
ConfigurationSettings projectConfig = (ConfigurationSettings)
BuildConfigurations[solutionConfiguration];
// add type library
if (projectConfig.RegisterForComInterop) {
string typeLib = GetTypeLibraryPath(projectConfig);
if (!outputFiles.ContainsKey(typeLib)) {
outputFiles.Add(typeLib, Path.GetFileName(typeLib));
}
}
// add satellite assemblies
Hashtable resourceSets = GetLocalizedResources();
foreach (LocalizedResourceSet localizedResourceSet in resourceSets.Values) {
FileInfo satelliteAssembly = localizedResourceSet.GetSatelliteAssemblyPath(
projectConfig, ProjectSettings);
// skip files that do not exist, or are already in hashtable
if (satelliteAssembly.Exists && !outputFiles.ContainsKey(satelliteAssembly.FullName)) {
string relativePath = localizedResourceSet.GetRelativePath(
ProjectSettings);
outputFiles.Add(satelliteAssembly.FullName, relativePath);
}
}
}
protected override BuildResult Build(string solutionConfiguration) {
bool bSuccess = true;
bool outputUpdated;
string tempFile = null;
GacCache.RecreateDomain();
try {
// obtain project configuration (corresponding with solution configuration)
ConfigurationSettings cs = (ConfigurationSettings) BuildConfigurations[solutionConfiguration];
// perform prebuild actions (for VS.NET 2003 and higher)
if (!PreBuild(cs)) {
// no longer bother trying to build the project and do not
// execute any post-build events
return BuildResult.Failed;
}
// unregister types exposed to COM, and unregister type
// library (if it exists)
UnregisterForComInterop(cs);
// ensure temp directory exists
if (!Directory.Exists(TemporaryFiles.BasePath)) {
Directory.CreateDirectory(TemporaryFiles.BasePath);
}
// compile neutral and localized resx files
CompileResXFiles(solutionConfiguration);
// check if project output needs to be rebuilt
if (CheckUpToDate(solutionConfiguration)) {
Log(Level.Verbose, "Project is up-to-date.");
// project output is up-to-date
outputUpdated = false;
} else {
// prepare the project for build
Prepare(solutionConfiguration);
// check if project does not contain any sources
if (_sourceFiles.Count == 0) {
// create temp file
tempFile = Path.GetTempFileName();
// add temp file to collection of sources to compile
// as command line compilers require a least one source
// file to be specified, but VS.NET supports empty
// projects
_sourceFiles[tempFile] = null;
}
string tempResponseFile = FileUtils.CombinePaths(TemporaryFiles.BasePath,
CommandFile);
using (StreamWriter sw = File.CreateText(tempResponseFile)) {
// write compiler options
WriteCompilerOptions(sw, solutionConfiguration);
}
Log(Level.Verbose, "Starting compiler...");
if (SolutionTask.Verbose) {
using (StreamReader sr = new StreamReader(tempResponseFile)) {
Log(Level.Verbose, "Commands:");
// increment indentation level
SolutionTask.Project.Indent();
try {
while (true) {
// read line
string line = sr.ReadLine();
if (line == null) {
break;
}
// display line
Log(Level.Verbose, " " + line);
}
} finally {
// restore indentation level
SolutionTask.Project.Unindent();
}
}
}
ProcessStartInfo psi = GetProcessStartInfo(cs, tempResponseFile);
psi.UseShellExecute = false;
psi.RedirectStandardOutput = true;
// start compiler
Process p = Process.Start(psi);
while (true) {
// read line
string line = p.StandardOutput.ReadLine();
if (line == null) {
break;
}
// display line
Log(Level.Info, line);
}
p.WaitForExit();
int exitCode = p.ExitCode;
Log(Level.Verbose, "{0}! (exit code = {1})", (exitCode == 0) ? "Success" : "Failure", exitCode);
if (exitCode > 0) {
bSuccess = false;
}
// project output has been updated
outputUpdated = true;
}
#region Process culture-specific resource files
Log(Level.Verbose, "Building satellite assemblies...");
Hashtable resourceSets = GetLocalizedResources();
foreach (LocalizedResourceSet localizedResourceSet in resourceSets.Values) {
AssemblyLinkerTask al = new AssemblyLinkerTask();
al.Project = SolutionTask.Project;
al.NamespaceManager = SolutionTask.NamespaceManager;
al.Parent = SolutionTask;
al.BaseDirectory = cs.OutputDir;
al.InitializeTaskConfiguration();
DirectoryInfo satelliteBuildDir = localizedResourceSet.
GetBuildDirectory(cs);
// ensure satellite build directory exists
if (!satelliteBuildDir.Exists) {
satelliteBuildDir.Create();
}
al.OutputFile = localizedResourceSet.GetSatelliteAssemblyPath(
cs, ProjectSettings);
al.OutputTarget = "lib";
al.Culture = localizedResourceSet.Culture.Name;
al.TemplateFile = new FileInfo(cs.BuildPath);
foreach (Resource resource in localizedResourceSet.Resources) {
FileInfo compiledResourceFile = null;
if (resource.IsResX) {
// localized resx files have already been compiled
compiledResourceFile = resource.GetCompiledResourceFile(
solutionConfiguration);
} else {
// compile resource
compiledResourceFile = resource.Compile(solutionConfiguration);
}
// add resources to embed
EmbeddedResource embeddedResource = new EmbeddedResource(
compiledResourceFile.FullName, resource.GetManifestResourceName(solutionConfiguration));
al.EmbeddedResources.Add(embeddedResource);
}
// increment indentation level
SolutionTask.Project.Indent();
try {
Log(Level.Verbose, " - {0}", al.Culture);
// run assembly linker
al.Execute();
} finally {
// restore indentation level
SolutionTask.Project.Unindent();
}
}
#endregion Process culture-specific resource files
#region Register project output for COM Interop
// check if we need to build type library
if (cs.RegisterForComInterop) {
// create type library in output dir, and register it using
// that path to match VS.NET
string typeLibPath = GetTypeLibraryPath(cs);
RegisterForComInterop(cs, typeLibPath);
// copy generated type library to object directory to match
// VS.NET
string objTypeLibPath = Path.ChangeExtension(cs.BuildPath, ".tlb");
CopyFile(new FileInfo(typeLibPath), new FileInfo (objTypeLibPath),
SolutionTask);
}
#endregion Register project output for COM Interop
#region Deploy project and configuration level output files
// copy primary project output (and related files)
Hashtable outputFiles = CollectionsUtil.CreateCaseInsensitiveHashtable();
GetOutputFiles(solutionConfiguration, outputFiles);
foreach (DictionaryEntry de in outputFiles) {
string srcPath = (string) de.Key;
string relativePath = (string) de.Value;
if (IsWebProject) {
WebDavClient wdc = new WebDavClient(new Uri(_webProjectBaseUrl));
wdc.UploadFile(srcPath, FileUtils.CombinePaths(cs.RelativeOutputDir,
relativePath).Replace(@"\", "/"));
} else {
// determine destination file
FileInfo destFile = new FileInfo(FileUtils.CombinePaths(cs.OutputDir.FullName,
relativePath));
// copy file using task
CopyFile(new FileInfo(srcPath), destFile, SolutionTask);
}
}
#endregion Deploy project and configuration level output files
if (ProjectSettings.RunPostBuildEvent != null) {
if (!PostBuild(cs, !outputUpdated || bSuccess, outputUpdated)) {
bSuccess = false;
}
}
if (!bSuccess) {
Log(Level.Error, "Build failed.");
return BuildResult.Failed;
}
return outputUpdated ? BuildResult.SuccessOutputUpdated : BuildResult.Success;
} finally {
// check if temporary file was created to support empty projects
if (tempFile != null) {
// ensure temp file is deleted
File.Delete(tempFile);
}
}
}
#endregion Override implementation of ProjectBase
#region Protected Instance Methods
///
/// Returns a for launching the compiler
/// for this project.
///
/// The configuration to build.
/// The response file for the compiler.
///
/// A for launching the compiler for
/// this project.
///
protected abstract ProcessStartInfo GetProcessStartInfo(ConfigurationBase config, string responseFile);
protected virtual ReferenceBase CreateReference(SolutionBase solution, XmlElement xmlDefinition) {
if (solution == null) {
throw new ArgumentNullException("solution");
}
if (xmlDefinition == null) {
throw new ArgumentNullException("xmlDefinition");
}
if (xmlDefinition.Attributes["Project"] != null) {
return new ManagedProjectReference(xmlDefinition, ReferencesResolver, this,
solution, ProjectSettings.TemporaryFiles, GacCache, OutputDir);
} else if (xmlDefinition.Attributes["WrapperTool"] != null) {
// wrapper
return new ManagedWrapperReference(xmlDefinition, ReferencesResolver,
this, GacCache, ProjectSettings);
} else {
// assembly reference
return new ManagedAssemblyReference(xmlDefinition, ReferencesResolver,
this, GacCache);
}
}
public override ProjectReferenceBase CreateProjectReference(ProjectBase project, bool isPrivateSpecified, bool isPrivate) {
return new ManagedProjectReference(project, this, isPrivateSpecified,
isPrivate);
}
protected virtual void WriteCompilerOptions(StreamWriter sw, string solutionConfiguration) {
// obtain project configuration (corresponding with solution configuration)
ConfigurationSettings config = (ConfigurationSettings) BuildConfigurations[solutionConfiguration];
// write project level options (eg. /target)
foreach (string setting in ProjectSettings.Settings) {
sw.WriteLine(setting);
}
// write configuration-level compiler options
foreach (string setting in config.Settings) {
sw.WriteLine(setting);
}
// write assembly references to response file
foreach (string assemblyReference in GetAssemblyReferences(solutionConfiguration)) {
sw.WriteLine("/r:\"{0}\"", assemblyReference);
}
if (ProjectSettings.ApplicationIcon != null) {
sw.WriteLine(@"/win32icon:""{0}""",
ProjectSettings.ApplicationIcon.FullName);
}
if (_neutralResources.Count > 0) {
WriteNeutralResourceOptions(sw, solutionConfiguration);
}
// before writing files to response file, allow project specific
// options to be written (eg. VB specific options)
WriteProjectOptions(sw);
// add the files to compile
foreach (string file in _sourceFiles.Keys) {
sw.WriteLine(@"""" + file + @"""");
}
}
protected virtual void WriteProjectOptions(StreamWriter sw) {
}
///
/// Returns the project location from the specified project XML fragment.
///
/// XML fragment representing the project file.
///
/// The project location of the specified project XML file.
///
///
/// The project location could not be determined.
/// -or-
/// The project location is invalid.
///
protected abstract ProjectLocation DetermineProjectLocation(XmlElement docElement);
#endregion Protected Instance Methods
#region Private Instance Methods
///
/// Gets the absolute path of the type library for the project
/// output.
///
/// The configuration to build.
///
/// The absolute path of the type library for the project output.
///
private string GetTypeLibraryPath(ConfigurationSettings config) {
if (config == null) {
throw new ArgumentNullException("config");
}
return Path.ChangeExtension(config.OutputPath, ".tlb");
}
///
/// Generates a type library for the specified assembly, registers it.
///
/// The project configuration that is built.
/// The path of the type library to generate.
///
/// The regasm tool is used to generate the type library.
///
private void RegisterForComInterop(ConfigurationSettings config, string typelibPath) {
Log(Level.Verbose, "Registering project output for COM Interop...");
// create and initialize regasm task
RegAsmTask regasm = CreateRegAsmTask();
// add assembly references
foreach (ReferenceBase reference in References) {
StringCollection assemblyReferences = reference.GetAssemblyReferences(
config.Name);
foreach (string assemblyFile in assemblyReferences) {
regasm.References.Includes.Add(assemblyFile);
}
}
// assembly to register for COM interop
regasm.AssemblyFile = new FileInfo(config.BuildPath);
// type library to create
regasm.TypeLib = new FileInfo(typelibPath);
// increment indentation level
regasm.Project.Indent();
try {
// execute task
regasm.Execute();
} finally {
// restore indentation level
regasm.Project.Unindent();
}
}
///
/// Unregister a type library for the specified assembly, and the types
/// in that assembly.
///
/// The project configuration that is built.
///
/// The regasm tool is used to unregister the type library, and
/// remove the COM registration for types in the specified assembly.
///
private void UnregisterForComInterop(ConfigurationSettings config) {
// if COM interop registration is not enabled or the previous project
// output does not exist, then there's nothing to do
if (!config.RegisterForComInterop || !File.Exists(config.OutputPath)) {
return;
}
Log(Level.Verbose, "Unregistering project output for COM Interop...");
// create and initialize regasm task
RegAsmTask regasm = CreateRegAsmTask();
// add assembly references
foreach (ReferenceBase reference in References) {
StringCollection assemblyReferences = reference.GetAssemblyReferences(
config.Name);
foreach (string assemblyFile in assemblyReferences) {
regasm.References.Includes.Add(assemblyFile);
}
}
// unregister types
regasm.Unregister = true;
// assembly to unregister
regasm.AssemblyFile = new FileInfo(config.OutputPath);
// determine path for type library
string typeLibPath = GetTypeLibraryPath(config);
// if the type library exists, unregister it
if (File.Exists(typeLibPath)) {
regasm.TypeLib = new FileInfo(typeLibPath);
}
// increment indentation level
regasm.Project.Indent();
try {
regasm.Execute();
} finally {
// restore indentation level
regasm.Project.Unindent();
}
}
private void RegisterEmbeddedResource(string resourceFile, XmlElement elemFile) {
FileInfo fi = new FileInfo(resourceFile);
if (fi.Exists && string.Compare(".resx", fi.Extension, true) == 0 && fi.Length == 0) {
Log(Level.Verbose, "Skipping zero-byte embedded resource '{0}'.",
fi.FullName);
} else {
string dependentOn = (elemFile.Attributes["DependentUpon"] != null) ? FileUtils.CombinePaths(fi.DirectoryName, elemFile.Attributes["DependentUpon"].Value) : null;
Resource r = new Resource(this, fi, elemFile.Attributes["RelPath"].Value, dependentOn, SolutionTask, GacCache);
if (r.Culture != null) {
_localizedResources.Add(r);
} else {
_neutralResources.Add(r);
}
}
}
private void CompileResXFiles(string solutionConfiguration) {
Log(Level.Verbose, "Compiling resources:");
Hashtable resxResources = new Hashtable();
// neutral resources
foreach (Resource resource in _neutralResources) {
if (!resource.IsResX) {
continue;
}
Log(Level.Verbose, " - {0}", resource.InputFile);
// determine filename of output file
FileInfo compiledResxFile = resource.GetCompiledResourceFile(solutionConfiguration);
// add to list of resx files to compile
resxResources.Add(resource, compiledResxFile);
}
// localized resources
foreach (Resource resource in _localizedResources) {
if (!resource.IsResX) {
continue;
}
Log(Level.Verbose, " - {0}", resource.InputFile);
// determine filename of output file
FileInfo compiledResxFile = resource.GetCompiledResourceFile(solutionConfiguration);
// add to list of resx files to compile
resxResources.Add(resource, compiledResxFile);
}
// no further processing required if there are no resx files to
// compile
if (resxResources.Count == 0) {
return;
}
// create instance of ResGen task
ResGenTask rt = new ResGenTask();
// inherit project from solution task
rt.Project = SolutionTask.Project;
// inherit namespace manager from solution task
rt.NamespaceManager = SolutionTask.NamespaceManager;
// parent is solution task
rt.Parent = SolutionTask;
// inherit verbose setting from solution task
rt.Verbose = SolutionTask.Verbose;
// make sure framework specific information is set
rt.InitializeTaskConfiguration();
// set parent of child elements
rt.Assemblies.Parent = rt;
// inherit project from solution task from parent task
rt.Assemblies.Project = rt.Project;
// inherit namespace manager from parent task
rt.Assemblies.NamespaceManager = rt.NamespaceManager;
// set base directory for filesets
rt.Assemblies.BaseDirectory = ProjectDirectory;
// set resx files to compile
foreach (DictionaryEntry entry in resxResources) {
Resource resource = (Resource) entry.Key;
FileInfo outputFile = (FileInfo) entry.Value;
QualifiedResource qualifiedResource = new QualifiedResource(
resource.InputFile, outputFile);
rt.QualifiedResources.Add(qualifiedResource);
}
// inherit assembly references from project
foreach (ReferenceBase reference in References) {
StringCollection assemblyReferences = reference.GetAssemblyReferences(
solutionConfiguration);
foreach (string assemblyFile in assemblyReferences) {
rt.Assemblies.Includes.Add(assemblyFile);
}
}
// increment indentation level
rt.Project.Indent();
try {
// execute task
rt.Execute();
} finally {
// restore indentation level
rt.Project.Unindent();
}
}
private void WriteNeutralResourceOptions(StreamWriter sw, string solutionConfiguration) {
// no further processing required if there are no neutral resource
// files
if (_neutralResources.Count == 0) {
return;
}
foreach (Resource resource in _neutralResources) {
Log(Level.Verbose, " - {0}", resource.InputFile);
if (resource.IsResX) {
// determine filename of compiled file
FileInfo compiledResxFile = resource.GetCompiledResourceFile(solutionConfiguration);
// determine manifest resource name
string manifestResourceName = resource.GetManifestResourceName(
solutionConfiguration);
// write option to response file
sw.WriteLine(string.Format(CultureInfo.InvariantCulture,
"/res:\"{0}\",\"{1}\"", compiledResxFile.FullName,
manifestResourceName));
} else {
// compile resource
FileInfo compiledResourceFile = resource.Compile(
solutionConfiguration);
// write option to response file
sw.WriteLine(string.Format(CultureInfo.InvariantCulture,
"/res:\"{0}\",\"{1}\"", compiledResourceFile.FullName,
resource.GetManifestResourceName(solutionConfiguration)));
}
}
}
private bool PreBuild(ConfigurationSettings cs) {
string buildCommandLine = ProjectSettings.PreBuildEvent;
// check if there are pre build commands to be run
if (buildCommandLine != null) {
string batchFile = FileUtils.CombinePaths(cs.OutputDir.FullName, "PreBuildEvent.bat");
string workingDirectory = cs.OutputDir.FullName;
return ExecuteBuildEvent("PreBuildEvent", buildCommandLine,
batchFile, workingDirectory, cs);
}
// nothing to do, signal success
return true;
}
private bool PostBuild(ConfigurationSettings cs, bool bCompileSuccess, bool bOutputUpdated) {
string buildCommandLine = ProjectSettings.PostBuildEvent;
// check if there are post build commands to be run
if (buildCommandLine != null) {
Log(Level.Debug, "PostBuild commandline: {0}", buildCommandLine);
string batchFile = FileUtils.CombinePaths(cs.OutputDir.FullName, "PostBuildEvent.bat");
string workingDirectory = cs.OutputDir.FullName;
bool bBuildEventSuccess;
// there are three different settings for when the PostBuildEvent should be run
switch (ProjectSettings.RunPostBuildEvent) {
case "OnBuildSuccess":
// post-build event will run if the build succeeds. Thus,
// the event will even run for a project that is up-to-date,
// as long as the build succeeds
if (bCompileSuccess) {
Log(Level.Debug, "PostBuild+OnBuildSuccess+bCompileSuccess");
bBuildEventSuccess = ExecuteBuildEvent("PostBuildEvent",
buildCommandLine, batchFile, workingDirectory, cs);
} else {
Log(Level.Debug, "PostBuild+OnBuildSuccess");
bBuildEventSuccess = true;
}
break;
case "Always":
// post-build event will run regardless of whether the
// build succeeded
Log(Level.Debug, "PostBuild+Always");
bBuildEventSuccess = ExecuteBuildEvent("PostBuildEvent",
buildCommandLine, batchFile, workingDirectory, cs);
break;
case "OnOutputUpdated":
// post-build event will only run when the compiler's
// output file (.exe or .dll) is different than the
// previous compiler output file. Thus, a post-build
// event will not run if a project is up-to-date
if (bOutputUpdated) {
Log(Level.Debug, "PostBuild+OnOutputUpdated+bOutputUpdated");
bBuildEventSuccess = ExecuteBuildEvent("PostBuildEvent",
buildCommandLine, batchFile, workingDirectory, cs);
} else {
Log(Level.Debug, "PostBuild+OnOutputUpdated");
bBuildEventSuccess = true;
}
break;
default:
// getting here means unknown values in the RunPostBuildEvent
// property
bBuildEventSuccess = false;
break;
}
return bBuildEventSuccess;
}
// nothing to do, signal success
return true;
}
private bool CheckUpToDate(string solutionConfiguration) {
DateTime dtOutputTimeStamp;
// obtain project configuration (corresponding with solution configuration)
ConfigurationSettings cs = (ConfigurationSettings) BuildConfigurations[solutionConfiguration];
// check if project build output exists
if (File.Exists(cs.BuildPath)) {
dtOutputTimeStamp = File.GetLastWriteTime(cs.BuildPath);
} else {
return false;
}
// check if project file was updated after the output file was
// built
string fileName = FileSet.FindMoreRecentLastWriteTime(ProjectPath,
dtOutputTimeStamp);
if (fileName != null) {
Log(Level.Debug, "Project file \"0\" has been updated, recompiling.",
fileName);
return false;
}
// check all of the input files
foreach (string file in _sourceFiles.Keys) {
if (dtOutputTimeStamp < File.GetLastWriteTime(file)) {
return false;
}
}
// check all culture-neutral resources
foreach (Resource resource in _neutralResources) {
// check if input file was updated since last compile
if (dtOutputTimeStamp < resource.InputFile.LastWriteTime) {
return false;
}
// check if compiled resource file exists
FileInfo compiledResourceFile = resource.GetCompiledResourceFile(solutionConfiguration);
if (!compiledResourceFile.Exists) {
return false;
}
// check if compiled resource file is up-to-date
if (dtOutputTimeStamp < compiledResourceFile.LastWriteTime) {
return false;
}
}
// check all of the input references
foreach (ReferenceBase reference in _references) {
if (dtOutputTimeStamp < reference.GetTimestamp(solutionConfiguration)) {
return false;
}
}
// check extra output files
foreach (DictionaryEntry de in cs.ExtraOutputFiles) {
string extraOutputFile = (string) de.Key;
// check if extra output file exists
if (!File.Exists(extraOutputFile)) {
return false;
}
}
return true;
}
///
/// Returns containing culture-specific resources.
///
///
/// A containing culture-specific resources.
///
///
/// The key of the is
/// and the value is an instance
/// for that culture.
///
private Hashtable GetLocalizedResources() {
Hashtable localizedResourceSets = new Hashtable();
foreach (Resource resource in _localizedResources) {
CultureInfo resourceCulture = resource.Culture;
LocalizedResourceSet resourceSet = (LocalizedResourceSet)
localizedResourceSets[resourceCulture];
if (resourceSet == null) {
resourceSet = new LocalizedResourceSet(resourceCulture);
localizedResourceSets.Add(resourceCulture, resourceSet);
}
resourceSet.Resources.Add(resource);
}
return localizedResourceSets;
}
///
/// Creates and initializes a instance.
///
///
/// An initialized instance.
///
private RegAsmTask CreateRegAsmTask() {
RegAsmTask regasm = new RegAsmTask();
// parent is solution task
regasm.Parent = SolutionTask;
// inherit project from solution task
regasm.Parent = regasm.Project = SolutionTask.Project;
// inherit verbose setting from solution task
regasm.Verbose = SolutionTask.Verbose;
// inherit namespace manager from solution task
regasm.NamespaceManager = SolutionTask.NamespaceManager;
// initialize framework configuration
regasm.InitializeTaskConfiguration();
// inherit project from parent task
regasm.Assemblies.Project = regasm.Project;
// set parent of child elements
regasm.Assemblies.Parent = regasm;
// inherit namespace manager from parent task
regasm.Assemblies.NamespaceManager = regasm.NamespaceManager;
// set base directory for filesets
regasm.Assemblies.BaseDirectory = ProjectDirectory;
// inherit project from parent task
regasm.References.Project = regasm.Project;
// set parent of child elements
regasm.References.Parent = regasm;
// inherit namespace manager from parent task
regasm.References.NamespaceManager = regasm.NamespaceManager;
// set base directory for filesets
regasm.References.BaseDirectory = ProjectDirectory;
// only output warning messages or higher, unless
// we're running in verbose mode
if (!regasm.Verbose) {
regasm.Threshold = Level.Warning;
}
return regasm;
}
#endregion Private Instance Methods
#region Public Static Methods
public static bool IsEnterpriseTemplateProject(string fileName) {
try {
using (StreamReader sr = new StreamReader(fileName, Encoding.Default, true)) {
XmlTextReader xtr = new XmlTextReader(sr);
xtr.MoveToContent();
if (xtr.NodeType == XmlNodeType.Element && xtr.LocalName == "EFPROJECT") {
return true;
}
}
return false;
} catch (XmlException) {
// when the project isn't a valid XML document, it definitely
// isn't an enterprise template project
return false;
} catch (Exception ex) {
throw new BuildException(string.Format(CultureInfo.InvariantCulture,
"Error checking whether '{0}' is an enterprise template project.",
fileName), Location.UnknownLocation, ex);
}
}
public static string LoadGuid(string fileName) {
try {
using (StreamReader sr = new StreamReader(fileName))
{
XmlTextReader guidReader = new XmlTextReader(sr);
while (guidReader.Read()) {
if (guidReader.NodeType == XmlNodeType.Element) {
while (guidReader.Read()) {
if (guidReader.NodeType == XmlNodeType.Element) {
if (guidReader.MoveToAttribute( "ProjectGuid" ))
return guidReader.Value;
}
}
}
}
}
throw new BuildException(string.Format(CultureInfo.InvariantCulture,
"Couldn't locate ProjectGuid in project '{0}'", fileName),
Location.UnknownLocation);
} catch (Exception ex) {
throw new BuildException(string.Format(CultureInfo.InvariantCulture,
"Error loading GUID of project '{0}'.", fileName),
Location.UnknownLocation, ex);
}
}
#endregion Public Static Methods
#region Protected Static Methods
///
/// Returns the Visual Studio product version of the specified project
/// XML fragment.
///
/// XML fragment representing the project to check.
///
/// The Visual Studio product version of the specified project XML
/// fragment.
///
///
/// The product version could not be determined.
/// -or-
/// The product version is not supported.
///
protected static ProductVersion GetProductVersion(XmlNode projectNode) {
if (projectNode == null) {
throw new ArgumentNullException("projectNode");
}
XmlAttribute productVersionAttribute = projectNode.Attributes["ProductVersion"];
if (productVersionAttribute == null) {
throw new BuildException("The \"ProductVersion\" attribute is"
+ " missing from the project node.", Location.UnknownLocation);
}
// check if we're dealing with a valid version number
Version productVersion = null;
try {
productVersion = new Version(productVersionAttribute.Value);
} catch (Exception ex) {
throw new BuildException(string.Format(CultureInfo.InvariantCulture,
"The value of the \"Version\" attribute ({0}) is not a valid"
+ " version string.", productVersionAttribute.Value),
Location.UnknownLocation, ex);
}
if (productVersion.Major == 7) {
switch (productVersion.Minor) {
case 0:
return ProductVersion.Rainier;
case 10:
return ProductVersion.Everett;
}
}
throw new BuildException(string.Format(CultureInfo.InvariantCulture,
"Visual Studio version \"{0\" is not supported.",
productVersion.ToString()), Location.UnknownLocation);
}
///
/// Returns the of the specified project
/// XML fragment.
///
/// XML fragment representing the project to check.
///
/// The of the specified project XML
/// fragment.
///
///
/// The project location could not be determined.
/// -or-
/// The project location is invalid.
///
protected static ProjectLocation GetProjectLocation(XmlNode projectNode) {
if (projectNode == null) {
throw new ArgumentNullException("projectNode");
}
XmlAttribute projectTypeAttribute = projectNode.Attributes["ProjectType"];
if (projectTypeAttribute == null) {
throw new BuildException("The \"ProjectType\" attribute is"
+ " missing from the project node.", Location.UnknownLocation);
}
try {
return (ProjectLocation) Enum.Parse(typeof(ProjectLocation),
projectTypeAttribute.Value, true);
} catch (Exception ex) {
throw new BuildException(string.Format(CultureInfo.InvariantCulture,
"The value of the \"ProjectType\" attribute ({0}) is not a valid"
+ " location string.", projectTypeAttribute.Value),
Location.UnknownLocation, ex);
}
}
#endregion Protected Static Methods
#region Private Instance Fields
private ArrayList _references;
///
/// Holds a case-insensitive list of source files.
///
///
/// The key of the is the full path of the
/// source file and the value is .
///
private readonly Hashtable _sourceFiles;
private readonly ArrayList _neutralResources;
private readonly ArrayList _localizedResources;
private readonly string _projectPath;
private readonly DirectoryInfo _projectDirectory;
private readonly string _webProjectBaseUrl;
private readonly ProjectSettings _projectSettings;
private readonly ProjectLocation _projectLocation;
#endregion Private Instance Fields
#region Private Static Fields
private const string CommandFile = "compile-commands.txt";
#endregion Private Static Fields
///
/// Groups a set of instances for a specific
/// culture.
///
private class LocalizedResourceSet {
#region Private Instance Fields
private readonly CultureInfo _culture;
private readonly ArrayList _resources;
#endregion Private Instance Fields
#region Public Instance Constructors
///
/// Initializes a new instance
/// for the specified culture.
///
/// A .
public LocalizedResourceSet(CultureInfo culture) {
if (culture == null) {
throw new ArgumentNullException("culture");
}
_culture = culture;
_resources = new ArrayList();
}
#endregion Public Instance Constructors
#region Public Instance Properties
///
/// Gets the of the
/// .
///
public CultureInfo Culture {
get { return _culture; }
}
///
/// Gets the set of localized resources.
///
public ArrayList Resources {
get { return _resources; }
}
#endregion Public Instance Properties
#region Public Instance Methods
///
/// Gets the intermediate build directory in which the satellite
/// assembly is built.
///
/// The project build configuration.
///
/// The intermediate build directory in which the satellite assembly
/// is built.
///
public DirectoryInfo GetBuildDirectory(ConfigurationSettings projectConfig) {
return new DirectoryInfo(FileUtils.CombinePaths(
projectConfig.ObjectDir.FullName, Culture.Name));
}
///
/// Gets a representing the path to the
/// intermediate file location of the satellite assembly.
///
/// The project build configuration.
/// The project settings.
///
/// A representing the path to the
/// intermediate file location of the satellite assembly.
///
public FileInfo GetSatelliteAssemblyPath(ConfigurationSettings projectConfig, ProjectSettings projectSettings) {
DirectoryInfo buildDir = GetBuildDirectory(projectConfig);
return new FileInfo(FileUtils.CombinePaths(buildDir.FullName,
GetSatelliteFileName(projectSettings)));
}
///
/// Gets path of the satellite assembly, relative to the output
/// directory.
///
/// The project settings.
///
/// The path of the satellite assembly, relative to the output
/// directory.
///
public string GetRelativePath(ProjectSettings projectSettings) {
return FileUtils.CombinePaths(Culture.Name, GetSatelliteFileName(
projectSettings));
}
#endregion Public Instance Methods
#region Private Instance Methods
private string GetSatelliteFileName(ProjectSettings projectSettings) {
return string.Format(CultureInfo.InvariantCulture,
"{0}.resources.dll", projectSettings.AssemblyName);
}
#endregion Private Instance Methods
}
}
///
/// Indentifies the different output types of a managed project.
///
///
/// Visual Studio .NET does not support modules.
///
public enum ManagedOutputType {
///
/// A class library.
///
Library = 1,
///
/// A console application.
///
Executable = 2,
///
/// A Windows program.
///
WindowsExecutable = 3
}
}