2
// CustomToolService.cs
5
// Michael Hutchinson <mhutchinson@novell.com>
7
// Copyright (c) 2010 Novell, Inc.
9
// Permission is hereby granted, free of charge, to any person obtaining a copy
10
// of this software and associated documentation files (the "Software"), to deal
11
// in the Software without restriction, including without limitation the rights
12
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
13
// copies of the Software, and to permit persons to whom the Software is
14
// furnished to do so, subject to the following conditions:
16
// The above copyright notice and this permission notice shall be included in
17
// all copies or substantial portions of the Software.
19
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
22
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
23
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
24
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
28
using MonoDevelop.Ide.Extensions;
29
using System.Collections.Generic;
30
using MonoDevelop.Core;
32
using MonoDevelop.Projects;
34
using MonoDevelop.Ide.Tasks;
35
using System.CodeDom.Compiler;
36
using MonoDevelop.Ide.Gui;
37
using MonoDevelop.Core.ProgressMonitoring;
39
namespace MonoDevelop.Ide.CustomTools
41
public static class CustomToolService
43
static Dictionary<string,CustomToolExtensionNode> nodes = new Dictionary<string,CustomToolExtensionNode> ();
45
static Dictionary<string,IAsyncOperation> runningTasks = new Dictionary<string, IAsyncOperation> ();
47
static CustomToolService ()
49
AddinManager.AddExtensionNodeHandler ("/MonoDevelop/Ide/CustomTools", delegate(object sender, ExtensionNodeEventArgs args) {
50
var node = (CustomToolExtensionNode)args.ExtensionNode;
51
switch (args.Change) {
52
case ExtensionChange.Add:
53
if (nodes.ContainsKey (node.Name))
54
LoggingService.LogError ("Duplicate custom tool name '{0}'", node.Name);
56
nodes.Add (node.Name, node);
58
case ExtensionChange.Remove:
59
nodes.Remove (node.Name);
63
IdeApp.Workspace.FileChangedInProject += delegate (object sender, ProjectFileEventArgs e) {
64
Update (e.ProjectFile, false);
66
IdeApp.Workspace.FilePropertyChangedInProject += delegate (object sender, ProjectFileEventArgs e) {
67
Update (e.ProjectFile, false);
69
//FIXME: handle the rename
70
//MonoDevelop.Ide.Gui.IdeApp.Workspace.FileRenamedInProject
73
internal static void Init ()
75
//forces static ctor to run
78
static ISingleFileCustomTool GetGenerator (ProjectFile file)
80
CustomToolExtensionNode node;
81
if (!string.IsNullOrEmpty (file.Generator) && nodes.TryGetValue (file.Generator, out node))
86
public static void Update (ProjectFile file, bool force)
88
var tool = GetGenerator (file);
92
ProjectFile genFile = null;
93
if (!string.IsNullOrEmpty (file.LastGenOutput))
94
genFile = file.Project.Files.GetFile (file.FilePath.ParentDirectory.Combine (file.LastGenOutput));
96
if (!force && genFile != null && File.Exists (genFile.FilePath) &&
97
File.GetLastWriteTime (file.FilePath) < File.GetLastWriteTime (genFile.FilePath)) {
101
TaskService.Errors.ClearByOwner (file);
103
//if this file is already being run, cancel it
104
lock (runningTasks) {
105
IAsyncOperation runningTask;
106
if (runningTasks.TryGetValue (file.FilePath, out runningTask)) {
107
runningTask.Cancel ();
108
runningTasks.Remove (file.FilePath);
112
string title = GettextCatalog.GetString ("Custom Tool");
113
var monitor = IdeApp.Workbench.ProgressMonitors.GetOutputProgressMonitor (title, null, false, true);
114
var result = new SingleFileCustomToolResult ();
115
var aggOp = new AggregatedOperationMonitor (monitor);
117
monitor.BeginTask (GettextCatalog.GetString ("Running generator '{0}' on file '{1}'...", file.Generator, file.Name), 1);
118
var op = tool.Generate (monitor, file, result);
119
runningTasks.Add (file.FilePath, op);
120
aggOp.AddOperation (op);
121
op.Completed += delegate {
122
lock (runningTasks) {
123
IAsyncOperation runningTask;
124
if (runningTasks.TryGetValue (file.FilePath, out runningTask) && runningTask == op) {
125
runningTasks.Remove (file.FilePath);
126
UpdateCompleted (monitor, aggOp, file, genFile, result);
128
//it was cancelled because another was run for the same file, so just clean up
131
monitor.ReportWarning (GettextCatalog.GetString ("Cancelled because generator ran again for the same file"));
136
} catch (Exception ex) {
137
result.UnhandledException = ex;
138
UpdateCompleted (monitor, aggOp, file, genFile, result);
142
static void UpdateCompleted (IProgressMonitor monitor, AggregatedOperationMonitor aggOp,
143
ProjectFile file, ProjectFile genFile, SingleFileCustomToolResult result)
148
if (monitor.IsCancelRequested) {
149
monitor.ReportError (GettextCatalog.GetString ("Cancelled"), null);
159
if (result.UnhandledException != null) {
161
string msg = GettextCatalog.GetString ("The '{0}' code generator crashed", file.Generator);
162
result.Errors.Add (new CompilerError (file.Name, 0, 0, "", msg + ": " + result.UnhandledException.Message));
163
monitor.ReportError (msg, result.UnhandledException);
166
genFileName = result.GeneratedFilePath.IsNullOrEmpty?
167
null : result.GeneratedFilePath.ToRelative (file.FilePath.ParentDirectory);
169
bool validName = !string.IsNullOrEmpty (genFileName)
170
&& genFileName.IndexOfAny (new char[] { '/', '\\' }) < 0
171
&& FileService.IsValidFileName (genFileName);
175
string msg = GettextCatalog.GetString ("The '{0}' code generator output invalid filename '{1}'",
176
file.Generator, result.GeneratedFilePath);
177
result.Errors.Add (new CompilerError (file.Name, 0, 0, "", msg));
178
monitor.ReportError (msg, null);
181
if (result.Errors.Count > 0) {
182
foreach (CompilerError err in result.Errors)
183
TaskService.Errors.Add (new Task (file.FilePath, err.ErrorText, err.Column, err.Line,
184
err.IsWarning? TaskSeverity.Warning : TaskSeverity.Error,
185
TaskPriority.Normal, file.Project.ParentSolution, file));
192
monitor.ReportSuccess ("Generated file successfully.");
194
monitor.ReportError ("Failed to generate file. See error pad for details.", null);
200
if (!result.GeneratedFilePath.IsNullOrEmpty && File.Exists (result.GeneratedFilePath)) {
201
if (genFile == null) {
202
genFile = file.Project.AddFile (result.GeneratedFilePath);
203
} else if (result.GeneratedFilePath != genFile.FilePath) {
204
genFile.Name = result.GeneratedFilePath;
206
file.LastGenOutput = genFileName;
207
genFile.DependsOn = file.FilePath.FileName;
211
public static void HandleRename (ProjectFileRenamedEventArgs args)
213
var file = args.ProjectFile;
214
var tool = GetGenerator (file);