You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
347 lines
11 KiB
347 lines
11 KiB
// <file> |
|
// <copyright see="prj:///doc/copyright.txt"/> |
|
// <license see="prj:///doc/license.txt"/> |
|
// <author name="Daniel Grunwald"/> |
|
// <version>$Revision$</version> |
|
// </file> |
|
|
|
using System; |
|
using System.Collections.Generic; |
|
using System.Collections.ObjectModel; |
|
using System.Diagnostics; |
|
using System.IO; |
|
using System.Linq; |
|
using System.Text; |
|
using System.Text.RegularExpressions; |
|
using System.Xml; |
|
|
|
using ICSharpCode.Core; |
|
using ICSharpCode.SharpDevelop; |
|
using ICSharpCode.SharpDevelop.Project; |
|
using Microsoft.Win32; |
|
|
|
namespace CppBackendBinding |
|
{ |
|
/// <summary> |
|
/// C++ project class. Handlings project loading and saving. |
|
/// </summary> |
|
public class CppProject : AbstractProject, IProjectItemListProvider, IProjectAllowChangeConfigurations |
|
{ |
|
XmlDocument document = new XmlDocument(); |
|
List<FileGroup> groups = new List<FileGroup>(); |
|
List<FileItem> items = new List<FileItem>(); |
|
|
|
/// <summary> |
|
/// Create a new C++ project that loads the specified .vcproj file. |
|
/// </summary> |
|
public CppProject(ProjectLoadInformation info) |
|
{ |
|
this.Name = info.ProjectName; |
|
this.FileName = info.FileName; |
|
this.TypeGuid = "{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}"; |
|
|
|
using (StreamReader r = new StreamReader(info.FileName, Encoding.Default)) { |
|
try { |
|
document.Load(r); |
|
} catch (Exception ex) { |
|
throw new ProjectLoadException(ex.Message, ex); |
|
} |
|
} |
|
if (document.DocumentElement.Name != "VisualStudioProject") |
|
throw new ProjectLoadException("The project is not a visual studio project."); |
|
XmlElement filesElement = document.DocumentElement["Files"]; |
|
if (filesElement != null) { |
|
foreach (XmlElement filterElement in filesElement.ChildNodes.OfType<XmlElement>()) { |
|
if (filterElement.Name == "Filter") { |
|
FileGroup group = new FileGroup(this, filterElement); |
|
groups.Add(group); |
|
foreach (XmlElement fileElement in filterElement.ChildNodes.OfType<XmlElement>()) { |
|
if (fileElement.Name == "File" && fileElement.HasAttribute("RelativePath")) { |
|
items.Add(new FileItem(group, fileElement)); |
|
} |
|
} |
|
} |
|
} |
|
} |
|
} |
|
|
|
public override string Language { |
|
get { return CppProjectBinding.LanguageName; } |
|
} |
|
|
|
public override void Save(string fileName) |
|
{ |
|
lock (SyncRoot) { |
|
// file item types may have changed, so remove all items from their parent elements |
|
// and re-add them to the correct Filter elements |
|
foreach (FileItem item in items) { |
|
item.SaveChanges(); |
|
if (item.XmlElement.ParentNode != null) |
|
item.XmlElement.ParentNode.RemoveChild(item.XmlElement); |
|
} |
|
foreach (FileItem item in items) { |
|
FileGroup group = groups.Find(fg => fg.ItemType == item.ProjectItem.ItemType); |
|
if (group != null) { |
|
group.XmlElement.AppendChild(item.XmlElement); |
|
} else { |
|
LoggingService.Warn("Couldn't find filter for item type " + item.ProjectItem.ItemType + ", the item was not saved!"); |
|
} |
|
} |
|
using (XmlWriter writer = XmlWriter.Create(fileName, new XmlWriterSettings { |
|
NewLineOnAttributes = true, |
|
Indent = true, |
|
IndentChars = "\t", |
|
Encoding = Encoding.Default |
|
})) |
|
{ |
|
document.Save(writer); |
|
} |
|
} |
|
} |
|
|
|
/// <summary> |
|
/// Gets the list of available file item types. This member is thread-safe. |
|
/// </summary> |
|
public override ICollection<ItemType> AvailableFileItemTypes { |
|
get { |
|
lock (SyncRoot) { |
|
return groups.ConvertAll(fg => fg.ItemType).AsReadOnly(); |
|
} |
|
} |
|
} |
|
|
|
/// <summary> |
|
/// Gets the list of items in the project. This member is thread-safe. |
|
/// The returned collection is guaranteed not to change - adding new items or removing existing items |
|
/// will create a new collection. |
|
/// </summary> |
|
public override ReadOnlyCollection<ProjectItem> Items { |
|
get { |
|
lock (SyncRoot) { |
|
return new ReadOnlyCollection<ProjectItem>(items.ConvertAll(fi => fi.ProjectItem)); |
|
} |
|
} |
|
} |
|
|
|
public override ItemType GetDefaultItemType(string fileName) |
|
{ |
|
string extension = Path.GetExtension(fileName); |
|
if (string.Equals(extension, ".c", StringComparison.OrdinalIgnoreCase) || string.Equals(extension, ".cpp", StringComparison.OrdinalIgnoreCase)) |
|
return ItemType.Compile; |
|
else if (string.Equals(extension, ".h", StringComparison.OrdinalIgnoreCase)) |
|
return ItemType.Header; |
|
else |
|
return base.GetDefaultItemType(fileName); |
|
} |
|
|
|
#region IProjectAllowChangeConfigurations |
|
// TODO: Configuration/Platform handling |
|
bool IProjectAllowChangeConfigurations.RenameProjectConfiguration(string oldName, string newName) |
|
{ |
|
throw new NotImplementedException(); |
|
} |
|
|
|
bool IProjectAllowChangeConfigurations.RenameProjectPlatform(string oldName, string newName) |
|
{ |
|
throw new NotImplementedException(); |
|
} |
|
|
|
bool IProjectAllowChangeConfigurations.AddProjectConfiguration(string newName, string copyFrom) |
|
{ |
|
throw new NotImplementedException(); |
|
} |
|
|
|
bool IProjectAllowChangeConfigurations.AddProjectPlatform(string newName, string copyFrom) |
|
{ |
|
throw new NotImplementedException(); |
|
} |
|
|
|
bool IProjectAllowChangeConfigurations.RemoveProjectConfiguration(string name) |
|
{ |
|
throw new NotImplementedException(); |
|
} |
|
|
|
bool IProjectAllowChangeConfigurations.RemoveProjectPlatform(string name) |
|
{ |
|
throw new NotImplementedException(); |
|
} |
|
#endregion |
|
|
|
#region IProjectItemListProvider |
|
void IProjectItemListProvider.AddProjectItem(ProjectItem item) |
|
{ |
|
if (item == null) |
|
throw new ArgumentNullException("item"); |
|
lock (SyncRoot) { |
|
if (items.Exists(fi => fi.ProjectItem == item)) |
|
throw new ArgumentException("Project item already exists in project!"); |
|
items.Add(new FileItem(document, item)); |
|
} |
|
} |
|
|
|
bool IProjectItemListProvider.RemoveProjectItem(ProjectItem item) |
|
{ |
|
lock (SyncRoot) { |
|
return items.RemoveAll(fi => fi.ProjectItem == item) > 0; |
|
} |
|
} |
|
#endregion |
|
|
|
static string GetFile(string filename) |
|
{ |
|
if (string.IsNullOrEmpty(filename)) |
|
return null; |
|
filename = Environment.ExpandEnvironmentVariables(filename); |
|
if (File.Exists(filename)) |
|
return filename; |
|
else |
|
return null; |
|
} |
|
|
|
static string GetPathFromRegistry(string key, string valueName) |
|
{ |
|
using (RegistryKey installRootKey = Registry.LocalMachine.OpenSubKey(key)) { |
|
if (installRootKey != null) { |
|
object o = installRootKey.GetValue(valueName); |
|
if (o != null) { |
|
string r = o.ToString(); |
|
if (!string.IsNullOrEmpty(r)) |
|
return r; |
|
} |
|
} |
|
} |
|
return null; |
|
} |
|
|
|
public override void StartBuild(ProjectBuildOptions options, IBuildFeedbackSink feedbackSink) |
|
{ |
|
string productDir = GetPathFromRegistry(@"SOFTWARE\Microsoft\VisualStudio\9.0\Setup\VC", "ProductDir"); |
|
|
|
string batFile = "vcvars32.bat"; |
|
if (options.Platform == "x64") { |
|
batFile = "vcvars64.bat"; |
|
} |
|
|
|
string commonTools = |
|
GetFile(productDir != null ? Path.Combine(productDir, "bin\\" + batFile) : null) |
|
?? GetFile("%VS90COMNTOOLS%\\" + batFile) |
|
?? GetFile("%VS80COMNTOOLS%\\" + batFile); |
|
|
|
Process p = new Process(); |
|
p.StartInfo.FileName = "cmd.exe"; |
|
p.StartInfo.Arguments = "/C"; |
|
if (!string.IsNullOrEmpty(commonTools)) { |
|
p.StartInfo.Arguments += " call \"" + commonTools + "\" &&"; |
|
} |
|
p.StartInfo.Arguments += " vcbuild"; |
|
if (options.Target == BuildTarget.Build) { |
|
// OK |
|
} else if (options.Target == BuildTarget.Clean) { |
|
p.StartInfo.Arguments += " /clean"; |
|
} else if (options.Target == BuildTarget.Rebuild) { |
|
p.StartInfo.Arguments += " /rebuild"; |
|
} |
|
p.StartInfo.Arguments += " /showenv"; |
|
p.StartInfo.Arguments += " \"" + this.FileName + "\""; |
|
p.StartInfo.Arguments += " \"/error:Error: \""; |
|
p.StartInfo.Arguments += " \"/warning:Warning: \""; |
|
|
|
p.StartInfo.WorkingDirectory = this.Directory; |
|
p.StartInfo.RedirectStandardOutput = true; |
|
p.StartInfo.RedirectStandardError = true; |
|
p.StartInfo.CreateNoWindow = true; |
|
p.StartInfo.UseShellExecute = false; |
|
p.StartInfo.EnvironmentVariables["VCBUILD_DEFAULT_CFG"] = options.Configuration + "|" + options.Platform; |
|
p.StartInfo.EnvironmentVariables["SolutionPath"] = ParentSolution.FileName; |
|
|
|
p.EnableRaisingEvents = true; |
|
p.OutputDataReceived += delegate(object sender, DataReceivedEventArgs e) { |
|
if (!string.IsNullOrEmpty(e.Data)) { |
|
BuildError error = ParseError(e.Data); |
|
if (error != null) |
|
feedbackSink.ReportError(error); |
|
else |
|
feedbackSink.ReportMessage(e.Data); |
|
} |
|
}; |
|
p.ErrorDataReceived += delegate(object sender, DataReceivedEventArgs e) { |
|
if (!string.IsNullOrEmpty(e.Data)) { |
|
BuildError error = ParseError(e.Data); |
|
if (error != null) |
|
feedbackSink.ReportError(error); |
|
else |
|
feedbackSink.ReportError(new BuildError(null, e.Data)); |
|
} |
|
}; |
|
p.Exited += delegate(object sender, EventArgs e) { |
|
p.CancelErrorRead(); |
|
p.CancelOutputRead(); |
|
feedbackSink.Done(p.ExitCode == 0); |
|
p.Dispose(); |
|
}; |
|
|
|
feedbackSink.ReportMessage("Building " + this.Name); |
|
feedbackSink.ReportMessage(p.StartInfo.FileName + " " + p.StartInfo.Arguments); |
|
p.Start(); |
|
p.BeginOutputReadLine(); |
|
p.BeginErrorReadLine(); |
|
} |
|
|
|
static readonly Regex errorRegex = new Regex(@"^Error: " + |
|
@"((?:[^(:]|:\\)+)" + // group 1: file name |
|
@"(?:\((\d+)\))?" + // group 2: line number |
|
@"\s*:\s*" + // first separator |
|
@"(?:error ([^:]+):)?" + // group 3: error code |
|
@"\s*(.*)$" // group 4: error message |
|
); |
|
|
|
|
|
static readonly Regex warningRegex = new Regex(@"^(?:\d+\>)?Warning: " + |
|
@"((?:[^(:]|:\\)+)" + // group 1: file name |
|
@"(?:\((\d+)\))?" + // group 2: line number |
|
@"\s*:\s*" + // first separator |
|
@"(?:warning ([^:]+):)?" + // group 3: error code |
|
@"\s*(.*)$" // group 4: error message |
|
); |
|
|
|
|
|
/// <summary> |
|
/// Parses an error or warning message and returns a BuildError object for it. |
|
/// </summary> |
|
BuildError ParseError(string text) |
|
{ |
|
bool isWarning = false; |
|
Match match = errorRegex.Match(text); |
|
if (!match.Success) { |
|
match = warningRegex.Match(text); |
|
isWarning = true; |
|
} |
|
if (match.Success) { |
|
int line = -1; |
|
try { |
|
if (match.Groups[2].Length > 0) { |
|
line = int.Parse(match.Groups[2].Value); |
|
} |
|
} catch (FormatException) { |
|
} catch (OverflowException) { |
|
} |
|
return new BuildError(Path.Combine(Directory, match.Groups[1].Value), line, 0, |
|
match.Groups[3].Value, match.Groups[4].Value) { |
|
IsWarning = isWarning |
|
}; |
|
} else { |
|
return null; |
|
} |
|
} |
|
|
|
public override ICollection<string> PlatformNames { |
|
get { |
|
List<string> l = new List<string>(); |
|
foreach (XmlElement platformElement in document.DocumentElement["Platforms"]) { |
|
l.Add(platformElement.GetAttribute("Name")); |
|
} |
|
return l.AsReadOnly(); |
|
} |
|
} |
|
} |
|
}
|
|
|