4
// Lluis Sanchez Gual <lluis@novell.com>
5
// Viktoria Dudka <viktoriad@remobjects.com>
7
// Copyright (c) 2009 Novell, Inc (http://www.novell.com)
8
// Copyright (c) 2009 RemObjects Software
10
// Permission is hereby granted, free of charge, to any person obtaining a copy
11
// of this software and associated documentation files (the "Software"), to deal
12
// in the Software without restriction, including without limitation the rights
13
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
14
// copies of the Software, and to permit persons to whom the Software is
15
// furnished to do so, subject to the following conditions:
17
// The above copyright notice and this permission notice shall be included in
18
// all copies or substantial portions of the Software.
20
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
23
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
25
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
32
using System.Collections;
33
using System.Collections.Generic;
36
using MonoDevelop.Core;
37
using MonoDevelop.Core.Serialization;
38
using MonoDevelop.Projects;
39
using MonoDevelop.Projects.Dom.Output;
42
namespace MonoDevelop.Projects
44
public enum NewFileSearch
51
[DataInclude(typeof(ProjectFile))]
52
[DataItem(FallbackType = typeof(UnknownProject))]
53
public abstract class Project : SolutionEntityItem
55
string[] buildActions;
60
FileService.FileChanged += OnFileChanged;
61
files = new ProjectFileCollection ();
63
DependencyResolutionEnabled = true;
66
[ItemProperty("Description", DefaultValue = "")]
67
private string description = "";
68
public string Description {
69
get { return description; }
72
NotifyModified ("Description");
76
public virtual bool IsCompileable (string fileName)
81
private ProjectFileCollection files;
82
public ProjectFileCollection Files {
86
[ItemProperty("newfilesearch", DefaultValue = NewFileSearch.None)]
87
protected NewFileSearch newFileSearch = NewFileSearch.None;
88
public NewFileSearch NewFileSearch {
89
get { return newFileSearch; }
92
newFileSearch = value;
93
NotifyModified ("NewFileSearch");
97
public abstract string ProjectType {
101
string stockIcon = "md-project";
102
public virtual string StockIcon {
103
get { return stockIcon; }
104
set { this.stockIcon = value; }
107
public virtual Ambience Ambience {
108
get { return new NetAmbience (); }
111
public virtual string[] SupportedLanguages {
112
get { return new String[] { "" }; }
115
public virtual string GetDefaultBuildAction (string fileName)
117
return IsCompileable (fileName) ? BuildAction.Compile : BuildAction.None;
120
public ProjectFile GetProjectFile (string fileName)
122
return files.GetFile (fileName);
125
public bool IsFileInProject (string fileName)
127
return files.GetFile (fileName) != null;
130
//NOTE: groups the common actions at the top, separated by a "--" entry *IF* there are
131
// more "uncommon" actions than "common" actions
132
public string[] GetBuildActions ()
134
if (buildActions != null)
137
// find all the actions in use and add them to the list of standard actions
138
Hashtable actions = new Hashtable ();
139
object marker = new object (); //avoid using bools as they need to be boxed. re-use single object instead
140
//ad the standard actions
141
foreach (string action in GetStandardBuildActions ())
142
actions[action] = marker;
144
//add any more actions that are in the project file
145
foreach (ProjectFile pf in files)
146
if (!actions.ContainsKey (pf.BuildAction))
147
actions[pf.BuildAction] = marker;
149
//remove the "common" actions, since they're handled separately
150
IList<string> commonActions = GetCommonBuildActions ();
151
foreach (string action in commonActions)
152
if (actions.Contains (action))
153
actions.Remove (action);
155
//calculate dimensions for our new array and create it
156
int dashPos = commonActions.Count;
157
bool hasDash = commonActions.Count > 0 && actions.Count > 0;
158
int arrayLen = commonActions.Count + actions.Count;
159
int uncommonStart = hasDash ? dashPos + 1 : dashPos;
162
buildActions = new string[arrayLen];
165
if (commonActions.Count > 0)
166
commonActions.CopyTo (buildActions, 0);
168
buildActions[dashPos] = "--";
169
if (actions.Count > 0)
170
actions.Keys.CopyTo (buildActions, uncommonStart);
174
//it may be better to leave common actions in the order that the project specified
175
//Array.Sort (buildActions, 0, commonActions.Count, StringComparer.Ordinal);
176
Array.Sort (buildActions, uncommonStart, arrayLen - uncommonStart, StringComparer.Ordinal);
178
Array.Sort (buildActions, StringComparer.Ordinal);
183
protected virtual IEnumerable<string> GetStandardBuildActions ()
185
return BuildAction.StandardActions;
188
protected virtual IList<string> GetCommonBuildActions ()
190
return BuildAction.StandardActions;
193
public static Project LoadProject (string filename, IProgressMonitor monitor)
195
Project prj = Services.ProjectService.ReadSolutionItem (monitor, filename) as Project;
197
throw new InvalidOperationException ("Invalid project file: " + filename);
203
public override void Dispose ()
205
FileService.FileChanged -= OnFileChanged;
206
foreach (ProjectFile file in Files) {
212
public ProjectFile AddFile (string filename)
214
return AddFile (filename, null);
217
public ProjectFile AddFile (string filename, string buildAction)
219
foreach (ProjectFile fInfo in Files) {
220
if (fInfo.Name == filename) {
225
if (String.IsNullOrEmpty (buildAction)) {
226
buildAction = GetDefaultBuildAction (filename);
229
ProjectFile newFileInformation = new ProjectFile (filename, buildAction);
230
Files.Add (newFileInformation);
231
return newFileInformation;
234
public void AddFile (ProjectFile projectFile)
236
Files.Add (projectFile);
239
public ProjectFile AddDirectory (string relativePath)
241
string newPath = Path.Combine (BaseDirectory, relativePath);
243
foreach (ProjectFile fInfo in Files)
244
if (fInfo.Name == newPath && fInfo.Subtype == Subtype.Directory)
247
if (!Directory.Exists (newPath)) {
248
if (File.Exists (newPath)) {
249
string message = GettextCatalog.GetString ("Cannot create directory {0}, as a file with that name exists.", newPath);
250
throw new InvalidOperationException (message);
252
FileService.CreateDirectory (newPath);
255
ProjectFile newDir = new ProjectFile (newPath);
256
newDir.Subtype = Subtype.Directory;
261
protected internal override BuildResult OnBuild (IProgressMonitor monitor, ConfigurationSelector configuration)
263
// create output directory, if not exists
264
ProjectConfiguration conf = GetConfiguration (configuration) as ProjectConfiguration;
266
BuildResult cres = new BuildResult ();
267
cres.AddError (GettextCatalog.GetString ("Configuration '{0}' not found in project '{1}'", configuration.ToString (), Name));
270
string outputDir = conf.OutputDirectory;
272
DirectoryInfo directoryInfo = new DirectoryInfo (outputDir);
273
if (!directoryInfo.Exists) {
274
directoryInfo.Create ();
276
} catch (Exception e) {
277
throw new ApplicationException ("Can't create project output directory " + outputDir + " original exception:\n" + e.ToString ());
280
//copy references and files marked to "CopyToOutputDirectory"
281
CopySupportFiles (monitor, configuration);
283
StringParserService.Properties["Project"] = Name;
285
monitor.Log.WriteLine (GettextCatalog.GetString ("Performing main compilation..."));
286
BuildResult res = DoBuild (monitor, configuration);
291
string errorString = GettextCatalog.GetPluralString ("{0} error", "{0} errors", res.ErrorCount, res.ErrorCount);
292
string warningString = GettextCatalog.GetPluralString ("{0} warning", "{0} warnings", res.WarningCount, res.WarningCount);
294
monitor.Log.WriteLine (GettextCatalog.GetString ("Build complete -- ") + errorString + ", " + warningString);
300
public void CopySupportFiles (IProgressMonitor monitor, ConfigurationSelector configuration)
302
ProjectConfiguration config = (ProjectConfiguration) GetConfiguration (configuration);
304
foreach (FileCopySet.Item item in GetSupportFileList (configuration)) {
305
string dest = Path.GetFullPath (Path.Combine (config.OutputDirectory, item.Target));
306
string src = Path.GetFullPath (item.Src);
312
if (item.CopyOnlyIfNewer && File.Exists (dest) && (File.GetLastWriteTimeUtc (dest) >= File.GetLastWriteTimeUtc (src)))
315
if (!Directory.Exists (Path.GetDirectoryName (dest)))
316
FileService.CreateDirectory (Path.GetDirectoryName (dest));
318
if (File.Exists (src))
319
FileService.CopyFile (src, dest);
321
monitor.ReportError (GettextCatalog.GetString ("Could not find support file '{0}'.", src), null);
323
} catch (IOException ex) {
324
monitor.ReportError (GettextCatalog.GetString ("Error copying support file '{0}'.", dest), ex);
329
public void DeleteSupportFiles (IProgressMonitor monitor, ConfigurationSelector configuration)
331
ProjectConfiguration config = (ProjectConfiguration) GetConfiguration (configuration);
333
foreach (FileCopySet.Item item in GetSupportFileList (configuration)) {
334
string dest = Path.Combine (config.OutputDirectory, item.Target);
336
// Ignore files which were not copied
337
if (Path.GetFullPath (dest) == Path.GetFullPath (item.Src))
341
if (File.Exists (dest)) {
342
FileService.DeleteFile (dest);
344
} catch (IOException ex) {
345
monitor.ReportError (GettextCatalog.GetString ("Error deleting support file '{0}'.", dest), ex);
350
public FileCopySet GetSupportFileList (ConfigurationSelector configuration)
352
FileCopySet list = new FileCopySet ();
353
PopulateSupportFileList (list, configuration);
357
protected virtual void PopulateSupportFileList (FileCopySet list, ConfigurationSelector configuration)
359
foreach (ProjectFile pf in Files) {
360
if (pf.CopyToOutputDirectory == FileCopyMode.None)
362
FilePath outpath = pf.IsExternalToProject ? (FilePath)pf.FilePath.FileName : pf.RelativePath;
363
list.Add (pf.FilePath, pf.CopyToOutputDirectory == FileCopyMode.PreserveNewest, outpath);
367
protected virtual BuildResult DoBuild (IProgressMonitor monitor, ConfigurationSelector configuration)
369
BuildResult res = ItemHandler.RunTarget (monitor, "Build", configuration);
370
return res ?? new BuildResult ();
373
protected internal override void OnClean (IProgressMonitor monitor, ConfigurationSelector configuration)
376
ProjectConfiguration config = GetConfiguration (configuration) as ProjectConfiguration;
377
if (config == null) {
378
monitor.ReportError (GettextCatalog.GetString ("Configuration '{0}' not found in project '{1}'", config.Id, Name), null);
382
// Delete the generated assembly
383
string file = GetOutputFileName (configuration);
385
if (File.Exists (file))
386
FileService.DeleteFile (file);
389
DeleteSupportFiles (monitor, configuration);
391
DoClean (monitor, config.Selector);
394
protected virtual void DoClean (IProgressMonitor monitor, ConfigurationSelector configuration)
396
ItemHandler.RunTarget (monitor, "Clean", configuration);
399
void GetBuildableReferencedItems (List<SolutionItem> referenced, SolutionItem item, ConfigurationSelector configuration)
401
if (referenced.Contains (item))
404
if (item.NeedsBuilding (configuration))
405
referenced.Add (item);
407
foreach (SolutionItem ritem in item.GetReferencedItems (configuration))
408
GetBuildableReferencedItems (referenced, ritem, configuration);
411
protected internal override void OnExecute (IProgressMonitor monitor, ExecutionContext context, ConfigurationSelector configuration)
413
ProjectConfiguration config = GetConfiguration (configuration) as ProjectConfiguration;
414
if (config == null) {
415
monitor.ReportError (GettextCatalog.GetString ("Configuration '{0}' not found in project '{1}'", configuration, Name), null);
418
DoExecute (monitor, context, configuration);
421
protected virtual void DoExecute (IProgressMonitor monitor, ExecutionContext context, ConfigurationSelector configuration)
425
public virtual FilePath GetOutputFileName (ConfigurationSelector configuration)
427
return FilePath.Null;
430
protected internal override bool OnGetNeedsBuilding (ConfigurationSelector configuration)
433
if (CheckNeedsBuild (configuration))
439
protected internal override void OnSetNeedsBuilding (bool value, ConfigurationSelector configuration)
450
protected virtual bool CheckNeedsBuild (ConfigurationSelector configuration)
452
DateTime tim = GetLastBuildTime (configuration);
453
if (tim == DateTime.MinValue)
456
foreach (ProjectFile file in Files) {
457
if (file.BuildAction == BuildAction.Content || file.BuildAction == BuildAction.None)
460
if (File.GetLastWriteTime (file.FilePath) > tim)
462
} catch (IOException) {
467
foreach (SolutionItem pref in GetReferencedItems (configuration)) {
468
if (pref.GetLastBuildTime (configuration) > tim || pref.NeedsBuilding (configuration))
473
if (File.GetLastWriteTime (FileName) > tim)
482
protected internal override DateTime OnGetLastBuildTime (ConfigurationSelector configuration)
484
string file = GetOutputFileName (configuration);
486
return DateTime.MinValue;
488
FileInfo finfo = new FileInfo (file);
490
return DateTime.MinValue;
492
return finfo.LastWriteTime;
495
internal virtual void OnFileChanged (object source, FileEventArgs e)
497
ProjectFile file = GetProjectFile (e.FileName);
501
NotifyFileChangedInProject (file);
503
// Workaround Mono bug. The watcher seems to
504
// stop watching if an exception is thrown in
511
protected internal override List<FilePath> OnGetItemFiles (bool includeReferencedFiles)
513
List<FilePath> col = base.OnGetItemFiles (includeReferencedFiles);
514
if (includeReferencedFiles) {
515
foreach (ProjectFile pf in Files) {
516
if (pf.Subtype != Subtype.Directory)
517
col.Add (pf.FilePath);
523
protected internal override void OnItemAdded (object obj)
525
base.OnItemAdded (obj);
526
if (obj is ProjectFile)
527
NotifyFileAddedToProject ((ProjectFile)obj);
530
protected internal override void OnItemRemoved (object obj)
532
base.OnItemRemoved (obj);
533
if (obj is ProjectFile)
534
NotifyFileRemovedFromProject ((ProjectFile)obj);
537
internal void NotifyFileChangedInProject (ProjectFile file)
539
OnFileChangedInProject (new ProjectFileEventArgs (this, file));
542
internal void NotifyFilePropertyChangedInProject (ProjectFile file)
544
NotifyModified ("Files");
545
OnFilePropertyChangedInProject (new ProjectFileEventArgs (this, file));
548
List<ProjectFile> unresolvedDeps;
550
void NotifyFileRemovedFromProject (ProjectFile file)
552
file.SetProject (null);
554
if (DependencyResolutionEnabled) {
555
if (unresolvedDeps.Contains (file))
556
unresolvedDeps.Remove (file);
557
foreach (ProjectFile f in file.DependentChildren) {
558
f.DependsOnFile = null;
559
if (!string.IsNullOrEmpty (f.DependsOn))
560
unresolvedDeps.Add (f);
562
file.DependsOnFile = null;
566
NotifyModified ("Files");
567
OnFileRemovedFromProject (new ProjectFileEventArgs (this, file));
570
void NotifyFileAddedToProject (ProjectFile file)
572
if (file.Project != null)
573
throw new InvalidOperationException ("ProjectFile already belongs to a project");
574
file.SetProject (this);
576
ResolveDependencies (file);
579
NotifyModified ("Files");
580
OnFileAddedToProject (new ProjectFileEventArgs (this, file));
583
internal void ResolveDependencies (ProjectFile file)
585
if (!DependencyResolutionEnabled)
588
if (!file.ResolveParent ())
589
unresolvedDeps.Add (file);
591
List<ProjectFile> resolved = null;
592
foreach (ProjectFile unres in unresolvedDeps) {
593
if (string.IsNullOrEmpty (unres.DependsOn)) {
594
resolved.Add (unres);
596
if (unres.ResolveParent ()) {
597
if (resolved == null)
598
resolved = new List<ProjectFile> ();
599
resolved.Add (unres);
602
if (resolved != null)
603
foreach (ProjectFile pf in resolved)
604
unresolvedDeps.Remove (pf);
607
bool DependencyResolutionEnabled {
609
get { return unresolvedDeps != null; }
612
if (unresolvedDeps != null)
614
unresolvedDeps = new List<ProjectFile> ();
615
foreach (ProjectFile file in files)
616
ResolveDependencies (file);
618
unresolvedDeps = null;
623
internal void NotifyFileRenamedInProject (ProjectFileRenamedEventArgs args)
626
NotifyModified ("Files");
627
OnFileRenamedInProject (args);
630
protected virtual void OnFileRemovedFromProject (ProjectFileEventArgs e)
633
if (FileRemovedFromProject != null) {
634
FileRemovedFromProject (this, e);
638
protected virtual void OnFileAddedToProject (ProjectFileEventArgs e)
641
if (FileAddedToProject != null) {
642
FileAddedToProject (this, e);
646
protected virtual void OnFileChangedInProject (ProjectFileEventArgs e)
648
if (FileChangedInProject != null) {
649
FileChangedInProject (this, e);
653
protected virtual void OnFilePropertyChangedInProject (ProjectFileEventArgs e)
656
if (FilePropertyChangedInProject != null) {
657
FilePropertyChangedInProject (this, e);
661
protected virtual void OnFileRenamedInProject (ProjectFileRenamedEventArgs e)
663
if (FileRenamedInProject != null) {
664
FileRenamedInProject (this, e);
668
public event ProjectFileEventHandler FileRemovedFromProject;
669
public event ProjectFileEventHandler FileAddedToProject;
670
public event ProjectFileEventHandler FileChangedInProject;
671
public event ProjectFileEventHandler FilePropertyChangedInProject;
672
public event ProjectFileRenamedEventHandler FileRenamedInProject;
677
public class UnknownProject : Project
679
public override string ProjectType {
683
public override SolutionItemConfiguration CreateConfiguration (string name)
688
internal protected override bool OnGetCanExecute (ExecutionContext context, ConfigurationSelector configuration)
693
internal protected override BuildResult OnBuild (IProgressMonitor monitor, ConfigurationSelector configuration)
695
BuildResult res = new BuildResult ();
696
res.AddError ("Unknown project type");
701
public delegate void ProjectEventHandler (Object sender, ProjectEventArgs e);
702
public class ProjectEventArgs : EventArgs
704
public ProjectEventArgs (Project project)
706
this.project = project;
709
private Project project;
710
public Project Project {
711
get { return project; }