// 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.ComponentModel; using System.Collections; using System.Collections.Specialized; using System.Diagnostics; using System.Globalization; using System.IO; 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.VSNet.Tasks; namespace NAnt.VSNet { public class Project : ProjectBase { #region Public Instance Constructors public Project(SolutionTask solutionTask, TempFileCollection tfc, ReferenceGacCache gacCache, DirectoryInfo outputDir) : base(solutionTask, tfc, gacCache, outputDir) { _htReferences = CollectionsUtil.CreateCaseInsensitiveHashtable(); _sourceFiles = CollectionsUtil.CreateCaseInsensitiveHashtable(); _htResources = CollectionsUtil.CreateCaseInsensitiveHashtable(); _htAssemblies = CollectionsUtil.CreateCaseInsensitiveHashtable(); } #endregion Public Instance Constructors #region Public Instance Properties /// /// 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 Path.GetFullPath(_projectPath); } } } /// /// 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 Reference[] References { get { return (Reference[]) new ArrayList(_htReferences.Values).ToArray(typeof(Reference)); } } public Resource[] Resources { get { return (Resource[]) new ArrayList(_htResources.Values).ToArray(typeof(Resource)); } } public ProjectSettings ProjectSettings { get { return _projectSettings; } } #endregion Public Instance Properties #region Public Static Methods public static bool IsEnterpriseTemplateProject(string fileName) { try { XmlDocument doc = LoadXmlDocument(fileName); return doc.DocumentElement.Name.ToString(CultureInfo.InvariantCulture) == "EFPROJECT"; } 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 { XmlDocument doc = LoadXmlDocument(fileName); return ProjectSettings.GetProjectGuid(doc.DocumentElement); } 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 Public Instance Methods public override void Load(Solution sln, string projectPath) { XmlDocument doc = LoadXmlDocument(projectPath); _projectPath = projectPath; if (!_isWebProject) { _projectDirectory = new FileInfo(projectPath).DirectoryName; } else { _projectDirectory = projectPath.Replace(":", "_"); _projectDirectory = _projectDirectory.Replace("/", "_"); _projectDirectory = _projectDirectory.Replace("\\", "_"); _projectDirectory = Path.Combine(TempFiles.BasePath, _projectDirectory); // ensure project directory exists Directory.CreateDirectory(_projectDirectory); _webProjectBaseUrl = projectPath.Substring(0, projectPath.LastIndexOf("/")); } _projectSettings = new ProjectSettings(doc.DocumentElement, (XmlElement) doc.SelectSingleNode("//Build/Settings"), new DirectoryInfo(_projectDirectory), TempFiles); _isWebProject = ProjectFactory.IsUrl(projectPath); _webProjectBaseUrl = string.Empty; XmlNodeList nlConfigurations, nlReferences, nlFiles, nlImports; nlConfigurations = doc.SelectNodes("//Config"); foreach (XmlElement elemConfig in nlConfigurations) { ConfigurationSettings cs = new ConfigurationSettings(this, elemConfig, SolutionTask, OutputDir); ProjectConfigurations[elemConfig.Attributes["Name"].Value] = cs; } nlReferences = doc.SelectNodes("//References/Reference"); foreach (XmlElement elemReference in nlReferences) { Reference reference = new Reference(sln, _projectSettings, elemReference, GacCache, SolutionTask, OutputDir); _htReferences[elemReference.Attributes["Name"].Value] = reference; } if (_projectSettings.Type == ProjectType.VBNet) { nlImports = doc.SelectNodes("//Imports/Import"); foreach (XmlElement elemReference in nlImports) { _imports += elemReference.Attributes["Namespace"].Value.ToString(CultureInfo.InvariantCulture) + ","; } if (!StringUtils.IsNullOrEmpty(_imports)) { _imports = "/Imports:" + _imports; } } nlFiles = doc.SelectNodes("//Files/Include/File"); foreach (XmlElement elemFile in nlFiles) { string buildAction = elemFile.Attributes["BuildAction"].Value; string sourceFile; if (!StringUtils.IsNullOrEmpty(elemFile.GetAttribute("Link"))) { sourceFile = Path.GetFullPath(Path.Combine(_projectDirectory, elemFile.GetAttribute("Link"))); } else { sourceFile = Path.GetFullPath(Path.Combine(_projectDirectory, elemFile.GetAttribute("RelPath"))); } if (_isWebProject) { WebDavClient wdc = new WebDavClient(new Uri(_webProjectBaseUrl)); wdc.DownloadFile(sourceFile, elemFile.Attributes["RelPath"].Value); if (buildAction == "Compile") { _sourceFiles[sourceFile] = null; } else if (buildAction == "EmbeddedResource") { FileInfo fi = new FileInfo(sourceFile); if (fi.Exists && fi.Length == 0) { Log(Level.Verbose, LogPrefix + "Skipping zero-byte embedded resource '{0}'.", fi.FullName); } else { string dependentOn = (elemFile.Attributes["DependentUpon"] != null) ? Path.Combine(fi.DirectoryName, elemFile.Attributes["DependentUpon"].Value) : null; Resource r = new Resource(this, fi, elemFile.Attributes["RelPath"].Value, Path.Combine(fi.DirectoryName, elemFile.Attributes["DependentUpon"].Value), SolutionTask); _htResources[r.InputFile] = r; } } } else { if (buildAction == "Compile") { _sourceFiles[sourceFile] = null; } else if (buildAction == "EmbeddedResource") { FileInfo resourceFile = new FileInfo(sourceFile); if (resourceFile.Exists && resourceFile.Length == 0) { Log(Level.Verbose, LogPrefix + "Skipping zero-byte embedded resource '{0}'.", resourceFile.FullName); } else { string dependentOn = (elemFile.Attributes["DependentUpon"] != null) ? Path.Combine(resourceFile.DirectoryName, elemFile.Attributes["DependentUpon"].Value) : null; Resource r = new Resource(this, resourceFile, elemFile.Attributes["RelPath"].Value, dependentOn, SolutionTask); _htResources[r.InputFile] = r; } } } } } protected override bool Build(ConfigurationBase configurationSettings) { bool bSuccess = true; bool haveCultureSpecificResources = false; string tempFile = null; try { ConfigurationSettings cs = (ConfigurationSettings) configurationSettings; // perform prebuild actions (for VS.NET 2003) if (ProjectSettings.RunPostBuildEvent != null) { bSuccess = PreBuild(cs); } // ensure the temp dir exists Directory.CreateDirectory(TempFiles.BasePath); string tempResponseFile = Path.Combine(TempFiles.BasePath, Project.CommandFile); using (StreamWriter sw = File.CreateText(tempResponseFile)) { if (CheckUpToDate(cs)) { Log(Level.Verbose, LogPrefix + "Project is up-to-date."); if (ProjectSettings.RunPostBuildEvent != null) { PostBuild(cs, true, false); } return true; } foreach (string setting in ProjectSettings.Settings) { sw.WriteLine(setting); } foreach (string setting in cs.Settings) { sw.WriteLine(setting); } if (ProjectSettings.ApplicationIcon != null) { sw.WriteLine(@"/win32icon:""{0}""", ProjectSettings.ApplicationIcon.FullName); } if (_projectSettings.Type == ProjectType.VBNet) { sw.WriteLine(_imports); } Log(Level.Verbose, LogPrefix + "Copying references:"); foreach (Reference reference in _htReferences.Values) { Log(Level.Verbose, LogPrefix + " - " + reference.Name); if (reference.CopyLocal) { if (reference.IsCreated) { string program, commandLine; reference.GetCreationCommand(cs, out program, out commandLine); // Check that we have an SdkDirectory available if(null == SolutionTask.Project.TargetFramework.SdkDirectory) { throw new BuildException(string.Format(CultureInfo.InvariantCulture, "While building {0}:" + Environment.NewLine + " The framework SDK directory was not found or not set." + Environment.NewLine + " You may need to specify a specific framework to use with the -k: command line option.", Name), Location.UnknownLocation); } // Append the correct SDK directory to the program program = Path.Combine(SolutionTask.Project.TargetFramework.SdkDirectory.FullName, program); Log(Level.Verbose, LogPrefix + program + " " + commandLine); ProcessStartInfo psiRef = new ProcessStartInfo(program, commandLine); psiRef.UseShellExecute = false; psiRef.WorkingDirectory = cs.OutputDir.FullName; try { Process pRef = Process.Start(psiRef); pRef.WaitForExit(); } catch (Win32Exception ex) { throw new BuildException(string.Format(CultureInfo.InvariantCulture, "Unable to start process '{0}' with commandline '{1}'.", program, commandLine), Location.UnknownLocation, ex); } } else { StringCollection fromFilenames = reference.GetReferenceFiles(cs); // create instance of Copy task CopyTask ct = new CopyTask(); // inherit project from solution task ct.Project = SolutionTask.Project; // inherit parent from solution task ct.Parent = SolutionTask.Parent; // inherit verbose setting from solution task ct.Verbose = SolutionTask.Verbose; // make sure framework specific information is set ct.InitializeTaskConfiguration(); // set parent of child elements ct.CopyFileSet.Parent = ct; // inherit project from solution task for child elements ct.CopyFileSet.Project = SolutionTask.Project; // set base directory of fileset ct.CopyFileSet.BaseDirectory = reference.GetBaseDirectory(cs); // add files to copy foreach (string file in fromFilenames) { ct.CopyFileSet.Includes.Add(file); } // set destination directory ct.ToDirectory = cs.OutputDir; // increment indentation level ct.Project.Indent(); try { // execute task ct.Execute(); } finally { // restore indentation level ct.Project.Unindent(); } } } sw.WriteLine(reference.Setting); } if (_htResources.Count > 0) { Log(Level.Verbose, LogPrefix + "Compiling resources:"); foreach (Resource resource in _htResources.Values) { // ignore resource files associated with a culture if (resource.Culture != null) { haveCultureSpecificResources = true; continue; } Log(Level.Verbose, LogPrefix + " - {0}", resource.InputFile); resource.Compile(cs); sw.WriteLine(string.Format(CultureInfo.InvariantCulture, "/res:\"{0}\",\"{1}\"", resource.CompiledResourceFile, resource.ManifestResourceName)); } } // 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; } // add the files to compile foreach (string file in _sourceFiles.Keys) { sw.WriteLine(@"""" + file + @""""); } } Log(Level.Verbose, LogPrefix + "Starting compiler..."); if (SolutionTask.Verbose) { using (StreamReader sr = new StreamReader(tempResponseFile)) { Log(Level.Verbose, LogPrefix + "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, LogPrefix + " " + line); } } finally { // restore indentation level SolutionTask.Project.Unindent(); } } } ProcessStartInfo psi = null; if (_projectSettings.Type == ProjectType.CSharp) { psi = new ProcessStartInfo(Path.Combine(SolutionTask.Project.TargetFramework.FrameworkDirectory.FullName, "csc.exe"), "@\"" + tempResponseFile + "\""); } if (_projectSettings.Type == ProjectType.VBNet) { psi = new ProcessStartInfo(Path.Combine(SolutionTask.Project.TargetFramework.FrameworkDirectory.FullName, "vbc.exe"), "@\"" + tempResponseFile + "\""); } psi.UseShellExecute = false; psi.RedirectStandardOutput = true; // VS.NET (probably) uses the \obj\ // as working directory, so we should do the same to make sure // relative paths are resovled correctly (eg. AssemblyKeyFile) DirectoryInfo objConfigDir = new DirectoryInfo(Path.Combine(_projectDirectory, Path.Combine("obj", cs.Name))); if (!objConfigDir.Exists) { objConfigDir.Create(); } psi.WorkingDirectory = objConfigDir.FullName; Process p = Process.Start(psi); // increment indentation level SolutionTask.Project.Indent(); try { while (true) { // read line string line = p.StandardOutput.ReadLine(); if (line == null) { break; } // display line Log(Level.Info, line); } } finally { // restore indentation level SolutionTask.Project.Unindent(); } p.WaitForExit(); int exitCode = p.ExitCode; Log(Level.Verbose, LogPrefix + "{0}! (exit code = {1})", (exitCode == 0) ? "Success" : "Failure", exitCode); if (exitCode > 0) { bSuccess = false; } else { if (_isWebProject) { Log(Level.Verbose, LogPrefix + "Uploading output files..."); WebDavClient wdc = new WebDavClient(new Uri(_webProjectBaseUrl)); //wdc.DeleteFile( cs.FullOutputFile, cs.RelativeOutputPath.Replace(@"\", "/") + _ps.OutputFile ); wdc.UploadFile(cs.OutputPath, cs.RelativeOutputDir.Replace(@"\", "/") + _projectSettings.OutputFileName); } // Copy any extra files over foreach (string extraOutputFile in cs.ExtraOutputFiles) { FileInfo sourceFile = new FileInfo(extraOutputFile); if (_isWebProject) { WebDavClient wdc = new WebDavClient(new Uri(_webProjectBaseUrl)); wdc.UploadFile(extraOutputFile, cs.RelativeOutputDir.Replace(@"\", "/") + sourceFile.Name); } else { FileInfo destFile = new FileInfo(Path.Combine(cs.OutputDir.FullName, sourceFile.Name)); if (destFile.Exists) { // only copy the file if the source file is more // recent than the destination file if (FileSet.FindMoreRecentLastWriteTime(sourceFile.FullName, destFile.LastWriteTime) == null) { continue; } // make sure the destination file is writable destFile.Attributes = FileAttributes.Normal; } // copy the file and overwrite the destination file // if it already exists sourceFile.CopyTo(destFile.FullName, true); } } } #region Process culture-specific resource files if (bSuccess && haveCultureSpecificResources) { Log(Level.Verbose, LogPrefix + "Compiling satellite resources:"); Hashtable cultures = new Hashtable(); foreach (Resource resource in _htResources.Values) { // ignore resource files NOT associated with a culture if (resource.Culture == null) { continue; } ArrayList resFiles = null; if (!cultures.ContainsKey(resource.Culture)) { resFiles = new ArrayList(); cultures.Add(resource.Culture, resFiles); } else { resFiles = (ArrayList) cultures[resource.Culture]; } resFiles.Add(resource); } foreach (DictionaryEntry de in cultures) { string culture = ((CultureInfo) de.Key).Name; ArrayList resFiles = (ArrayList) de.Value; AssemblyLinkerTask al = new AssemblyLinkerTask(); al.Project = this.SolutionTask.Project; al.Parent = this.SolutionTask.Parent; al.BaseDirectory = cs.OutputDir; al.InitializeTaskConfiguration(); string satellitePath = cs.OutputDir.FullName; satellitePath = Path.Combine (satellitePath, culture); Directory.CreateDirectory(satellitePath); satellitePath = Path.Combine(satellitePath, string.Format( CultureInfo.InvariantCulture, "{0}.resources.dll", _projectSettings.AssemblyName)); al.OutputFile = new FileInfo(satellitePath); al.OutputTarget = "lib"; al.Culture = culture; al.TemplateFile = new FileInfo(Path.Combine(cs.OutputDir.FullName, _projectSettings.OutputFileName)); foreach (Resource resource in resFiles) { resource.Compile(cs); // add resources to embed Argument arg = new Argument(); arg.Value = string.Format(CultureInfo.InvariantCulture, "/embed:\"{0}\",\"{1}\"", resource.CompiledResourceFile, resource.ManifestResourceName); al.Arguments.Add(arg); } // increment indentation level SolutionTask.Project.Indent(); try { Log(Level.Verbose, LogPrefix + " - {0}", culture); // run assembly linker al.Execute(); } finally { // restore indentation level SolutionTask.Project.Unindent(); } } } #endregion Process culture-specific resource files if (ProjectSettings.RunPostBuildEvent != null) { if (!PostBuild(cs, (exitCode == 0) ? true : false, (exitCode == 0) ? true : false)) { bSuccess = false; } } if (!bSuccess ) { Log(Level.Error, LogPrefix + "Build failed."); } return bSuccess; } finally { // check if temporary file was created to support empty projects if (tempFile != null) { // ensure temp file is deleted File.Delete(tempFile); } } } #endregion Public Instance Methods #region Private Instance Methods private bool PreBuild(ConfigurationSettings cs) { string buildCommandLine = _projectSettings.PreBuildEvent; Log(Level.Debug, LogPrefix + "PreBuild commandline: {0}", buildCommandLine); // check if there are pre build commands to be run if (buildCommandLine != null) { // create a batch file for this, mirroring the behavior of VS.NET // the file is not even removed after a successful build by VS.NET, // so we don't either using (StreamWriter sw = new StreamWriter(Path.Combine(cs.OutputDir.FullName, "PreBuildEvent.bat"))) { sw.WriteLine("@echo off"); // replace any VS macros in the command line with real values buildCommandLine = ReplaceMacros(_projectSettings, cs, buildCommandLine); // handle linebreak charaters buildCommandLine = buildCommandLine.Replace(" ", "\n"); sw.WriteLine(buildCommandLine); sw.WriteLine("if errorlevel 1 goto EventReportError"); sw.WriteLine("goto EventEnd"); sw.WriteLine(":EventReportError"); sw.WriteLine("echo Project error: A tool returned an error code from the build event"); sw.WriteLine("exit 1"); sw.WriteLine(":EventEnd"); } // now that we have to file on disk execute it return ExecuteBuildEvent(Path.Combine(cs.OutputDir.FullName, "PreBuildEvent.bat"), "PreBuildEvent"); } // nothing to do, signal success return true; } private bool PostBuild(ConfigurationSettings cs, bool bCompileSuccess, bool bOutputUpdated) { string buildCommandLine = _projectSettings.PostBuildEvent; Log(Level.Debug, LogPrefix + "PostBuild commandline: {0}", buildCommandLine); // check if there are post build commands to be run if (buildCommandLine != null) { // Create a batch file for this. This mirrors VS behavior. Also this // file is not removed even after a successful build by VS so we don't either. using (StreamWriter sw = new StreamWriter(Path.Combine(cs.OutputDir.FullName, "PostBuildEvent.bat"))) { sw.WriteLine("@echo off"); // replace any VS macros in the command line with real values buildCommandLine = ReplaceMacros(_projectSettings, cs, buildCommandLine); // handle linebreak charaters buildCommandLine = buildCommandLine.Replace(" ", "\n"); sw.WriteLine(buildCommandLine); sw.WriteLine("if errorlevel 1 goto EventReportError"); sw.WriteLine("goto EventEnd"); sw.WriteLine(":EventReportError"); sw.WriteLine("echo Project error: A tool returned an error code from the build event"); sw.WriteLine("exit 1"); sw.WriteLine(":EventEnd"); } 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, LogPrefix + "PostBuild+OnBuildSuccess+bCompileSuccess"); bBuildEventSuccess = ExecuteBuildEvent(Path.Combine( cs.OutputDir.FullName, "PostBuildEvent.bat"), "PostBuildEvent"); } else { Log(Level.Debug, LogPrefix + "PostBuild+OnBuildSuccess"); bBuildEventSuccess = true; } break; case "Always": // post-build event will run regardless of whether the // build succeeded Log(Level.Debug, LogPrefix + "PostBuild+Always"); bBuildEventSuccess = ExecuteBuildEvent(Path.Combine( cs.OutputDir.FullName, "PostBuildEvent.bat"), "PostBuildEvent"); 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, LogPrefix + "PostBuild+OnOutputUpdated+bOutputUpdated"); bBuildEventSuccess = ExecuteBuildEvent(Path.Combine( cs.OutputDir.FullName, "PostBuildEvent.bat"), "PostBuildEvent"); } else { Log(Level.Debug, LogPrefix + "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 ExecuteBuildEvent(string batchFile, string buildEvent) { // need to set some process info ProcessStartInfo psi = new ProcessStartInfo(batchFile); psi.UseShellExecute = false; psi.RedirectStandardOutput = true; // For logging psi.WorkingDirectory = Path.GetDirectoryName(batchFile); // start the process now Process batchEvent = Process.Start(psi); // keep logging output from the process for as long as it exists while (true) { string logContents = batchEvent.StandardOutput.ReadLine(); if (logContents == null) { break; } Log(Level.Verbose, " [" + buildEvent.ToLower(CultureInfo.InvariantCulture) + "] " + logContents); } batchEvent.WaitForExit(); // notify if there where problems running the batch file or it // returned errors int exitCode = batchEvent.ExitCode; if (exitCode == 0) { Log(Level.Verbose, LogPrefix + "{0} succeeded (exit code = 0)", buildEvent); } else { Log(Level.Error, LogPrefix + "{0} failed with exit code = {1}", buildEvent, exitCode); } return (exitCode == 0) ? true : false; } private string ReplaceMacros(ProjectSettings ps, ConfigurationSettings cs, string commands) { // Replace all $(...} patterns with their expanded real values. Those are the // "macros" in the pre/post event builder in VS. This is potentionally a source // for user errors as expanded paths containing spaces needs to be enclosed in // quotation marks. Opted to follow VS behaviour where paths with spaces are left // to the user as a drill. string commandsExpanded, macro; int startPosition, stopPosition = 0; // Find the beginning of the first macro startPosition = commands.IndexOf("$(", stopPosition); commandsExpanded = commands; if (startPosition > -1) { // There is at least one macro to replace string targetPath = cs.OutputPath; string solutionPath = null; // The solution tag allows the specification of a bunch of projects with no // reference to a solution file. $(Solution... macros will not work then. if (SolutionTask.SolutionFile != null) { solutionPath = SolutionTask.SolutionFile.FullName; } // As long there are new macros... while (startPosition > -1) { stopPosition = commands.IndexOf(")", startPosition + 2); macro = commands.Substring(startPosition, stopPosition - startPosition + 1); switch (macro) { // Expand the appropriate macro case "$(OutDir)": // E.g. bin\Debug\ commandsExpanded = commandsExpanded.Replace("$(OutDir)", cs.RelativeOutputDir); break; case "$(ConfigurationName)": // E.g. Debug commandsExpanded = commandsExpanded.Replace("$(ConfigurationName)", cs.Name); break; case "$(ProjectName)": // E.g. WindowsApplication1 commandsExpanded = commandsExpanded.Replace("$(ProjectName)", Name); break; case "$(ProjectPath)": // E.g. C:\Doc...\Visual Studio Projects\WindowsApplications1\WindowsApplications1.csproj commandsExpanded = commandsExpanded.Replace("$(ProjectPath)", ProjectPath); break; case "$(ProjectFileName)": // E.g. WindowsApplication1.csproj commandsExpanded = commandsExpanded.Replace("$(ProjectFileName)", Path.GetFileName(ProjectPath)); break; case "$(ProjectExt)": // .csproj commandsExpanded = commandsExpanded.Replace("$(ProjectExt)", Path.GetExtension(ProjectPath)); break; case "$(ProjectDir)": // ProjectPath without ProjectFileName at the end commandsExpanded = commandsExpanded.Replace("$(ProjectDir)", Path.GetDirectoryName(ProjectPath) + Path.DirectorySeparatorChar); break; case "$(TargetName)": // E.g. WindowsApplication1 commandsExpanded = commandsExpanded.Replace("$(TargetName)", ps.AssemblyName); break; case "$(TargetPath)": // E.g. C:\Doc...\Visual Studio Projects\WindowsApplications1\bin\Debug\WindowsApplications1.exe commandsExpanded = commandsExpanded.Replace("$(TargetPath)", cs.OutputPath); break; case "$(TargetExt)": // E.g. .exe commandsExpanded = commandsExpanded.Replace("$(TargetExt)", Path.GetExtension(cs.OutputPath)); break; case "$(TargetFileName)": // E.g. WindowsApplications1.exe commandsExpanded = commandsExpanded.Replace("$(TargetFileName)", Path.GetFileName(cs.OutputPath)); break; case "$(TargetDir)": // Absolute path to OutDir commandsExpanded = commandsExpanded.Replace("$(TargetDir)", cs.OutputDir.FullName + (cs.OutputDir.FullName.EndsWith(Path.DirectorySeparatorChar.ToString(CultureInfo.InvariantCulture)) ? "" : Path.DirectorySeparatorChar.ToString(CultureInfo.InvariantCulture))); break; case "$(SolutionFileName)": // E.g. WindowsApplication1.sln if (solutionPath != null) { commandsExpanded = commandsExpanded.Replace("$(SolutionFileName)", Path.GetFileName(solutionPath)); } else { Log(Level.Error, LogPrefix + "Pre/post event macro {0} can not be set, no solution file specified.", macro); } break; case "$(SolutionPath)": // Absolute path for SolutionFileName if (solutionPath != null) { commandsExpanded = commandsExpanded.Replace("$(SolutionPath)", solutionPath); } else { Log(Level.Error, LogPrefix + "Pre/post event macro {0} can not be set, no solution file specified.", macro); } break; case "$(SolutionDir)": // SolutionPath without SolutionFileName appended if (solutionPath != null) { commandsExpanded = commandsExpanded.Replace("$(SolutionDir)", Path.GetDirectoryName(solutionPath) + Path.DirectorySeparatorChar); } else { Log(Level.Error, LogPrefix + "Pre/post event macro {0} can not be set, no solution file specified.", macro); } break; case "$(SolutionName)": // E.g. WindowsApplication1 if (solutionPath != null) { commandsExpanded = commandsExpanded.Replace("$(SolutionName)", Path.GetFileNameWithoutExtension(solutionPath)); } else { Log(Level.Error, LogPrefix + "Pre/post event macro {0} can not be set, no solution file specified.", macro); } break; case "$(SolutionExt)": // Is this ever anything but .sln? if (solutionPath != null) { commandsExpanded = commandsExpanded.Replace("$(SolutionExt)", Path.GetExtension(solutionPath)); } else { Log(Level.Error, LogPrefix + "Pre/post event macro {0} can not be set, no solution file specified.", macro); } break; case "$(PlatformName)": // .NET, does this value ever change? commandsExpanded = commandsExpanded.Replace("$(PlatformName)", ".NET"); break; // TO-DO // DevEnvDir is avaliable from the key "HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\VisualStudio\7.1\InstallDir" // But would require Microsoft.Win32 to be included. Don't want that for mono etc. ? /* case "$(DevEnvDir)": // VS installation directory with \Common7\IDE appended commandsExpanded = commandsExpanded.Replace("$(DevEnvDir)", "To be Implemented?"); break; */ default: // Signal errors for macros that do not exist Log(Level.Error, LogPrefix + "Pre/post event macro {0} not implemented.", macro); break; } // Find the beginning of the next macro if any startPosition = commands.IndexOf("$(", stopPosition); } Log(Level.Debug, LogPrefix + "Replaced command lines:{0} {1}.", Environment.NewLine, commandsExpanded); return commandsExpanded; } else { // No macro to replace Log(Level.Debug, LogPrefix + "Replaced command lines:{0} {1}.", Environment.NewLine, commandsExpanded); return commandsExpanded; } } private bool CheckUpToDate(ConfigurationSettings cs) { DateTime dtOutputTimeStamp; if (File.Exists(cs.OutputPath)) { dtOutputTimeStamp = File.GetLastWriteTime(cs.OutputPath); } else { return false; } // check all of the input files foreach (string file in _sourceFiles.Keys) if (dtOutputTimeStamp < File.GetLastWriteTime(file)) { return false; } // check all of the input references foreach (Reference reference in _htReferences.Values) { reference.ConfigurationSettings = cs; if (dtOutputTimeStamp < reference.Timestamp) { return false; } } return true; } #endregion Private Instance Methods #region Private Instance Fields private Hashtable _htReferences; /// /// 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 Hashtable _sourceFiles; private Hashtable _htResources; private Hashtable _htAssemblies; private string _imports; private bool _isWebProject; private string _projectPath; private string _projectDirectory; private string _webProjectBaseUrl; private ProjectSettings _projectSettings; #endregion Private Instance Fields #region Private Static Fields private const string CommandFile = "compile-commands.txt"; #endregion Private Static Fields } }