-
Notifications
You must be signed in to change notification settings - Fork 1.4k
/
Project.cs
4771 lines (4189 loc) · 241 KB
/
Project.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Linq;
using System.Text;
using System.Xml;
using Microsoft.Build.BackEnd;
using Microsoft.Build.Collections;
using ObjectModel = System.Collections.ObjectModel;
using Microsoft.Build.Construction;
using Microsoft.Build.Execution;
using Microsoft.Build.Framework;
using Microsoft.Build.Shared;
using Constants = Microsoft.Build.Internal.Constants;
using ForwardingLoggerRecord = Microsoft.Build.Logging.ForwardingLoggerRecord;
using ILoggingService = Microsoft.Build.BackEnd.Logging.ILoggingService;
using InvalidProjectFileException = Microsoft.Build.Exceptions.InvalidProjectFileException;
using ProjectItemFactory = Microsoft.Build.Evaluation.ProjectItem.ProjectItemFactory;
using System.Globalization;
using Microsoft.Build.BackEnd.Logging;
using Microsoft.Build.Definition;
using Microsoft.Build.Evaluation.Context;
using Microsoft.Build.Globbing;
using Microsoft.Build.ObjectModelRemoting;
using EvaluationItemSpec = Microsoft.Build.Evaluation.ItemSpec<Microsoft.Build.Evaluation.ProjectProperty, Microsoft.Build.Evaluation.ProjectItem>;
using EvaluationItemExpressionFragment = Microsoft.Build.Evaluation.ItemSpec<Microsoft.Build.Evaluation.ProjectProperty, Microsoft.Build.Evaluation.ProjectItem>.ItemExpressionFragment;
using SdkResult = Microsoft.Build.BackEnd.SdkResolution.SdkResult;
using Microsoft.Build.FileSystem;
#nullable disable
namespace Microsoft.Build.Evaluation
{
using Utilities = Microsoft.Build.Internal.Utilities;
/// <summary>
/// Represents an evaluated project with design time semantics.
/// Always backed by XML; can be built directly, or an instance can be cloned off to add virtual items/properties and build.
/// Edits to this project always update the backing XML.
/// </summary>
// UNDONE: (Multiple configurations.) Protect against problems when attempting to edit, after edits were made to the same ProjectRootElement either directly or through other projects evaluated from that ProjectRootElement.
[DebuggerDisplay("{FullPath} EffectiveToolsVersion={ToolsVersion} #GlobalProperties={implementation._data.GlobalPropertiesDictionary.Count} #Properties={implementation._data.Properties.Count} #ItemTypes={implementation._data.ItemTypes.Count} #ItemDefinitions={implementation._data.ItemDefinitions.Count} #Items={implementation._data.Items.Count} #Targets={implementation._data.Targets.Count}")]
public class Project : ILinkableObject
{
/// <summary>
/// Whether to write information about why we evaluate to debug output.
/// </summary>
private static readonly bool s_debugEvaluation = (Environment.GetEnvironmentVariable("MSBUILDDEBUGEVALUATION") != null);
/// <summary>
/// * and ? are invalid file name characters, but they occur in globs as wild cards.
/// </summary>
private static readonly char[] s_invalidGlobChars = FileUtilities.InvalidFileNameChars.Where(c => c != '*' && c != '?' && c!= '/' && c != '\\' && c != ':').ToArray();
/// <summary>
/// Context to log messages and events in.
/// </summary>
private static readonly BuildEventContext s_buildEventContext = new BuildEventContext(0 /* node ID */, BuildEventContext.InvalidTargetId, BuildEventContext.InvalidProjectContextId, BuildEventContext.InvalidTaskId);
private ProjectLink implementation;
private IProjectLinkInternal implementationInternal;
internal bool IsLinked => implementationInternal.IsLinked;
internal ProjectLink Link => implementation;
object ILinkableObject.Link => IsLinked ? Link : null;
/// <summary>
/// Host-provided factory for <see cref="IDirectoryCache"/> interfaces to be used during evaluation.
/// </summary>
private readonly IDirectoryCacheFactory _directoryCacheFactory;
/// <summary>
/// Default project template options (include all features).
/// </summary>
internal const NewProjectFileOptions DefaultNewProjectTemplateOptions = NewProjectFileOptions.IncludeAllOptions;
/// <summary>
/// Certain item operations split the item element in multiple elements if the include
/// contains globs, references to items or properties, or multiple item values.
///
/// The items operations that may expand item elements are:
/// - <see cref="RemoveItem"/>
/// - <see cref="RemoveItems"/>
/// - <see cref="AddItem(string,string, IEnumerable<KeyValuePair<string, string>>)"/>
/// - <see cref="AddItemFast(string,string, IEnumerable<KeyValuePair<string, string>>)"/>
/// - <see cref="ProjectItem.ChangeItemType"/>
/// - <see cref="ProjectItem.Rename"/>
/// - <see cref="ProjectItem.RemoveMetadata"/>
/// - <see cref="ProjectItem.SetMetadataValue(string,string)"/>
/// - <see cref="ProjectItem.SetMetadataValue(string,string, bool)"/>
///
/// When this property is set to true, the previous item operations throw an <see cref="InvalidOperationException" />
/// instead of expanding the item element.
/// </summary>
public bool ThrowInsteadOfSplittingItemElement
{
[DebuggerStepThrough]
get => implementation.ThrowInsteadOfSplittingItemElement;
[DebuggerStepThrough]
set => implementation.ThrowInsteadOfSplittingItemElement = value;
}
internal Project(ProjectCollection projectCollection, ProjectLink link)
{
ErrorUtilities.VerifyThrowArgumentNull(projectCollection, nameof(projectCollection));
ErrorUtilities.VerifyThrowArgumentNull(link, nameof(link));
ProjectCollection = projectCollection;
implementationInternal = new ProjectLinkInternalNotImplemented();
implementation = link;
}
/// <summary>
/// Construct an empty project, evaluating with the global project collection's
/// global properties and default tools version.
/// Project will be added to the global project collection when it is named.
/// </summary>
public Project()
: this(DefaultNewProjectTemplateOptions)
{
}
/// <summary>
/// Construct an empty project, evaluating with the global project collection's
/// global properties and default tools version.
/// Project will be added to the global project collection when it is named.
/// </summary>
public Project(NewProjectFileOptions newProjectFileOptions)
: this(ProjectRootElement.Create(ProjectCollection.GlobalProjectCollection, newProjectFileOptions))
{
}
/// <summary>
/// Construct an empty project, evaluating with the specified project collection's
/// global properties and default tools version.
/// Project will be added to the specified project collection when it is named.
/// </summary>
public Project(ProjectCollection projectCollection)
: this(ProjectRootElement.Create(projectCollection), null, null, projectCollection)
{
}
/// <summary>
/// Construct an empty project, evaluating with the specified project collection's
/// global properties and default tools version.
/// Project will be added to the specified project collection when it is named.
/// </summary>
public Project(ProjectCollection projectCollection, NewProjectFileOptions newProjectFileOptions)
: this(ProjectRootElement.Create(projectCollection, newProjectFileOptions), null, null, projectCollection)
{
}
/// <summary>
/// Construct an empty project, evaluating with the specified project collection and
/// the specified global properties and default tools version, either of which may be null.
/// Project will be added to the specified project collection when it is named.
/// </summary>
/// <param name="globalProperties">Global properties to evaluate with. May be null in which case the containing project collection's global properties will be used.</param>
/// <param name="toolsVersion">Tools version to evaluate with. May be null.</param>
/// <param name="projectCollection">The <see cref="ProjectCollection"/> the project is added to.</param>
public Project(IDictionary<string, string> globalProperties, string toolsVersion, ProjectCollection projectCollection)
: this(ProjectRootElement.Create(projectCollection, DefaultNewProjectTemplateOptions), globalProperties, toolsVersion, projectCollection)
{
}
/// <summary>
/// Construct an empty project, evaluating with the specified project collection and
/// the specified global properties and default tools version, either of which may be null.
/// Project will be added to the specified project collection when it is named.
/// </summary>
/// <param name="globalProperties">Global properties to evaluate with. May be null in which case the containing project collection's global properties will be used.</param>
/// <param name="toolsVersion">Tools version to evaluate with. May be null.</param>
/// <param name="projectCollection">The <see cref="ProjectCollection"/> the project is added to.</param>
/// <param name="newProjectFileOptions">The <see cref="NewProjectFileOptions"/> to use for the new project.</param>
public Project(IDictionary<string, string> globalProperties, string toolsVersion, ProjectCollection projectCollection, NewProjectFileOptions newProjectFileOptions)
: this(ProjectRootElement.Create(projectCollection, newProjectFileOptions), globalProperties, toolsVersion, projectCollection)
{
}
/// <summary>
/// Construct over a ProjectRootElement object, evaluating with the global project collection's
/// global properties and default tools version.
/// Project is added to the global project collection if it has a name, or else when it is named.
/// Throws InvalidProjectFileException if the evaluation fails.
/// Throws InvalidOperationException if there is already an equivalent project loaded in the project collection.
/// </summary>
/// <param name="xml">ProjectRootElement to use.</param>
public Project(ProjectRootElement xml)
: this(xml, null, null)
{
}
/// <summary>
/// Construct over a ProjectRootElement object, evaluating with specified
/// global properties and toolset, either or both of which may be null.
/// Project is added to the global project collection if it has a name, or else when it is named.
/// Throws InvalidProjectFileException if the evaluation fails.
/// Throws InvalidOperationException if there is already an equivalent project loaded in the project collection.
/// </summary>
/// <param name="xml">ProjectRootElement to use.</param>
/// <param name="globalProperties">Global properties to evaluate with. May be null in which case the containing project collection's global properties will be used.</param>
/// <param name="toolsVersion">Tools version to evaluate with. May be null.</param>
public Project(ProjectRootElement xml, IDictionary<string, string> globalProperties, string toolsVersion)
: this(xml, globalProperties, toolsVersion, ProjectCollection.GlobalProjectCollection)
{
}
/// <summary>
/// Construct over a ProjectRootElement object, evaluating with specified
/// global properties and toolset, either or both of which may be null.
/// Project is added to the global project collection if it has a name, or else when it is named.
/// Throws InvalidProjectFileException if the evaluation fails.
/// Throws InvalidOperationException if there is already an equivalent project loaded in the project collection.
/// </summary>
/// <param name="xml">ProjectRootElement to use.</param>
/// <param name="globalProperties">Global properties to evaluate with. May be null in which case the containing project collection's global properties will be used.</param>
/// <param name="toolsVersion">Tools version to evaluate with. May be null.</param>
/// <param name="projectCollection">The <see cref="ProjectCollection"/> the project is added to.</param>
public Project(ProjectRootElement xml, IDictionary<string, string> globalProperties, string toolsVersion, ProjectCollection projectCollection)
: this(xml, globalProperties, toolsVersion, projectCollection, ProjectLoadSettings.Default)
{
}
/// <summary>
/// Construct over a ProjectRootElement object, evaluating with specified
/// global properties and toolset, either or both of which may be null.
/// Project is added to the global project collection if it has a name, or else when it is named.
/// Throws InvalidProjectFileException if the evaluation fails.
/// Throws InvalidOperationException if there is already an equivalent project loaded in the project collection.
/// </summary>
/// <param name="xml">ProjectRootElement to use.</param>
/// <param name="globalProperties">Global properties to evaluate with. May be null in which case the containing project collection's global properties will be used.</param>
/// <param name="toolsVersion">Tools version to evaluate with. May be null.</param>
/// <param name="projectCollection">The <see cref="ProjectCollection"/> the project is added to.</param>
/// <param name="loadSettings">The <see cref="ProjectLoadSettings"/> to use for evaluation.</param>
public Project(ProjectRootElement xml, IDictionary<string, string> globalProperties, string toolsVersion, ProjectCollection projectCollection, ProjectLoadSettings loadSettings)
: this(xml, globalProperties, toolsVersion, null /* no explicit sub-toolset version */, projectCollection, loadSettings)
{
}
/// <summary>
/// Construct over a ProjectRootElement object, evaluating with specified
/// global properties and toolset, either or both of which may be null.
/// Project is added to the global project collection if it has a name, or else when it is named.
/// Throws InvalidProjectFileException if the evaluation fails.
/// Throws InvalidOperationException if there is already an equivalent project loaded in the project collection.
/// </summary>
/// <param name="xml">ProjectRootElement to use.</param>
/// <param name="globalProperties">Global properties to evaluate with. May be null in which case the containing project collection's global properties will be used.</param>
/// <param name="toolsVersion">Tools version to evaluate with. May be null.</param>
/// <param name="subToolsetVersion">Sub-toolset version to explicitly evaluate the toolset with. May be null.</param>
/// <param name="projectCollection">The <see cref="ProjectCollection"/> the project is added to.</param>
/// <param name="loadSettings">The <see cref="ProjectLoadSettings"/> to use for evaluation.</param>
public Project(ProjectRootElement xml, IDictionary<string, string> globalProperties, string toolsVersion, string subToolsetVersion, ProjectCollection projectCollection, ProjectLoadSettings loadSettings)
: this(xml, globalProperties, toolsVersion, subToolsetVersion, projectCollection, loadSettings, null, null)
{
}
private Project(ProjectRootElement xml, IDictionary<string, string> globalProperties, string toolsVersion, string subToolsetVersion, ProjectCollection projectCollection, ProjectLoadSettings loadSettings,
EvaluationContext evaluationContext, IDirectoryCacheFactory directoryCacheFactory)
{
ErrorUtilities.VerifyThrowArgumentNull(xml, nameof(xml));
ErrorUtilities.VerifyThrowArgumentLengthIfNotNull(toolsVersion, nameof(toolsVersion));
ErrorUtilities.VerifyThrowArgumentNull(projectCollection, nameof(projectCollection));
ProjectCollection = projectCollection;
var defaultImplementation = new ProjectImpl(this, xml, globalProperties, toolsVersion, subToolsetVersion, loadSettings);
implementationInternal = (IProjectLinkInternal)defaultImplementation;
implementation = defaultImplementation;
_directoryCacheFactory = directoryCacheFactory;
defaultImplementation.Initialize(globalProperties, toolsVersion, subToolsetVersion, loadSettings, evaluationContext);
}
/// <summary>
/// Construct over a text reader over project xml, evaluating with the global project collection's
/// global properties and default tools version.
/// Project will be added to the global project collection when it is named.
/// Throws InvalidProjectFileException if the evaluation fails.
/// Throws InvalidOperationException if there is already an equivalent project loaded in the project collection.
/// </summary>
/// <param name="xmlReader">Xml reader to read project from.</param>
public Project(XmlReader xmlReader)
: this(xmlReader, null, null)
{
}
/// <summary>
/// Construct over a text reader over project xml, evaluating with specified
/// global properties and toolset, either or both of which may be null.
/// Project will be added to the global project collection when it is named.
/// Throws InvalidProjectFileException if the evaluation fails.
/// Throws InvalidOperationException if there is already an equivalent project loaded in the project collection.
/// </summary>
/// <param name="xmlReader">Xml reader to read project from.</param>
/// <param name="globalProperties">Global properties to evaluate with. May be null in which case the containing project collection's global properties will be used.</param>
/// <param name="toolsVersion">Tools version to evaluate with. May be null.</param>
public Project(XmlReader xmlReader, IDictionary<string, string> globalProperties, string toolsVersion)
: this(xmlReader, globalProperties, toolsVersion, ProjectCollection.GlobalProjectCollection)
{
}
/// <summary>
/// Construct over a text reader over project xml, evaluating with specified
/// global properties and toolset, either or both of which may be null.
/// Project will be added to the specified project collection when it is named.
/// Throws InvalidProjectFileException if the evaluation fails.
/// Throws InvalidOperationException if there is already an equivalent project loaded in the project collection.
/// </summary>
/// <param name="xmlReader">Xml reader to read project from.</param>
/// <param name="globalProperties">Global properties to evaluate with. May be null in which case the containing project collection's global properties will be used.</param>
/// <param name="toolsVersion">Tools version to evaluate with. May be null.</param>
/// <param name="projectCollection">The collection with which this project should be associated. May not be null.</param>
public Project(XmlReader xmlReader, IDictionary<string, string> globalProperties, string toolsVersion, ProjectCollection projectCollection)
: this(xmlReader, globalProperties, toolsVersion, projectCollection, ProjectLoadSettings.Default)
{
}
/// <summary>
/// Construct over a text reader over project xml, evaluating with specified
/// global properties and toolset, either or both of which may be null.
/// Project will be added to the specified project collection when it is named.
/// Throws InvalidProjectFileException if the evaluation fails.
/// Throws InvalidOperationException if there is already an equivalent project loaded in the project collection.
/// </summary>
/// <param name="xmlReader">Xml reader to read project from.</param>
/// <param name="globalProperties">Global properties to evaluate with. May be null in which case the containing project collection's global properties will be used.</param>
/// <param name="toolsVersion">Tools version to evaluate with. May be null.</param>
/// <param name="projectCollection">The collection with which this project should be associated. May not be null.</param>
/// <param name="loadSettings">The <see cref="ProjectLoadSettings"/> to use for evaluation.</param>
public Project(XmlReader xmlReader, IDictionary<string, string> globalProperties, string toolsVersion, ProjectCollection projectCollection, ProjectLoadSettings loadSettings)
: this(xmlReader, globalProperties, toolsVersion, null /* no explicit sub-toolset version */, projectCollection, loadSettings)
{
}
/// <summary>
/// Construct over a text reader over project xml, evaluating with specified
/// global properties and toolset, either or both of which may be null.
/// Project will be added to the specified project collection when it is named.
/// Throws InvalidProjectFileException if the evaluation fails.
/// Throws InvalidOperationException if there is already an equivalent project loaded in the project collection.
/// </summary>
/// <param name="xmlReader">Xml reader to read project from.</param>
/// <param name="globalProperties">Global properties to evaluate with. May be null in which case the containing project collection's global properties will be used.</param>
/// <param name="toolsVersion">Tools version to evaluate with. May be null.</param>
/// <param name="subToolsetVersion">Sub-toolset version to explicitly evaluate the toolset with. May be null.</param>
/// <param name="projectCollection">The collection with which this project should be associated. May not be null.</param>
/// <param name="loadSettings">The load settings for this project.</param>
public Project(XmlReader xmlReader, IDictionary<string, string> globalProperties, string toolsVersion, string subToolsetVersion, ProjectCollection projectCollection, ProjectLoadSettings loadSettings)
: this(xmlReader, globalProperties, toolsVersion, subToolsetVersion, projectCollection, loadSettings, null, null)
{
}
private Project(XmlReader xmlReader, IDictionary<string, string> globalProperties, string toolsVersion, string subToolsetVersion, ProjectCollection projectCollection, ProjectLoadSettings loadSettings,
EvaluationContext evaluationContext, IDirectoryCacheFactory directoryCacheFactory)
{
ErrorUtilities.VerifyThrowArgumentNull(xmlReader, nameof(xmlReader));
ErrorUtilities.VerifyThrowArgumentLengthIfNotNull(toolsVersion, nameof(toolsVersion));
ErrorUtilities.VerifyThrowArgumentNull(projectCollection, nameof(projectCollection));
ProjectCollection = projectCollection;
var defaultImplementation = new ProjectImpl(this, xmlReader, globalProperties, toolsVersion, subToolsetVersion, loadSettings, evaluationContext);
implementationInternal = (IProjectLinkInternal)defaultImplementation;
implementation = defaultImplementation;
_directoryCacheFactory = directoryCacheFactory;
defaultImplementation.Initialize(globalProperties, toolsVersion, subToolsetVersion, loadSettings, evaluationContext);
}
/// <summary>
/// Construct over an existing project file, evaluating with the global project collection's
/// global properties and default tools version.
/// Project is added to the global project collection.
/// Throws InvalidProjectFileException if the evaluation fails.
/// Throws InvalidOperationException if there is already an equivalent project loaded in the project collection.
/// May throw IO-related exceptions.
/// </summary>
/// <exception cref="InvalidProjectFileException">If the evaluation fails.</exception>
public Project(string projectFile)
: this(projectFile, null, null)
{
}
/// <summary>
/// Construct over an existing project file, evaluating with specified
/// global properties and toolset, either or both of which may be null.
/// Project is added to the global project collection.
/// Throws InvalidProjectFileException if the evaluation fails.
/// Throws InvalidOperationException if there is already an equivalent project loaded in the project collection.
/// May throw IO-related exceptions.
/// </summary>
public Project(string projectFile, IDictionary<string, string> globalProperties, string toolsVersion)
: this(projectFile, globalProperties, toolsVersion, ProjectCollection.GlobalProjectCollection)
{
}
/// <summary>
/// Construct over an existing project file, evaluating with the specified global properties and
/// using the tools version provided, either or both of which may be null.
/// Project is added to the global project collection.
/// Throws InvalidProjectFileException if the evaluation fails.
/// Throws InvalidOperationException if there is already an equivalent project loaded in the project collection.
/// May throw IO-related exceptions.
/// </summary>
/// <param name="projectFile">The project file.</param>
/// <param name="globalProperties">The global properties. May be null.</param>
/// <param name="toolsVersion">The tools version. May be null.</param>
/// <param name="projectCollection">The collection with which this project should be associated. May not be null.</param>
public Project(string projectFile, IDictionary<string, string> globalProperties, string toolsVersion, ProjectCollection projectCollection)
: this(projectFile, globalProperties, toolsVersion, projectCollection, ProjectLoadSettings.Default)
{
}
/// <summary>
/// Construct over an existing project file, evaluating with the specified global properties and
/// using the tools version provided, either or both of which may be null.
/// Project is added to the global project collection.
/// Throws InvalidProjectFileException if the evaluation fails.
/// Throws InvalidOperationException if there is already an equivalent project loaded in the project collection.
/// May throw IO-related exceptions.
/// </summary>
/// <param name="projectFile">The project file.</param>
/// <param name="globalProperties">The global properties. May be null.</param>
/// <param name="toolsVersion">The tools version. May be null.</param>
/// <param name="projectCollection">The collection with which this project should be associated. May not be null.</param>
/// <param name="loadSettings">The load settings for this project.</param>
public Project(string projectFile, IDictionary<string, string> globalProperties, string toolsVersion, ProjectCollection projectCollection, ProjectLoadSettings loadSettings)
: this(projectFile, globalProperties, toolsVersion, null /* no explicitly specified sub-toolset version */, projectCollection, loadSettings)
{
}
/// <summary>
/// Construct over an existing project file, evaluating with the specified global properties and
/// using the tools version provided, either or both of which may be null.
/// Project is added to the global project collection.
/// Throws InvalidProjectFileException if the evaluation fails.
/// Throws InvalidOperationException if there is already an equivalent project loaded in the project collection.
/// May throw IO-related exceptions.
/// </summary>
/// <param name="projectFile">The project file.</param>
/// <param name="globalProperties">The global properties. May be null.</param>
/// <param name="toolsVersion">The tools version. May be null.</param>
/// <param name="subToolsetVersion">Sub-toolset version to explicitly evaluate the toolset with. May be null.</param>
/// <param name="projectCollection">The collection with which this project should be associated. May not be null.</param>
/// <param name="loadSettings">The load settings for this project.</param>
public Project(string projectFile, IDictionary<string, string> globalProperties, string toolsVersion, string subToolsetVersion, ProjectCollection projectCollection, ProjectLoadSettings loadSettings)
: this(projectFile, globalProperties, toolsVersion, subToolsetVersion, projectCollection, loadSettings, null, null)
{
}
private Project(string projectFile, IDictionary<string, string> globalProperties, string toolsVersion, string subToolsetVersion, ProjectCollection projectCollection, ProjectLoadSettings loadSettings,
EvaluationContext evaluationContext, IDirectoryCacheFactory directoryCacheFactory)
{
ErrorUtilities.VerifyThrowArgumentNull(projectFile, nameof(projectFile));
ErrorUtilities.VerifyThrowArgumentLengthIfNotNull(toolsVersion, nameof(toolsVersion));
ErrorUtilities.VerifyThrowArgumentNull(projectCollection, nameof(projectCollection));
ProjectCollection = projectCollection;
var defaultImplementation = new ProjectImpl(this, projectFile, globalProperties, toolsVersion, subToolsetVersion, loadSettings, evaluationContext);
implementationInternal = (IProjectLinkInternal)defaultImplementation;
implementation = defaultImplementation;
_directoryCacheFactory = directoryCacheFactory;
// Note: not sure why only this ctor flavor do TryUnloadProject
// seems the XmlReader based one should also clean the same way.
try
{
defaultImplementation.Initialize(globalProperties, toolsVersion, subToolsetVersion, loadSettings, evaluationContext);
}
catch (Exception ex) when (!ExceptionHandling.IsCriticalException(ex))
{
// If possible, clear out the XML we just loaded into the XML cache:
// if we had loaded the XML from disk into the cache within this constructor,
// and then are are bailing out because there is a typo in the XML such that
// evaluation failed, we don't want to leave the bad XML in the cache;
// the user wouldn't be able to fix the XML file and try again.
projectCollection.TryUnloadProject(Xml);
throw;
}
}
/// <summary>
/// Create a file based project.
/// </summary>
/// <param name="file">The file to evaluate the project from.</param>
/// <param name="options">The <see cref="ProjectOptions"/> to use.</param>
/// <returns></returns>
public static Project FromFile(string file, ProjectOptions options)
{
return new Project(
file,
options.GlobalProperties,
options.ToolsVersion,
options.SubToolsetVersion,
options.ProjectCollection ?? ProjectCollection.GlobalProjectCollection,
options.LoadSettings,
options.EvaluationContext,
options.DirectoryCacheFactory);
}
/// <summary>
/// Create a <see cref="ProjectRootElement"/> based project.
/// </summary>
/// <param name="rootElement">The <see cref="ProjectRootElement"/> to evaluate the project from.</param>
/// <param name="options">The <see cref="ProjectOptions"/> to use.</param>
public static Project FromProjectRootElement(ProjectRootElement rootElement, ProjectOptions options)
{
return new Project(
rootElement,
options.GlobalProperties,
options.ToolsVersion,
options.SubToolsetVersion,
options.ProjectCollection ?? ProjectCollection.GlobalProjectCollection,
options.LoadSettings,
options.EvaluationContext,
options.DirectoryCacheFactory);
}
/// <summary>
/// Create a <see cref="XmlReader"/> based project.
/// </summary>
/// <param name="reader">The <see cref="XmlReader"/> to evaluate the project from.</param>
/// <param name="options">The <see cref="ProjectOptions"/> to use.</param>
public static Project FromXmlReader(XmlReader reader, ProjectOptions options)
{
return new Project(
reader,
options.GlobalProperties,
options.ToolsVersion,
options.SubToolsetVersion,
options.ProjectCollection ?? ProjectCollection.GlobalProjectCollection,
options.LoadSettings,
options.EvaluationContext,
options.DirectoryCacheFactory);
}
/// <summary>
/// Whether build is enabled for this project.
/// </summary>
private enum BuildEnabledSetting
{
/// <summary>
/// Explicitly enabled
/// </summary>
BuildEnabled,
/// <summary>
/// Explicitly disabled
/// </summary>
BuildDisabled,
/// <summary>
/// No explicit setting, uses the setting on the
/// project collection.
/// This is the default.
/// </summary>
UseProjectCollectionSetting
}
internal Data TestOnlyGetPrivateData => (Data)implementationInternal.TestOnlyGetPrivateData;
/// <summary>
/// Gets or sets the project collection which contains this project.
/// Can never be null.
/// Cannot be modified.
/// </summary>
public ProjectCollection ProjectCollection { get; }
/// <summary>
/// The backing Xml project.
/// Can never be null.
/// </summary>
/// <remarks>
/// There is no setter here as that doesn't make sense. If you have a new ProjectRootElement, evaluate it into a new Project.
/// </remarks>
public ProjectRootElement Xml => implementation.Xml;
/// <summary>
/// Whether this project is dirty such that it needs reevaluation.
/// This may be because its underlying XML has changed (either through this project or another)
/// either the XML of the main project or an imported file;
/// or because its toolset may have changed.
/// </summary>
public bool IsDirty => implementation.IsDirty;
/// <summary>
/// Read only dictionary of the global properties used in the evaluation
/// of this project.
/// </summary>
/// <remarks>
/// This is the publicly exposed getter, that translates into a read-only dead IDictionary<string, string>.
///
/// In order to easily tell when we're dirtied, setting and removing global properties is done with
/// <see cref="SetGlobalProperty">SetGlobalProperty</see> and <see cref="RemoveGlobalProperty">RemoveGlobalProperty</see>.
/// </remarks>
public IDictionary<string, string> GlobalProperties => implementation.GlobalProperties;
/// <summary>
/// Indicates whether the global properties dictionary contains the specified key.
/// </summary>
internal bool GlobalPropertiesContains(string key) => implementation.GlobalPropertiesContains(key);
/// <summary>
/// Indicates how many elements are in the global properties dictionary.
/// </summary>
internal int GlobalPropertiesCount => implementation.GlobalPropertiesCount();
/// <summary>
/// Enumerates the values in the global properties dictionary.
/// </summary>
internal IEnumerable<KeyValuePair<string, string>> GlobalPropertiesEnumerable => implementation.GlobalPropertiesEnumerable();
/// <summary>
/// Item types in this project.
/// This is an ordered collection.
/// </summary>
/// <comments>
/// data.ItemTypes is a KeyCollection, so it doesn't need any
/// additional read-only protection.
/// </comments>
public ICollection<string> ItemTypes => implementation.ItemTypes;
/// <summary>
/// Properties in this project.
/// Since evaluation has occurred, this is an unordered collection.
/// </summary>
public ICollection<ProjectProperty> Properties => implementation.Properties;
/// <summary>
/// Collection of possible values implied for properties contained in the conditions found on properties,
/// property groups, imports, and whens.
///
/// For example, if the following conditions existed on properties in a project:
///
/// Condition="'$(Configuration)|$(Platform)' == 'Debug|x86'"
/// Condition="'$(Configuration)' == 'Release'"
///
/// the table would be populated with
///
/// { "Configuration", { "Debug", "Release" }}
/// { "Platform", { "x86" }}
///
/// This is used by Visual Studio to determine the configurations defined in the project.
/// </summary>
public IDictionary<string, List<string>> ConditionedProperties => implementation.ConditionedProperties;
/// <summary>
/// Read-only dictionary of item definitions in this project.
/// Keyed by item type.
/// </summary>
public IDictionary<string, ProjectItemDefinition> ItemDefinitions => implementation.ItemDefinitions;
/// <summary>
/// Items in this project, ordered within groups of item types.
/// </summary>
[SuppressMessage("Microsoft.Naming", "CA1721:PropertyNamesShouldNotMatchGetMethods", Justification = "This is a reasonable choice. API review approved")]
public ICollection<ProjectItem> Items => implementation.Items;
/// <summary>
/// Items in this project, ordered within groups of item types,
/// including items whose conditions evaluated to false, or that were
/// contained within item groups who themselves had conditioned evaluated to false.
/// This is useful for hosts that wish to display all items, even if they might not be part
/// of the build in the current configuration.
/// </summary>
[SuppressMessage("Microsoft.Naming", "CA1721:PropertyNamesShouldNotMatchGetMethods", Justification = "This is a reasonable choice. API review approved")]
public ICollection<ProjectItem> ItemsIgnoringCondition => implementation.ItemsIgnoringCondition;
/// <summary>
/// All the files that during evaluation contributed to this project, as ProjectRootElements,
/// with the ProjectImportElement that caused them to be imported.
/// This does not include projects that were never imported because a condition on an Import element was false.
/// The outer ProjectRootElement that maps to this project itself is not included.
/// </summary>
/// <remarks>
/// This can be used by the host to figure out what projects might be impacted by a change to a particular file.
/// It could also be used, for example, to find the .user file, and use its ProjectRootElement to modify properties in it.
/// </remarks>
public IList<ResolvedImport> Imports => implementation.Imports;
/// <summary>
/// This list will contain duplicate imports if an import is imported multiple times. However, only the first import was used in evaluation.
/// </summary>
public IList<ResolvedImport> ImportsIncludingDuplicates => implementation.ImportsIncludingDuplicates;
/// <summary>
/// Targets in the project. The key to the dictionary is the target's name.
/// Overridden targets are not included in this collection.
/// This collection is read-only.
/// </summary>
public IDictionary<string, ProjectTargetInstance> Targets => implementation.Targets;
/// <summary>
/// Properties encountered during evaluation. These are read during the first evaluation pass.
/// Unlike those returned by the Properties property, these are ordered, and includes any properties that
/// were subsequently overridden by others with the same name. It does not include any
/// properties whose conditions did not evaluate to true.
/// It does not include any properties added since the last evaluation.
/// </summary>
public ICollection<ProjectProperty> AllEvaluatedProperties => implementation.AllEvaluatedProperties;
/// <summary>
/// Item definition metadata encountered during evaluation. These are read during the second evaluation pass.
/// Unlike those returned by the ItemDefinitions property, these are ordered, and include any metadata that
/// were subsequently overridden by others with the same name and item type. It does not include any
/// elements whose conditions did not evaluate to true.
/// It does not include any item definition metadata added since the last evaluation.
/// </summary>
public ICollection<ProjectMetadata> AllEvaluatedItemDefinitionMetadata => implementation.AllEvaluatedItemDefinitionMetadata;
/// <summary>
/// Items encountered during evaluation. These are read during the third evaluation pass.
/// Unlike those returned by the Items property, these are ordered with respect to all other items
/// encountered during evaluation, not just ordered with respect to items of the same item type.
/// In some applications, like the F# language, this complete mutual ordering is significant, and such hosts
/// can use this property.
/// It does not include any elements whose conditions did not evaluate to true.
/// It does not include any items added since the last evaluation.
/// </summary>
public ICollection<ProjectItem> AllEvaluatedItems => implementation.AllEvaluatedItems;
/// <summary>
/// The tools version this project was evaluated with, if any.
/// Not necessarily the same as the tools version on the Project tag, if any;
/// it may have been externally specified, for example with a /tv switch.
/// The actual tools version on the Project tag, can be gotten from <see cref="Xml">Xml.ToolsVersion</see>.
/// Cannot be changed once the project has been created.
/// </summary>
/// <remarks>
/// Set by construction.
/// </remarks>
public string ToolsVersion => implementation.ToolsVersion;
/// <summary>
/// The sub-toolset version that, combined with the ToolsVersion, was used to determine
/// the toolset properties for this project.
/// </summary>
public string SubToolsetVersion => implementation.SubToolsetVersion;
/// <summary>
/// The root directory for this project.
/// Is never null: in-memory projects use the current directory from the time of load.
/// </summary>
public string DirectoryPath => Xml.DirectoryPath;
/// <summary>
/// The full path to this project's file.
/// May be null, if the project was not loaded from disk.
/// Setter renames the project, if it already had a name.
/// </summary>
public string FullPath
{
[DebuggerStepThrough]
get => Xml.FullPath;
[DebuggerStepThrough]
set => Xml.FullPath = value;
}
/// <summary>
/// Whether ReevaluateIfNecessary is temporarily disabled.
/// This is useful when the host expects to make a number of reads and writes
/// to the project, and wants to temporarily sacrifice correctness for performance.
/// </summary>
public bool SkipEvaluation
{
[DebuggerStepThrough]
get => implementation.SkipEvaluation;
[DebuggerStepThrough]
set => implementation.SkipEvaluation = value;
}
/// <summary>
/// Whether <see cref="MarkDirty()">MarkDirty()</see> is temporarily disabled.
/// This allows, for example, a global property to be set without the project getting
/// marked dirty for reevaluation as a consequence.
/// </summary>
public bool DisableMarkDirty
{
[DebuggerStepThrough]
get => implementation.DisableMarkDirty;
[DebuggerStepThrough]
set => implementation.DisableMarkDirty = value;
}
/// <summary>
/// This controls whether or not the building of targets/tasks is enabled for this
/// project. This is for security purposes in case a host wants to closely
/// control which projects it allows to run targets/tasks. By default, for a newly
/// created project, we will use whatever setting is in the parent project collection.
/// When build is disabled, the Build method on this class will fail. However if
/// the host has already created a ProjectInstance, it can still build it. (It is
/// free to put a similar check around where it does this.)
/// </summary>
public bool IsBuildEnabled
{
[DebuggerStepThrough]
get => implementation.IsBuildEnabled;
[DebuggerStepThrough]
set => implementation.IsBuildEnabled = value;
}
/// <summary>
/// Location of the originating file itself, not any specific content within it.
/// If the file has not been given a name, returns an empty location.
/// </summary>
public ElementLocation ProjectFileLocation => Xml.ProjectFileLocation;
/// <summary>
/// Obsolete. Use <see cref="LastEvaluationId"/> instead.
/// </summary>
// marked as obsolete in 15.3
public int EvaluationCounter => LastEvaluationId;
/// <summary>
/// The ID of the last evaluation for this Project.
/// A project is always evaluated upon construction and can subsequently get evaluated multiple times via
/// <see cref="Project.ReevaluateIfNecessary()" />
///
/// It is an arbitrary number that changes when this project reevaluates.
/// Hosts don't know whether an evaluation actually happened in an interval, but they can compare this number to
/// their previously stored value to find out, and if so perhaps decide to update their own state.
/// Note that the number may not increase monotonically.
///
/// This number corresponds to the <seealso cref="BuildEventContext.EvaluationId"/> and can be used to connect
/// evaluation logging events back to the Project instance.
/// </summary>
public int LastEvaluationId => implementation.LastEvaluationId;
/// <summary>
/// List of names of the properties that, while global, are still treated as overridable.
/// </summary>
internal ISet<string> GlobalPropertiesToTreatAsLocal => implementationInternal.GlobalPropertiesToTreatAsLocal;
/// <summary>
/// The logging service used for evaluation errors.
/// </summary>
internal ILoggingService LoggingService => ProjectCollection.LoggingService;
/// <summary>
/// Returns the evaluated, escaped value of the provided item's include.
/// </summary>
[SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters", Justification = "IItem is an internal interface; this is less confusing to outside customers. ")]
public static string GetEvaluatedItemIncludeEscaped(ProjectItem item)
{
ErrorUtilities.VerifyThrowArgumentNull(item, nameof(item));
return ((IItem)item).EvaluatedIncludeEscaped;
}
/// <summary>
/// Returns the evaluated, escaped value of the provided item definition's include.
/// </summary>
public static string GetEvaluatedItemIncludeEscaped(ProjectItemDefinition item)
{
ErrorUtilities.VerifyThrowArgumentNull(item, nameof(item));
return ((IItem)item).EvaluatedIncludeEscaped;
}
/// <summary>
/// Finds all the globs specified in item includes.
/// </summary>
/// <example>
///
/// <code>
/// <![CDATA[
/// <P>*.txt</P>
///
/// <Bar Include="bar"/> (both outside and inside project cone)
/// <Zar Include="C:\**\*.foo"/> (both outside and inside project cone)
/// <Foo Include="*.a;*.b" Exclude="3.a"/>
/// <Foo Remove="2.a" />
/// <Foo Include="**\*.b" Exclude="1.b;**\obj\*.b;**\bar\*.b"/>
/// <Foo Include="$(P)"/>
/// <Foo Include="*.a;@(Bar);3.a"/> (If Bar has globs, they will have been included when querying Bar ProjectItems for globs)
/// <Foo Include="*.cs" Exclude="@(Bar)"/>
/// ]]>
/// </code>
///
/// Example result:
/// <code>
/// <![CDATA[
/// [
/// GlobResult(glob: "C:\**\*.foo", exclude: []),
/// GlobResult(glob: ["*.a", "*.b"], exclude=["3.a"], remove=["2.a"]),
/// GlobResult(glob: "**\*.b", exclude=["1.b, **\obj\*.b", **\bar\*.b"]),
/// GlobResult(glob: "*.txt", exclude=[]),
/// GlobResult(glob: "*.a", exclude=[]),
/// GlobResult(glob: "*.cs", exclude=["bar"])
/// ].
/// ]]>
/// </code>
/// </example>
/// <remarks>
/// <para>
/// <see cref="GlobResult.MsBuildGlob"/> is a <see cref="IMSBuildGlob"/> that combines all globs in the include element and ignores
/// all the fragments in the exclude attribute and all the fragments in all Remove elements that apply to the include element.
/// </para>
///
/// Users can construct a composite glob that incorporates all the globs in the Project:
/// <code>
/// <![CDATA[
/// var uberGlob = new CompositeGlob(project.GetAllGlobs().Select(r => r.MSBuildGlob).ToArray());
/// uberGlob.IsMatch("foo.cs");
/// ]]>
/// </code>
///
/// </remarks>
/// <returns>
/// List of <see cref="GlobResult"/>.
/// </returns>
public List<GlobResult> GetAllGlobs()
{
return GetAllGlobs(evaluationContext: null);
}
/// <summary>
/// See <see cref="GetAllGlobs()"/>.
/// </summary>
/// <param name="evaluationContext">
/// The evaluation context to use in case reevaluation is required.
/// To avoid reevaluation use <see cref="ProjectLoadSettings.RecordEvaluatedItemElements"/>.
/// </param>
public List<GlobResult> GetAllGlobs(EvaluationContext evaluationContext)
{
return implementation.GetAllGlobs(evaluationContext);
}
/// <summary>
/// Overload of <see cref="GetAllGlobs()"/>.
/// </summary>
/// <param name="itemType">Confine search to item elements of this type.</param>
public List<GlobResult> GetAllGlobs(string itemType)
{
return implementation.GetAllGlobs(itemType, null);
}
/// <summary>
/// See <see cref="GetAllGlobs(string)"/>.
/// </summary>
/// <param name="itemType">Type of the item.</param>
/// <param name="evaluationContext">
/// The evaluation context to use in case reevaluation is required.
/// To avoid reevaluation use <see cref="ProjectLoadSettings.RecordEvaluatedItemElements"/>.
/// </param>
public List<GlobResult> GetAllGlobs(string itemType, EvaluationContext evaluationContext)
{
return implementation.GetAllGlobs(itemType, evaluationContext);
}
/// <summary>
/// Finds all the item elements in the logical project with itemspecs that match the given string:
/// - elements that would include (or exclude) the string
/// - elements that would update the string (not yet implemented)
/// - elements that would remove the string (not yet implemented).
/// </summary>
///
/// <example>
/// The following snippet shows what <c>GetItemProvenance("a.cs")</c> returns for various item elements.
/// <code>
/// <A Include="a.cs;*.cs"/> // Occurrences:2; Operation: Include; Provenance: StringLiteral | Glob
/// <B Include="*.cs" Exclude="a.cs"/> // Occurrences: 1; Operation: Exclude; Provenance: StringLiteral
/// <C Include="b.cs"/> // NA
/// <D Include="@(A)"/> // Occurrences: 2; Operation: Include; Provenance: Inconclusive (it is an indirect occurrence from a referenced item)
/// <E Include="$(P)"/> // Occurrences: 4; Operation: Include; Provenance: FromLiteral (direct reference in $P) | Glob (direct reference in $P) | Inconclusive (it is an indirect occurrence from referenced properties and items)
/// <PropertyGroup>
/// <P>a.cs;*.cs;@(A)</P>
/// </PropertyGroup>
/// </code>
///
/// </example>
///
/// <remarks>
/// This method and its overloads are useful for clients that need to inspect all the item elements
/// that might refer to a specific item instance. For example, Visual Studio uses it to inspect
/// projects with globs. Upon a file system or IDE file artifact change, VS calls this method to find all the items
/// that might refer to the detected file change (e.g. 'which item elements refer to "Program.cs"?').
/// It uses such information to know which elements it should edit to reflect the user or file system changes.
///
/// Literal string matching tries to first match the strings. If the check fails, it then tries to match
/// the strings as if they represented files: it normalizes both strings as files relative to the current project directory
///
/// GetItemProvenance suffers from some sources of inaccuracy:
/// - it is performed after evaluation, thus is insensitive to item data flow when item references are present
/// (it sees items as they are at the end of evaluation)
///
/// This API and its return types are prone to change.
/// </remarks>
///
/// <param name="itemToMatch">The string to perform matching against.</param>
///
/// <returns>
/// A list of <see cref="ProvenanceResult"/>, sorted in project evaluation order.
/// </returns>
public List<ProvenanceResult> GetItemProvenance(string itemToMatch)