// // // // // $Revision$ // 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.Threading.Tasks; using System.Xml; using ICSharpCode.AvalonEdit.Highlighting; using ICSharpCode.Core; using ICSharpCode.SharpDevelop; using ICSharpCode.SharpDevelop.Dom; using ICSharpCode.SharpDevelop.Project; using Microsoft.Win32; namespace CppBackendBinding { /// /// C++ project class. Handlings project loading and saving. /// public class CppProject : AbstractProject, IProject { sealed class ReadOnlyConfigurationOrPlatformNameCollection : ImmutableModelCollection, IConfigurationOrPlatformNameCollection { public ReadOnlyConfigurationOrPlatformNameCollection(IEnumerable items) : base(items) { } public string ValidateName(string name) { return Contains(name) ? name : null; } public void Add(string newName, string copyFrom) { throw new NotSupportedException(); } public void Remove(string name) { throw new NotSupportedException(); } public void Rename(string oldName, string newName) { throw new NotSupportedException(); } } class CppProjectItemsCollection : SimpleModelCollection { readonly CppProject project; readonly bool internalUpdating; public CppProjectItemsCollection(CppProject project) { internalUpdating = true; this.project = project; this.AddRange(this.project.items.Select(item => item.ProjectItem)); internalUpdating = false; } protected override void OnAdd(ProjectItem item) { base.OnAdd(item); lock (project.SyncRoot) { if (!internalUpdating) { if (project.items.Exists(fi => fi.ProjectItem == item)) throw new ArgumentException("Project item already exists in project!"); project.items.Add(new FileItem(project.document, item)); } } } protected override void OnRemove(ProjectItem item) { base.OnRemove(item); lock (project.SyncRoot) { if (!internalUpdating) { var removedFileItems = new List(project.items.Where(fi => fi.ProjectItem == item)); foreach (var fileItem in removedFileItems) { if (fileItem.XmlElement.ParentNode != null) fileItem.XmlElement.ParentNode.RemoveChild(fileItem.XmlElement); project.items.Remove(fileItem); } } } } } XmlDocument document = new XmlDocument(); List groups = new List(); List items = new List(); CppProjectItemsCollection projectItems; /// /// Create a new C++ project that loads the specified .vcproj file. /// public CppProject(ProjectLoadInformation info) : base(info) { this.Name = info.ProjectName; this.FileName = info.FileName; 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()) { if (filterElement.Name == "Filter") { FileGroup group = new FileGroup(this, filterElement); groups.Add(group); foreach (XmlElement fileElement in filterElement.ChildNodes.OfType()) { if (fileElement.Name == "File" && fileElement.HasAttribute("RelativePath")) { items.Add(new FileItem(group, fileElement)); } } } } } this.projectItems = new CppProjectItemsCollection(this); } 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!"); } } watcher.Disable(); using (XmlWriter writer = XmlWriter.Create(fileName, new XmlWriterSettings { NewLineOnAttributes = true, Indent = true, IndentChars = "\t", Encoding = Encoding.Default })) { document.Save(writer); } watcher.Enable(); } } /// /// Gets the list of available file item types. This member is thread-safe. /// public override IReadOnlyCollection AvailableFileItemTypes { get { lock (SyncRoot) { return groups.ConvertAll(fg => fg.ItemType).AsReadOnly(); } } } /// /// Gets the list of items in the project. This member is thread-safe. /// public override IMutableModelCollection Items { get { lock (SyncRoot) { return projectItems; } } } 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); } 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 Task BuildAsync(ProjectBuildOptions options, IBuildFeedbackSink feedbackSink, IProgressMonitor progressMonitor) { TaskCompletionSource tcs = new TaskCompletionSource(); StartBuild(tcs, options, feedbackSink, progressMonitor); return tcs.Task; } void StartBuild(TaskCompletionSource tcs, ProjectBuildOptions options, IBuildFeedbackSink feedbackSink, IProgressMonitor progressMonitor) { string productDir = GetPathFromRegistry(@"SOFTWARE\Microsoft\VisualStudio\9.0\Setup\VC", "ProductDir"); string batFile = "vcvars32.bat"; if (options.Platform == "x64") { batFile = "amd64\\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; bool buildErrors = false; p.OutputDataReceived += delegate(object sender, DataReceivedEventArgs e) { if (!string.IsNullOrEmpty(e.Data)) { BuildError error = ParseError(e.Data); if (error != null) { feedbackSink.ReportError(error); buildErrors = true; } else { feedbackSink.ReportMessage(new RichText(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)); buildErrors = true; } }; p.Exited += delegate(object sender, EventArgs e) { p.CancelErrorRead(); p.CancelOutputRead(); progressMonitor.Progress = 1; p.Dispose(); tcs.SetResult(buildErrors); }; feedbackSink.ReportMessage(new RichText("Building " + this.Name)); feedbackSink.ReportMessage(new RichText(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 ); /// /// Parses an error or warning message and returns a BuildError object for it. /// 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 IConfigurationOrPlatformNameCollection PlatformNames { get { List l = new List(); foreach (XmlElement platformElement in document.DocumentElement["Platforms"]) { l.Add(platformElement.GetAttribute("Name")); } return new ReadOnlyConfigurationOrPlatformNameCollection(l); } } } }