OLD | NEW |
| (Empty) |
1 /// Copyright (c) Microsoft Corporation. All rights reserved. | |
2 | |
3 using System; | |
4 using System.Diagnostics; | |
5 using System.Globalization; | |
6 using System.IO; | |
7 using System.Runtime.InteropServices; | |
8 using Microsoft.VisualStudio; | |
9 using Microsoft.VisualStudio.Shell; | |
10 using Microsoft.VisualStudio.Shell.Interop; | |
11 | |
12 namespace Microsoft.VisualStudio.Project | |
13 { | |
14 [CLSCompliant(false), ComVisible(true)] | |
15 public class ProjectReferenceNode : ReferenceNode | |
16 { | |
17 #region fieds | |
18 /// <summary> | |
19 /// The name of the assembly this refernce represents | |
20 /// </summary> | |
21 private Guid referencedProjectGuid; | |
22 | |
23 private string referencedProjectName = String.Empty; | |
24 | |
25 private string referencedProjectRelativePath = String.Empty; | |
26 | |
27 private string referencedProjectFullPath = String.Empty; | |
28 | |
29 private BuildDependency buildDependency; | |
30 | |
31 /// <summary> | |
32 /// This is a reference to the automation object for the referen
ced project. | |
33 /// </summary> | |
34 private EnvDTE.Project referencedProject; | |
35 | |
36 /// <summary> | |
37 /// This state is controlled by the solution events. | |
38 /// The state is set to false by OnBeforeUnloadProject. | |
39 /// The state is set to true by OnBeforeCloseProject event. | |
40 /// </summary> | |
41 private bool canRemoveReference = true; | |
42 | |
43 /// <summary> | |
44 /// Possibility for solution listener to update the state on the
dangling reference. | |
45 /// It will be set in OnBeforeUnloadProject then the nopde is in
validated then it is reset to false. | |
46 /// </summary> | |
47 private bool isNodeValid; | |
48 | |
49 #endregion | |
50 | |
51 #region properties | |
52 | |
53 public override string Url | |
54 { | |
55 get | |
56 { | |
57 return this.referencedProjectFullPath; | |
58 } | |
59 } | |
60 | |
61 public override string Caption | |
62 { | |
63 get | |
64 { | |
65 return this.referencedProjectName; | |
66 } | |
67 } | |
68 | |
69 internal Guid ReferencedProjectGuid | |
70 { | |
71 get | |
72 { | |
73 return this.referencedProjectGuid; | |
74 } | |
75 } | |
76 | |
77 /// <summary> | |
78 /// Possiblity to shortcut and set the dangling project referenc
e icon. | |
79 /// It is ussually manipulated by solution listsneres who handle
reference updates. | |
80 /// </summary> | |
81 internal protected bool IsNodeValid | |
82 { | |
83 get | |
84 { | |
85 return this.isNodeValid; | |
86 } | |
87 set | |
88 { | |
89 this.isNodeValid = value; | |
90 } | |
91 } | |
92 | |
93 /// <summary> | |
94 /// Controls the state whether this reference can be removed or
not. Think of the project unload scenario where the project reference should not
be deleted. | |
95 /// </summary> | |
96 internal bool CanRemoveReference | |
97 { | |
98 get | |
99 { | |
100 return this.canRemoveReference; | |
101 } | |
102 set | |
103 { | |
104 this.canRemoveReference = value; | |
105 } | |
106 } | |
107 | |
108 internal string ReferencedProjectName | |
109 { | |
110 get { return this.referencedProjectName; } | |
111 } | |
112 | |
113 /// <summary> | |
114 /// Gets the automation object for the referenced project. | |
115 /// </summary> | |
116 internal EnvDTE.Project ReferencedProjectObject | |
117 { | |
118 get | |
119 { | |
120 // If the referenced project is null then re-rea
d. | |
121 if(this.referencedProject == null) | |
122 { | |
123 | |
124 // Search for the project in the collect
ion of the projects in the | |
125 // current solution. | |
126 EnvDTE.DTE dte = (EnvDTE.DTE)this.Projec
tMgr.GetService(typeof(EnvDTE.DTE)); | |
127 if((null == dte) || (null == dte.Solutio
n)) | |
128 { | |
129 return null; | |
130 } | |
131 foreach(EnvDTE.Project prj in dte.Soluti
on.Projects) | |
132 { | |
133 //Skip this project if it is an
umodeled project (unloaded) | |
134 if(string.Compare(EnvDTE.Constan
ts.vsProjectKindUnmodeled, prj.Kind, StringComparison.OrdinalIgnoreCase) == 0) | |
135 { | |
136 continue; | |
137 } | |
138 | |
139 // Get the full path of the curr
ent project. | |
140 EnvDTE.Property pathProperty = n
ull; | |
141 try | |
142 { | |
143 pathProperty = prj.Prope
rties.Item("FullPath"); | |
144 if(null == pathProperty) | |
145 { | |
146 // The full path
should alway be availabe, but if this is not the | |
147 // case then we
have to skip it. | |
148 continue; | |
149 } | |
150 } | |
151 catch(ArgumentException) | |
152 { | |
153 continue; | |
154 } | |
155 string prjPath = pathProperty.Va
lue.ToString(); | |
156 EnvDTE.Property fileNameProperty
= null; | |
157 // Get the name of the project f
ile. | |
158 try | |
159 { | |
160 fileNameProperty = prj.P
roperties.Item("FileName"); | |
161 if(null == fileNamePrope
rty) | |
162 { | |
163 // Again, this s
hould never be the case, but we handle it anyway. | |
164 continue; | |
165 } | |
166 } | |
167 catch(ArgumentException) | |
168 { | |
169 continue; | |
170 } | |
171 prjPath = System.IO.Path.Combine
(prjPath, fileNameProperty.Value.ToString()); | |
172 | |
173 // If the full path of this proj
ect is the same as the one of this | |
174 // reference, then we have found
the right project. | |
175 if(NativeMethods.IsSamePath(prjP
ath, referencedProjectFullPath)) | |
176 { | |
177 this.referencedProject =
prj; | |
178 break; | |
179 } | |
180 } | |
181 } | |
182 | |
183 return this.referencedProject; | |
184 } | |
185 set | |
186 { | |
187 this.referencedProject = value; | |
188 } | |
189 } | |
190 | |
191 /// <summary> | |
192 /// Gets the full path to the assembly generated by this project
. | |
193 /// </summary> | |
194 internal string ReferencedProjectOutputPath | |
195 { | |
196 get | |
197 { | |
198 // Make sure that the referenced project impleme
nts the automation object. | |
199 if(null == this.ReferencedProjectObject) | |
200 { | |
201 return null; | |
202 } | |
203 | |
204 // Get the configuration manager from the projec
t. | |
205 EnvDTE.ConfigurationManager confManager = this.R
eferencedProjectObject.ConfigurationManager; | |
206 if(null == confManager) | |
207 { | |
208 return null; | |
209 } | |
210 | |
211 // Get the active configuration. | |
212 EnvDTE.Configuration config = confManager.Active
Configuration; | |
213 if(null == config) | |
214 { | |
215 return null; | |
216 } | |
217 | |
218 // Get the output path for the current configura
tion. | |
219 EnvDTE.Property outputPathProperty = config.Prop
erties.Item("OutputPath"); | |
220 if(null == outputPathProperty) | |
221 { | |
222 return null; | |
223 } | |
224 | |
225 string outputPath = outputPathProperty.Value.ToS
tring(); | |
226 | |
227 // Ususally the output path is relative to the p
roject path, but it is possible | |
228 // to set it as an absolute path. If it is not a
bsolute, then evaluate its value | |
229 // based on the project directory. | |
230 if(!System.IO.Path.IsPathRooted(outputPath)) | |
231 { | |
232 string projectDir = System.IO.Path.GetDi
rectoryName(referencedProjectFullPath); | |
233 outputPath = System.IO.Path.Combine(proj
ectDir, outputPath); | |
234 } | |
235 | |
236 // Now get the name of the assembly from the pro
ject. | |
237 // Some project system throw if the property doe
s not exist. We expect an ArgumentException. | |
238 EnvDTE.Property assemblyNameProperty = null; | |
239 try | |
240 { | |
241 assemblyNameProperty = this.ReferencedPr
ojectObject.Properties.Item("OutputFileName"); | |
242 } | |
243 catch(ArgumentException) | |
244 { | |
245 } | |
246 | |
247 if(null == assemblyNameProperty) | |
248 { | |
249 return null; | |
250 } | |
251 // build the full path adding the name of the as
sembly to the output path. | |
252 outputPath = System.IO.Path.Combine(outputPath,
assemblyNameProperty.Value.ToString()); | |
253 | |
254 return outputPath; | |
255 } | |
256 } | |
257 | |
258 private Automation.OAProjectReference projectReference; | |
259 internal override object Object | |
260 { | |
261 get | |
262 { | |
263 if(null == projectReference) | |
264 { | |
265 projectReference = new Automation.OAProj
ectReference(this); | |
266 } | |
267 return projectReference; | |
268 } | |
269 } | |
270 #endregion | |
271 | |
272 #region ctors | |
273 /// <summary> | |
274 /// Constructor for the ReferenceNode. It is called when the pro
ject is reloaded, when the project element representing the refernce exists. | |
275 /// </summary> | |
276 [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usag
e", "CA2234:PassSystemUriObjectsInsteadOfStrings")] | |
277 public ProjectReferenceNode(ProjectNode root, ProjectElement ele
ment) | |
278 : base(root, element) | |
279 { | |
280 this.referencedProjectRelativePath = this.ItemNode.GetMe
tadata(ProjectFileConstants.Include); | |
281 Debug.Assert(!String.IsNullOrEmpty(this.referencedProjec
tRelativePath), "Could not retrive referenced project path form project file"); | |
282 | |
283 string guidString = this.ItemNode.GetMetadata(ProjectFil
eConstants.Project); | |
284 | |
285 // Continue even if project setttings cannot be read. | |
286 try | |
287 { | |
288 this.referencedProjectGuid = new Guid(guidString
); | |
289 | |
290 this.buildDependency = new BuildDependency(this.
ProjectMgr, this.referencedProjectGuid); | |
291 this.ProjectMgr.AddBuildDependency(this.buildDep
endency); | |
292 } | |
293 finally | |
294 { | |
295 Debug.Assert(this.referencedProjectGuid != Guid.
Empty, "Could not retrive referenced project guidproject file"); | |
296 | |
297 this.referencedProjectName = this.ItemNode.GetMe
tadata(ProjectFileConstants.Name); | |
298 | |
299 Debug.Assert(!String.IsNullOrEmpty(this.referenc
edProjectName), "Could not retrive referenced project name form project file"); | |
300 } | |
301 | |
302 Uri uri = new Uri(this.ProjectMgr.BaseURI.Uri, this.refe
rencedProjectRelativePath); | |
303 | |
304 if(uri != null) | |
305 { | |
306 this.referencedProjectFullPath = Microsoft.Visua
lStudio.Shell.Url.Unescape(uri.LocalPath, true); | |
307 } | |
308 } | |
309 | |
310 /// <summary> | |
311 /// constructor for the ProjectReferenceNode | |
312 /// </summary> | |
313 public ProjectReferenceNode(ProjectNode root, string referencedP
rojectName, string projectPath, string projectReference) | |
314 : base(root) | |
315 { | |
316 Debug.Assert(root != null && !String.IsNullOrEmpty(refer
encedProjectName) && !String.IsNullOrEmpty(projectReference) | |
317 && !String.IsNullOrEmpty(projectPath), "Can not
add a reference because the input for adding one is invalid."); | |
318 this.referencedProjectName = referencedProjectName; | |
319 | |
320 int indexOfSeparator = projectReference.IndexOf('|'); | |
321 | |
322 | |
323 string fileName = String.Empty; | |
324 | |
325 // Unfortunately we cannot use the path part of the proj
ectReference string since it is not resolving correctly relative pathes. | |
326 if(indexOfSeparator != -1) | |
327 { | |
328 string projectGuid = projectReference.Substring(
0, indexOfSeparator); | |
329 this.referencedProjectGuid = new Guid(projectGui
d); | |
330 if(indexOfSeparator + 1 < projectReference.Lengt
h) | |
331 { | |
332 string remaining = projectReference.Subs
tring(indexOfSeparator + 1); | |
333 indexOfSeparator = remaining.IndexOf('|'
); | |
334 | |
335 if(indexOfSeparator == -1) | |
336 { | |
337 fileName = remaining; | |
338 } | |
339 else | |
340 { | |
341 fileName = remaining.Substring(0
, indexOfSeparator); | |
342 } | |
343 } | |
344 } | |
345 | |
346 Debug.Assert(!String.IsNullOrEmpty(fileName), "Can not a
dd a project reference because the input for adding one is invalid."); | |
347 | |
348 // Did we get just a file or a relative path? | |
349 Uri uri = new Uri(projectPath); | |
350 | |
351 string referenceDir = PackageUtilities.GetPathDistance(t
his.ProjectMgr.BaseURI.Uri, uri); | |
352 | |
353 Debug.Assert(!String.IsNullOrEmpty(referenceDir), "Can n
ot add a project reference because the input for adding one is invalid."); | |
354 | |
355 string justTheFileName = Path.GetFileName(fileName); | |
356 this.referencedProjectRelativePath = Path.Combine(refere
nceDir, justTheFileName); | |
357 | |
358 this.referencedProjectFullPath = Path.Combine(projectPat
h, justTheFileName); | |
359 | |
360 this.buildDependency = new BuildDependency(this.ProjectM
gr, this.referencedProjectGuid); | |
361 | |
362 } | |
363 #endregion | |
364 | |
365 #region methods | |
366 protected override NodeProperties CreatePropertiesObject() | |
367 { | |
368 return new ProjectReferencesProperties(this); | |
369 } | |
370 | |
371 /// <summary> | |
372 /// The node is added to the hierarchy and then updates the buil
d dependency list. | |
373 /// </summary> | |
374 public override void AddReference() | |
375 { | |
376 if(this.ProjectMgr == null) | |
377 { | |
378 return; | |
379 } | |
380 base.AddReference(); | |
381 this.ProjectMgr.AddBuildDependency(this.buildDependency)
; | |
382 return; | |
383 } | |
384 | |
385 /// <summary> | |
386 /// Overridden method. The method updates the build dependency l
ist before removing the node from the hierarchy. | |
387 /// </summary> | |
388 public override void Remove(bool removeFromStorage) | |
389 { | |
390 if(this.ProjectMgr == null || !this.CanRemoveReference) | |
391 { | |
392 return; | |
393 } | |
394 this.ProjectMgr.RemoveBuildDependency(this.buildDependen
cy); | |
395 base.Remove(removeFromStorage); | |
396 return; | |
397 } | |
398 | |
399 /// <summary> | |
400 /// Links a reference node to the project file. | |
401 /// </summary> | |
402 protected override void BindReferenceData() | |
403 { | |
404 Debug.Assert(!String.IsNullOrEmpty(this.referencedProjec
tName), "The referencedProjectName field has not been initialized"); | |
405 Debug.Assert(this.referencedProjectGuid != Guid.Empty, "
The referencedProjectName field has not been initialized"); | |
406 | |
407 this.ItemNode = new ProjectElement(this.ProjectMgr, this
.referencedProjectRelativePath, ProjectFileConstants.ProjectReference); | |
408 | |
409 this.ItemNode.SetMetadata(ProjectFileConstants.Name, thi
s.referencedProjectName); | |
410 this.ItemNode.SetMetadata(ProjectFileConstants.Project,
this.referencedProjectGuid.ToString("B")); | |
411 this.ItemNode.SetMetadata(ProjectFileConstants.Private,
true.ToString()); | |
412 } | |
413 | |
414 /// <summary> | |
415 /// Defines whether this node is valid node for painting the ref
ererence icon. | |
416 /// </summary> | |
417 /// <returns></returns> | |
418 protected override bool CanShowDefaultIcon() | |
419 { | |
420 if(this.referencedProjectGuid == Guid.Empty || this.Proj
ectMgr == null || this.ProjectMgr.IsClosed || this.isNodeValid) | |
421 { | |
422 return false; | |
423 } | |
424 | |
425 IVsHierarchy hierarchy = null; | |
426 | |
427 hierarchy = VsShellUtilities.GetHierarchy(this.ProjectMg
r.Site, this.referencedProjectGuid); | |
428 | |
429 if(hierarchy == null) | |
430 { | |
431 return false; | |
432 } | |
433 | |
434 //If the Project is unloaded return false | |
435 if(this.ReferencedProjectObject == null) | |
436 { | |
437 return false; | |
438 } | |
439 | |
440 return (!String.IsNullOrEmpty(this.referencedProjectFull
Path) && File.Exists(this.referencedProjectFullPath)); | |
441 } | |
442 | |
443 /// <summary> | |
444 /// Checks if a project reference can be added to the hierarchy.
It calls base to see if the reference is not already there, then checks for cir
cular references. | |
445 /// </summary> | |
446 /// <param name="errorHandler">The error handler delegate to ret
urn</param> | |
447 /// <returns></returns> | |
448 protected override bool CanAddReference(out CannotAddReferenceEr
rorMessage errorHandler) | |
449 { | |
450 // When this method is called this refererence has not y
et been added to the hierarchy, only instantiated. | |
451 if(!base.CanAddReference(out errorHandler)) | |
452 { | |
453 return false; | |
454 } | |
455 | |
456 errorHandler = null; | |
457 if(this.IsThisProjectReferenceInCycle()) | |
458 { | |
459 errorHandler = new CannotAddReferenceErrorMessag
e(ShowCircularReferenceErrorMessage); | |
460 return false; | |
461 } | |
462 | |
463 return true; | |
464 } | |
465 | |
466 private bool IsThisProjectReferenceInCycle() | |
467 { | |
468 return IsReferenceInCycle(this.referencedProjectGuid); | |
469 } | |
470 | |
471 private void ShowCircularReferenceErrorMessage() | |
472 { | |
473 string message = String.Format(CultureInfo.CurrentCultur
e, SR.GetString(SR.ProjectContainsCircularReferences, CultureInfo.CurrentUICultu
re), this.referencedProjectName); | |
474 string title = string.Empty; | |
475 OLEMSGICON icon = OLEMSGICON.OLEMSGICON_CRITICAL; | |
476 OLEMSGBUTTON buttons = OLEMSGBUTTON.OLEMSGBUTTON_OK; | |
477 OLEMSGDEFBUTTON defaultButton = OLEMSGDEFBUTTON.OLEMSGDE
FBUTTON_FIRST; | |
478 VsShellUtilities.ShowMessageBox(this.ProjectMgr.Site, ti
tle, message, icon, buttons, defaultButton); | |
479 } | |
480 | |
481 /// <summary> | |
482 /// Recursively search if this project reference guid is in cycl
e. | |
483 /// </summary> | |
484 private bool IsReferenceInCycle(Guid projectGuid) | |
485 { | |
486 IVsHierarchy hierarchy = VsShellUtilities.GetHierarchy(t
his.ProjectMgr.Site, projectGuid); | |
487 | |
488 IReferenceContainerProvider provider = hierarchy as IRef
erenceContainerProvider; | |
489 if(provider != null) | |
490 { | |
491 IReferenceContainer referenceContainer = provide
r.GetReferenceContainer(); | |
492 | |
493 Debug.Assert(referenceContainer != null, "Could
not found the References virtual node"); | |
494 | |
495 foreach(ReferenceNode refNode in referenceContai
ner.EnumReferences()) | |
496 { | |
497 ProjectReferenceNode projRefNode = refNo
de as ProjectReferenceNode; | |
498 if(projRefNode != null) | |
499 { | |
500 if(projRefNode.ReferencedProject
Guid == this.ProjectMgr.ProjectIDGuid) | |
501 { | |
502 return true; | |
503 } | |
504 | |
505 if(this.IsReferenceInCycle(projR
efNode.ReferencedProjectGuid)) | |
506 { | |
507 return true; | |
508 } | |
509 } | |
510 } | |
511 } | |
512 | |
513 return false; | |
514 } | |
515 #endregion | |
516 } | |
517 | |
518 } | |
OLD | NEW |