-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathcpp_modules.cs
More file actions
993 lines (866 loc) · 40.1 KB
/
Copy pathcpp_modules.cs
File metadata and controls
993 lines (866 loc) · 40.1 KB
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
using Task = Microsoft.Build.Utilities.Task;
using TaskItem = Microsoft.Build.Utilities.TaskItem;
using MessageImportance = Microsoft.Build.Framework.MessageImportance;
using ITaskItem = Microsoft.Build.Framework.ITaskItem;
using Required = Microsoft.Build.Framework.RequiredAttribute;
using ProjectCollection = Microsoft.Build.Evaluation.ProjectCollection;
using Project = Microsoft.Build.Evaluation.Project;
using ProjectProperty = Microsoft.Build.Evaluation.ProjectProperty;
using ProjectItem = Microsoft.Build.Evaluation.ProjectItem;
using CLCommandLine = Microsoft.Build.CPPTasks.CLCommandLine;
using GetOutOfDateItems = Microsoft.Build.CPPTasks.GetOutOfDateItems;
using MSBuild = Microsoft.Build.Tasks.MSBuild;
using Exec = Microsoft.Build.Tasks.Exec;
using String = System.String;
using Convert = System.Convert;
using Type = System.Type;
using System.Collections.Generic;
using PropertyInfo = System.Reflection.PropertyInfo;
using FileInfo = System.IO.FileInfo;
using File = System.IO.File;
using Path = System.IO.Path;
using Directory = System.IO.Directory;
using Stopwatch = System.Diagnostics.Stopwatch;
using System.Linq;
using Scanner = cppm_cs.Scanner;
using DepInfo = depinfo_cs.DepInfo;
using DepInfoObserver = cppm_cs.DepInfoObserver;
using ScanItem = cppm_cs.ScanItem;
using ScanItemSet = cppm_cs.ScanItemSet;
// the following are used for the ProjectInstance.Build() workaround
using ProjectInstance = Microsoft.Build.Execution.ProjectInstance;
using BuildManager = Microsoft.Build.Execution.BuildManager;
using BuildRequestData = Microsoft.Build.Execution.BuildRequestData;
using BuildParameters = Microsoft.Build.Execution.BuildParameters;
using BuildResultCode = Microsoft.Build.Execution.BuildResultCode;
public class CppM_CL : Task
{
[Required] public ITaskItem[] CL_Input_All { get; set; }
[Required] public ITaskItem[] CL_Input_Manually_Selected { get; set; }
[Required] public ITaskItem[] CurrentProjectHolder { get; set; }
[Required] public ITaskItem[] ProjectReference { get; set; }
[Required] public String SolutionFilePath { get; set; }
[Required] public String Configuration { get; set; }
[Required] public String Platform { get; set; }
[Required] public String PlatformToolset { get; set; }
[Required] public String ClangScanDepsPath { get; set; }
[Required] public String IntDir { get; set; }
[Required] public String NinjaExecutablePath { get; set; }
[Required] public String DB_Path { get; set; }
[Required] public String JustCleanItems { get; set; }
// note: remember CppM_BeforeClean when changing this
string PP_ModuleMapFile = "pp.modulemap";
string PP_CompilationDatabase = "pp_commands.json";
string ModuleMapFile = "module_map.json";
string NinjaBuildFile = "build.ninja";
string NinjaRecursiveBuildFile = "build_rec.ninja";
ScanItemSet GetScanItems(ItemSet itemset, IEnumerable<IGrouping<Target,Item>> items_by_target, bool add_commands) {
var ret = new ScanItemSet();
ret.item_root_path = ""; // using only full paths, todo: maybe switch to relative paths ?
ret.commands_contain_item_path = true;
uint target_idx = 0, command_idx = 0;
foreach(var group in items_by_target) {
var target = group.Key;
ret.targets.Add(target.project.FullPath); // todo: maybe use project GUID instead ? does that get regenerated by CMake ?
foreach(var item in group) {
if(add_commands) ret.commands.Add(GetBaseCommand(item, true));
ret.items.Add(new ScanItem{ path = item.path, command_idx = command_idx, target_idx = target_idx });
if(add_commands) command_idx++;
}
target_idx++;
}
return ret;
}
ScanItemSet GetScanItems(ItemSet itemset, bool add_commands) {
var items_by_target = itemset.Items.GroupBy(item => item.target);
return GetScanItems(itemset, items_by_target, add_commands);
}
bool CleanItems() {
if(!File.Exists(DB_Path + "\\scanner.mdb"))
return true;
CreateProjectCollection(root_only: true);
var itemset = InitItemSet();
var scanner = new Scanner();
var scan_items = GetScanItems(itemset, add_commands: false);
scanner.clean(DB_Path, scan_items);
//Log.LogMessage(MessageImportance.High, "scanner returned {0}", ret);
return true;
}
public class SharedBuildState {
public ulong build_start_time = 0;
}
SharedBuildState shared_build_state = null;
void InitSharedBuildState() {
string key = "shared_state_cppm";
var lifetime = Microsoft.Build.Framework.RegisteredTaskObjectLifetime.Build;
shared_build_state = (SharedBuildState)this.BuildEngine4.GetRegisteredTaskObject(key, lifetime);
if(shared_build_state == null) {
shared_build_state = new SharedBuildState {};
this.BuildEngine4.RegisterTaskObject(key, shared_build_state, lifetime, allowEarlyCollection: false);
}
//Log.LogMessage(MessageImportance.High, "build start time: {0}", shared_build_state.build_start_time);
}
// todo: replace this with a compiler enum
bool IsLLVM() {
var toolset = PlatformToolset.ToLower();
return toolset == "llvm" || toolset == "clangcl";
}
enum BuildMode {
CurrentTarget, // every file in the current project, but not dependencies outside of the project
SelectedFiles, // only the selected files and their transitive dependencies (possibly in other targets)
FullSubTree // every file in all of the transitively referenced projects
};
BuildMode GetBuildMode() {
// unless some specific files/projects are manually selected (to build only that)
// the references of this project are assumed to have been built already
// (either by the IDE, or by passing BuildProjectReferences=true to MSBuild)
if(CL_Input_Manually_Selected.Length > 0 && ProjectReference.Length > 0)
return BuildMode.SelectedFiles;
// a hack that allows building the whole tree at once:
// todo: find a way to override what VS does with the projects when you build
string CurrentProjectName = CurrentProjectHolder[0].GetMetadata("FileName");
if(CurrentProjectName == "_CPPM_ALL_BUILD")
return BuildMode.FullSubTree;
return BuildMode.CurrentTarget;
}
delegate T MeasureFunc<T>();
T CallMeasured<T>(string measure_message, MeasureFunc<T> f) {
if(measure_message == null) return f();
var sw = new Stopwatch(); sw.Start(); T ret = f(); sw.Stop();
Log.LogMessage(MessageImportance.High, "{0} finished in {1}s", measure_message, sw.ElapsedMilliseconds / 1000.0 );
return ret;
}
delegate void MeasureFuncVoid();
void CallMeasuredVoid(string measure_message, MeasureFuncVoid f) {
if(measure_message == null) { f(); return; }
var sw = new Stopwatch(); sw.Start(); f(); sw.Stop();
Log.LogMessage(MessageImportance.High, "{0} finished in {1}s", measure_message, sw.ElapsedMilliseconds / 1000.0 );
}
bool ExecuteTask(Task t, string measure_message = null) {
return CallMeasured<bool>(measure_message, () => {
return t.Execute();
});
}
//info related to a collection of source files that form a project
public class Target
{
public ModuleMap map = null;
public Project project = null;
public string int_dir = null;
public List<Node> nodes = new List<Node>();
public string ninja = "";
}
Target root_target = null;
List<Target> all_targets = new List<Target>();
void CreateProjectCollection(string measure_message) {
CallMeasuredVoid(measure_message, () => CreateProjectCollection());
}
// load all transitively referenced projects
// todo: keep this in memory somehow when building a project hierarchy
// maybe even run a daemon ?
void CreateProjectCollection(bool root_only = false) {
string CurrentProject = CurrentProjectHolder[0].GetMetadata("FullPath");
ProjectCollection project_collection = new ProjectCollection( new Dictionary<String, String> {
{ "SolutionDir", Path.GetDirectoryName(SolutionFilePath) + "\\" },
{ "Configuration", Configuration }, { "Platform", Platform },
{ "SolutionPath", SolutionFilePath } // just for completeness
});
//project_collection = ProjectCollection.GlobalProjectCollection; // todo: check if this is faster ?
if(root_only) {
root_target = new Target{ project = project_collection.LoadProject(CurrentProject), int_dir = IntDir };
all_targets.Add(root_target);
return;
}
var project_queue = new Queue<string>();
var loaded_projects = new HashSet<string>();
project_queue.Enqueue(CurrentProject);
loaded_projects.Add(CurrentProject);
bool first = true;
while(project_queue.Count > 0) {
var project_file = project_queue.Dequeue();
var project = project_collection.LoadProject(project_file);
// skip projects like ZERO_CHECK and others that aren't using cpp_modules
var int_dir = project.GetPropertyValue("CppM_IntDir_FullPath");
if(int_dir == "") {
project_collection.UnloadProject(project);
continue;
}
var target = new Target{ project = project, int_dir = int_dir };
all_targets.Add(target);
if(first) { root_target = target; first = false; }
foreach(ProjectItem reference in project.GetItems("ProjectReference")) {
string path = reference.GetMetadataValue("FullPath");
if(loaded_projects.Add(path))
project_queue.Enqueue(path);
}
}
}
abstract class Item {
public string path = null;
public bool is_ood = false;
public bool visited = false;
public Target target = null; // used by RunScanDeps
public Node node = null; // used by GetSelectedNinjaTargets
// note: could just access the target through node, but you don't have nodes until after RunScanDeps
public abstract string GetMetadata(string prop);
public Item Init(Target target) {
this.path = NormalizeFilePath(GetMetadata("FullPath"));
this.target = target;
return this;
}
}
class ITaskItem_Wrapper : Item {
public ITaskItem item = null;
public override string GetMetadata(string prop) { return item.GetMetadata(prop).ToString(); }
}
class ProjectItem_Wrapper : Item {
public ProjectItem item = null;
public override string GetMetadata(string prop) { return item.GetMetadataValue(prop); }
}
class ItemSet {
public Dictionary<string, Item> dict = new Dictionary<string, Item>();
public Dictionary<string, Item>.ValueCollection Items { get { return dict.Values; } }
public Item Get(string path) {
return dict[NormalizeFilePath(path)];
}
public bool TryGet(string path, out Item item) {
return dict.TryGetValue(NormalizeFilePath(path), out item);
}
public Item TryGet(string path) {
Item item = null;
TryGet(path, out item);
return item;
}
public void Add(Item item) { dict.Add(item.path, item); }
public void Add(ITaskItem item, Target tgt) { Add(new ITaskItem_Wrapper { item = item }.Init(tgt)); }
public void Add(ProjectItem item, Target tgt) { Add(new ProjectItem_Wrapper { item = item }.Init(tgt)); }
}
ItemSet InitItemSet() {
// todo: the same source file could appear in multiple projects
var itemset = new ItemSet();
foreach(var item in CL_Input_All)
itemset.Add(item, root_target);
return itemset;
}
BuildManager build_manager = new BuildManager("cpp_modules");
ProjectInstance Build(Project project, string target) {
//Log.LogMessage(MessageImportance.High, "building {0}", project.FullPath);
var instance = project.CreateProjectInstance(); // project is immutable, instance is not
/*the following fails with System.InvalidOperationException: The operation cannot be completed because a build is already in progress.
at Microsoft.Build.Execution.BuildManager.BeginBuild(BuildParameters parameters)
at Microsoft.Build.Execution.ProjectInstance.Build(String[] targets, IEnumerable`1 loggers, IEnumerable`1 remoteLoggers, ILoggingService loggingService, Int32 maxNodeCount, IDictionary`2& targetOutputs)*/
//if(!instance.Build(target, null))
// throw new System.Exception("failed to build project");
var targets = new string[] { target };
var data = new BuildRequestData(instance, targets, project.ProjectCollection.HostServices);
var parameters = new BuildParameters();
/*parameters.EnvironmentPropertiesInternal = project.ProjectCollection.EnvironmentProperties;
parameters.ProjectRootElementCache = project.ProjectCollection.ProjectRootElementCache;
parameters.MaxNodeCount = 1;*/
var result = build_manager.Build(parameters, data);
if(result.OverallResult != BuildResultCode.Success)
throw new System.Exception("failed to build project " + project.FullPath);
return instance;
}
// collect all the itemset from the transitively referenced projects
// try to support any other msbuild customizations that affect the item sets needed for the build
void LoadSubtreeItems(ItemSet itemset) {
// todo: do a topological sort for all_targets
// todo: or, if the user doesn't need dependencies between project preprocessing then
// we could also parallelize this
foreach(var target in all_targets) {
// we've already added the items for the current project, don't build it again
if(target == root_target) continue;
var instance = Build(target.project, "CppM_PreProcess");
foreach(var item in instance.GetItems("CppM_CL_Input_All"))
itemset.Add(item, target);
}
}
void LoadSubtreeItems(ItemSet itemset, string measure_message) {
CallMeasuredVoid(measure_message, () => LoadSubtreeItems(itemset));
}
static string GetImportableHeaderModuleName(string header_path) {
return Path.GetFileNameWithoutExtension(header_path).ToUpper();
}
void WriteAllLines_IfChanged(string file_path, IEnumerable<string> lines) {
if(!File.Exists(file_path) || !lines.SequenceEqual(File.ReadAllLines(file_path)))
File.WriteAllLines(file_path, lines);
}
HashSet<string> GetImportableHeaders()
{
// merge the sets of importable headers from all transitively referenced projects
var all_importable_headers = all_targets.SelectMany(target =>
//todo: use the CppM_PreProcess target
target.project.GetItems("ClCompile")
.Where(i => i.GetMetadataValue("CppM_Header_Unit") == "true"
&& i.GetMetadataValue("ExcludedFromBuild") != "true")
.Select(i => NormalizeFilePath(i.GetMetadataValue("FullPath")))
).ToHashSet();
// create a module map for the preprocessor to know which headers are importable
/*WriteAllLines_IfChanged(IntDir + PP_ModuleMapFile, all_importable_headers
.Select(header => "module " + GetImportableHeaderModuleName(header) +
" {\n header \"" + PortablePath(header) + "\"\n export *\n}"));*/
return all_importable_headers;
}
static string NormalizeFilePath(string file_path) {
try { return Path.GetFullPath(file_path).ToUpper(); }
catch (System.Exception e) {
throw new System.Exception(String.Format("failed to normalize path {0}", file_path), e);
}
}
string GetBaseCommand(Item item, bool preprocess)
{
// copy metadata from the source items into CLCommandLine
var cmdline = new CLCommandLine();
foreach(string prop in props) {
// this makes sure the command doesn't change when we call msbuild on other projects for preprocessing:
if(props_not_for_cmd.Contains(prop)) continue;
string meta_value = item.GetMetadata(prop);
if(meta_value != "" && !SetPropertyFromString(cmdline, prop, meta_value)) {
Log.LogMessage(MessageImportance.High, "failed to set {0}", prop);
throw new System.Exception("failed to set property for CLCommandLine");
}
}
if(preprocess) {
cmdline.EnableModules = false; // for clang-scan-deps
cmdline.AdditionalOptions = ""; // todo: just remove /module:stdIfcDir
} else {
if(!IsLLVM()) // serialize writes to the PDB files (otherwise cl.exe fails to open them in parallel)
cmdline.AdditionalOptions += " /FS ";
}
if(!cmdline.Execute())
throw new System.Exception("failed to execute CLCommandLine");
string tool = cmdline.ToolExe;
if(!IsLLVM()) // for some reason CLToolExe no longer seems to bet set for MSVC
tool = "cl.exe";
string args = cmdline.CommandLines[0].GetMetadata("Identity");
//if(IsLLVM() && !preprocess) // todo: at some point scan-deps will also need to know the set of importable headers
// args += " -Xclang \"-fmodule-map-file=" + item.target.int_dir + PP_ModuleMapFile + "\" ";
var name = item.path;
if(IsLLVM()) name = PortablePath(name);
return String.Format("\"{0}\" \"{1}\" {2}", tool, name, args);
}
bool SetPropertyFromString(object obj, string prop, string value)
{
//Log.LogMessage(MessageImportance.High, "{0} = {1}", prop, value);
PropertyInfo propertyInfo = obj.GetType().GetProperty(prop);
if(propertyInfo == null) {
Log.LogMessage(MessageImportance.High, " property " + prop + " not found in object");
return true;
}
object value_to_set = null;
if(propertyInfo.PropertyType.IsArray) {
Type t = propertyInfo.PropertyType.GetElementType();
if(t == typeof(String)) {
value_to_set = value.ToString().Split(new char[] {';'});
} else if(t == typeof(ITaskItem)) {
string[] elems = value.ToString().Split(new char[] {';'});
value_to_set = elems.Select(e => new TaskItem(e)).ToArray();
} else {
Log.LogMessage(MessageImportance.High, "ERROR: unknown array type");
return false;
}
} else {
value_to_set = Convert.ChangeType(value, propertyInfo.PropertyType);
}
propertyInfo.SetValue(obj, value_to_set, null);
return true;
}
// replaced ' +(\w+) +="([^"]+)"\r\n' with '"\1", '
// changed BuildingInIDE to BuildingInIde, commented TrackFileAccess, MinimalRebuild, MinimalRebuildFromTracking and AcceptableNonZeroExitCodes
string[] props = { "BuildingInIde", "Sources", "AdditionalIncludeDirectories", "AdditionalOptions", "AdditionalUsingDirectories", "AssemblerListingLocation", "AssemblerOutput", "BasicRuntimeChecks", "BrowseInformation", "BrowseInformationFile", "BufferSecurityCheck", "CallingConvention", "ControlFlowGuard", "CompileAsManaged", "CompileAsWinRT", "CompileAs", "ConformanceMode", "DebugInformationFormat", "DiagnosticsFormat", "DisableLanguageExtensions", "DisableSpecificWarnings", "EnableEnhancedInstructionSet", "EnableFiberSafeOptimizations", "EnableModules", "EnableParallelCodeGeneration", "EnablePREfast", "EnforceTypeConversionRules", "ErrorReporting", "ExceptionHandling", "ExcludedInputPaths", "ExpandAttributedSource", "FavorSizeOrSpeed", "FloatingPointExceptions", "FloatingPointModel", "ForceConformanceInForLoopScope", "ForcedIncludeFiles", "ForcedUsingFiles", "FunctionLevelLinking", "GenerateXMLDocumentationFiles", "IgnoreStandardIncludePath", "InlineFunctionExpansion", "IntrinsicFunctions", "LanguageStandard", /*"MinimalRebuild",*/ "MultiProcessorCompilation", "ObjectFileName", "OmitDefaultLibName", "OmitFramePointers", "OpenMPSupport", "Optimization", "PrecompiledHeader", "PrecompiledHeaderFile", "PrecompiledHeaderOutputFile", "PREfastAdditionalOptions", "PREfastAdditionalPlugins", "PREfastLog", "PreprocessKeepComments", "PreprocessorDefinitions", "PreprocessSuppressLineNumbers", "PreprocessToFile", "ProcessorNumber", "ProgramDataBaseFileName", "RemoveUnreferencedCodeData", "RuntimeLibrary", "RuntimeTypeInfo", "SDLCheck", "ShowIncludes", "WarningVersion", "SmallerTypeCheck", "SpectreMitigation", "StringPooling", "StructMemberAlignment", "SupportJustMyCode", "SuppressStartupBanner", "TreatSpecificWarningsAsErrors", "TreatWarningAsError", "TreatWChar_tAsBuiltInType", "UndefineAllPreprocessorDefinitions", "UndefinePreprocessorDefinitions", "UseFullPaths", "UseUnicodeForAssemblerListing", "WarningLevel", "WholeProgramOptimization", "WinRTNoStdLib", "XMLDocumentationFileName", "CreateHotpatchableImage", "TrackerLogDirectory", "TLogReadFiles", "TLogWriteFiles", "ToolExe", "ToolPath", /*"TrackFileAccess", "MinimalRebuildFromTracking",*/ "ToolArchitecture", "TrackerFrameworkPath", "TrackerSdkPath", "TrackedInputFilesToIgnore", "DeleteOutputOnExecute", /*"AcceptableNonZeroExitCodes",*/ "YieldDuringToolExecution" };
// we should not recompile the files if these properties change:
string[] props_not_for_cmd = { "BuildingInIde", "ErrorReporting" };
// returns true if a_file is newer than b_file
// false if b_file exists but a_file does not
// true if neither file exists
bool NewerThan(string a_file, string b_file) {
FileInfo a_info = new FileInfo(a_file);
FileInfo b_info = new FileInfo(b_file);
return a_info.LastWriteTime >= b_info.LastWriteTime;
}
public class ModuleDefinition
{
public string cmi_file = "";
public string obj_file = "";
public string exported_module = "";
public bool importable_header = false;
public List<string> imported_modules = new List<string>();
public List<string> imported_headers = new List<string>();
public List<string> included_headers = new List<string>();
public string base_command = "";
public void assign(ModuleDefinition def) {
cmi_file = def.cmi_file;
obj_file = def.obj_file;
exported_module = def.exported_module;
importable_header = def.importable_header;
imported_modules = def.imported_modules;
imported_headers = def.imported_headers;
included_headers = def.included_headers;
base_command = def.base_command;
}
}
public class ModuleMap
{
public class Entry : ModuleDefinition {
public string source_file = "";
public string mdef_file = "";
public static Entry create(string src_file, string mdef_file, ModuleDefinition module_def) {
Entry ret = new Entry { source_file = src_file, mdef_file = mdef_file };
ret.assign(module_def);
return ret;
}
}
public List<Entry> entries = new List<Entry>();
}
static string ToJSON<T>(T obj) {
return Newtonsoft.Json.JsonConvert.SerializeObject(obj, Newtonsoft.Json.Formatting.Indented);
}
static void SerializeToFile<T>(T obj, String file_path) {
Directory.CreateDirectory(Path.GetDirectoryName(file_path));
File.WriteAllText(file_path, ToJSON(obj));
}
static T Deserialize<T>(String file_path) {
return Newtonsoft.Json.JsonConvert.DeserializeObject<T>(File.ReadAllText(file_path));
}
static T DeserializeIfExists<T>(String file_path) {
try {
//return serializer.Deserialize<T>(File.ReadAllText(file_path));
return Deserialize<T>(file_path);
} catch(System.IO.FileNotFoundException) {
return default(T);
} catch(System.IO.DirectoryNotFoundException) {
return default(T);
}
}
Dictionary<string, ModuleMap.Entry> GetModuleMap_SrcToEntry(ModuleMap module_map) {
if(module_map == null)
return null;
var src_to_entry = new Dictionary<string, ModuleMap.Entry>();
foreach(ModuleMap.Entry entry in module_map.entries) {
src_to_entry.Add(entry.source_file, entry);
}
return src_to_entry;
}
class ExecuteResult { public int exit_code; public ITaskItem[] console_output; }
ExecuteResult ExecuteCommand(string command, bool pipe_output = false, string measure_message = null) {
return CallMeasured<ExecuteResult>(measure_message, () => {
Log.LogMessage(MessageImportance.High, "executing {0}", command);
var exec = new Exec {
Command = command,
ConsoleToMSBuild = pipe_output,
BuildEngine = this.BuildEngine,
EchoOff = pipe_output,
IgnoreExitCode = true,
StandardOutputImportance = pipe_output ? "low" : "high"
};
if(!exec.Execute())
throw new System.Exception(String.Format("ERROR: failed to execute {0}", command));
return new ExecuteResult { exit_code = exec.ExitCode, console_output = exec.ConsoleOutput };
});
}
// ideally the scanner should make it unnecessary to pass this to RunScanDeps
HashSet<string> all_importable_headers_tmp = null;
class OOD_Deps_Observer : DepInfoObserver {
public CppM_CL cppm = null;
public ItemSet itemset = null;
List<T> ens<T>(List<T> list) {
if(list != null) return list;
return new List<T>();
}
void LogScanReport(string src_file, ModuleDefinition mdef) {
string report = "";
if(mdef.exported_module != "" && !mdef.importable_header)
report = "exp: " + mdef.exported_module + " ";
string imports = String.Join(" ", mdef.imported_modules.Concat(mdef.imported_headers));
report += (imports != "" ? "imp: " + imports : "");
if(report != "") cppm.Log.LogMessage(MessageImportance.High, "{0} {1}", src_file, report);
}
public override void on_result(uint item_idx, DepInfo dep_info, bool out_of_date) {
//cppm.Log.LogMessage(MessageImportance.High, "results for {0}:", dep_info.input);
var item = itemset.Get(dep_info.input);
item.is_ood = out_of_date;
bool is_header_unit = (item.GetMetadata("CppM_Header_Unit") == "true");
var mdef = new ModuleDefinition {
base_command = cppm.GetBaseCommand(item, preprocess: false),
obj_file = NormalizeFilePath(item.GetMetadata("ObjectFileName") +
// for header units the full path is already provided by the targets file
(is_header_unit ? "" : Path.GetFileNameWithoutExtension(item.path) + ".obj")),
importable_header = is_header_unit,
};
if(is_header_unit)
mdef.exported_module = GetImportableHeaderModuleName(item.path);
foreach(var dep in ens(dep_info.depends)) {
//cppm.Log.LogMessage(MessageImportance.High, " include {0}", dep);
mdef.included_headers.Add(dep); // todo: separate include/other deps ?
}
var fc = dep_info.future_compile;
if(fc != null) {
foreach(var module in ens(fc.provides)) {
//cppm.Log.LogMessage(MessageImportance.High, " export {0}", module.logical_name);
mdef.exported_module = module.logical_name;
}
foreach(var module in ens(fc.requires)) {
if(module.source_path != null && module.source_path != "") {
//cppm.Log.LogMessage(MessageImportance.High, " import \"{0}\"", module.source_path);
mdef.imported_headers.Add(module.source_path);
mdef.imported_modules.Add(GetImportableHeaderModuleName(module.source_path));
} else {
//cppm.Log.LogMessage(MessageImportance.High, " import {0}", module.logical_name);
// MSVC uses named modules for the standard library
if(cppm.IsLLVM() || !module.logical_name.StartsWith("std."))
mdef.imported_modules.Add(module.logical_name);
}
}
}
if(mdef.exported_module != "") // could be from a named module or imported header
mdef.cmi_file = item.GetMetadata("CppM_CMI_File");
string def_file = item.GetMetadata("CppM_ModuleDefinitionFile");
// todo: item.tlog_set.inputs.Add(NormalizeFilePath(cppm.ClangScanDepsPath)); needed ?
// todo: item.tlog_set.outputs.Add(NormalizeFilePath(def_file)); needed ?
SerializeToFile(mdef, def_file); // todo: store these in the DB and only touch files necessary to get ninja to work ?
var entry_to_add = ModuleMap.Entry.create(item.path, def_file, mdef);
item.target.map.entries.Add(entry_to_add);
item.visited = true;
LogScanReport(item.path, mdef);
}
};
bool RunIncrementalScanner(ItemSet itemset) {
var scanner = new Scanner();
var tool_type = Scanner.Type.CLANG_SCAN_DEPS;
var tool_path = ClangScanDepsPath;
var int_dir = IntDir;
var items_by_target = itemset.Items.GroupBy(item => item.target);
var scan_items = GetScanItems(itemset, items_by_target, add_commands: true);
var concurrent_tagets = true;
var file_tracker_running = false;
var observer = new OOD_Deps_Observer { cppm = this, itemset = itemset };
bool submit_previous_results = false;
Directory.CreateDirectory(DB_Path);
scanner.scan(tool_type, tool_path, DB_Path, int_dir, scan_items,
concurrent_tagets, file_tracker_running, observer, submit_previous_results);
if(false) {
// todo: handle various scan errors
return false;
}
foreach(var group in items_by_target) {
var target = group.Key;
Log.LogMessage(MessageImportance.High, "OOD in {0} - {1}", target.project.GetPropertyValue("ProjectName"),
String.Join(",", group.Where(item => item.is_ood).Select(item => item.GetMetadata("FileName"))));
}
var remaining_ood = itemset.Items.Where(i => i.is_ood && !i.visited);
if(remaining_ood.Count() > 0) {
foreach(var item in remaining_ood)
Log.LogMessage(MessageImportance.High, "ERROR: source file {0} was not successfully preprocessed", item.path);
return false;
}
return true;
}
bool RunIncrementalScanner(ItemSet itemset, string measure_message) {
return CallMeasured(measure_message, () => RunIncrementalScanner(itemset));
}
bool IsProjectDirty(string project_file, string module_map_file) {
// note: NewerThan returns false if project exist but the module map does not
if(!NewerThan(module_map_file, project_file))
return true;
// todo: check imported property sheets, system environment ... ?
return false;
}
bool PreProcess(ItemSet itemset)
{
//Log.LogMessage(MessageImportance.High, "CppM: PreProcess");
var items_by_project = itemset.Items.GroupBy(i => i.target); // todo: is this efficient ?
foreach(var group in items_by_project) {
var target = group.Key;
target.map = new ModuleMap();
}
if(!RunIncrementalScanner(itemset, "scanner")) {
return false;
}
int nr_ood = itemset.Items.Where(i => i.is_ood).Count();
// scan out of date files for dependencies, add them to the module map(s)
foreach(var group in items_by_project) {
var target = group.Key;
var module_map_file = target.int_dir + ModuleMapFile;
var module_map = target.map;
// even if all of the files up to date, it's still possible
// for the module map to be out of date, e.g if a file is removed from the project
if(nr_ood > 0 || IsProjectDirty(target.project.FullPath, module_map_file))
{
var old_module_map = DeserializeIfExists<ModuleMap>(module_map_file);
var old_src_to_entry = GetModuleMap_SrcToEntry(old_module_map);
foreach(var item in group) {
var src_file = item.path;
if(item.is_ood) continue;
ModuleMap.Entry entry_to_add = null;
string def_file = item.GetMetadata("CppM_ModuleDefinitionFile");
// if a source file is ood that means it (or some header it includes)
// is newer than the module definition file
// so if it's up to date we could just read the definition file
// but it should be faster to read the definition from the module map file instead
if(old_module_map != null && NewerThan(module_map_file, def_file)) {
if(!old_src_to_entry.TryGetValue(src_file, out entry_to_add)) {
throw new System.Exception(String.Format("ERROR: module map {0} created after " +
"definition file {1}, but does not contain an entry for " +
"source file {2} ??", module_map_file, def_file, src_file));
}
} else {
// unless the last preprocess didn't finish successfully
var module_def = DeserializeIfExists<ModuleDefinition>(def_file);
if(module_def == null)
throw new System.Exception(String.Format("{0} is not OOD but {1} is missing, " +
"the scanner DB is probably out of sync", src_file, def_file));
entry_to_add = ModuleMap.Entry.create(src_file, def_file, module_def);
}
module_map.entries.Add(entry_to_add);
}
SerializeToFile(module_map, module_map_file);
} else {
File.SetLastWriteTimeUtc(module_map_file, System.DateTime.UtcNow);
}
}
return true;
}
//a node in the build DAG
//which may correspond to one of the Items in the preprocessed ItemSet
//but may also come from an external module map
public class Node
{
public Target target = null;
public ModuleMap.Entry entry = null;
public bool visited = false;
public List<Node> imports_nodes = new List<Node>();
public List<Node> imported_by_nodes = new List<Node>();
}
Dictionary<string, Node> module_to_node = new Dictionary<string, Node>();
bool GetNodes(ItemSet itemset)
{
// todo: try to keep the whole project tree in memory
// rather than reloading the subtrees over and over again during a build
// todo: maybe parallelize this ?
// todo: look for import cycles!
foreach(var target in all_targets) {
// we expect the module map to be up to date at this point
ModuleMap module_map = Deserialize<ModuleMap>(target.int_dir + ModuleMapFile);
foreach(ModuleMap.Entry entry in module_map.entries) {
var node = new Node {
target = target,
entry = entry
};
// imports_nodes and imported_by_nodes will be filled in later if needed
if(entry.exported_module != "") {
if(module_to_node.ContainsKey(entry.exported_module)) {
Log.LogMessage(MessageImportance.High, "duplicate module {0}", entry.exported_module);
return false;
}
module_to_node.Add(entry.exported_module, node);
}
//this might not be loaded for files in other projects
if(target == root_target) // todo: check that instead
itemset.Get(entry.source_file).node = node;
target.nodes.Add(node);
}
}
return true;
}
class ModuleNotFoundException : System.Exception {
}
// todo: use visitor colors ?
void ClearVisited(Node node) {
if(!node.visited)
return;
node.visited = false;
foreach(Node imported_node in node.imports_nodes)
ClearVisited(imported_node);
}
string GetSingleModuleReference(Node node) {
if(node.entry.cmi_file == "")
return "";
string prefix = "";
if(IsLLVM()) {
prefix = "-Xclang \"-fmodule-file=";
if(!node.entry.importable_header)
prefix += node.entry.exported_module + "=";
} else {
prefix = "/module:reference \"";
}
return prefix + node.entry.cmi_file + "\" ";
}
string GetModuleReferences(Node node, bool root) {
if(node.visited)
return "";
node.visited = true;
string ret = root ? "" : GetSingleModuleReference(node);
foreach(Node imported_node in node.imports_nodes)
ret += GetModuleReferences(imported_node, false);
return ret;
}
string NinjaEscape(string str) {
return str.Replace("$","$$").Replace(" ", "$ ").Replace(":","$:");
}
// https://stackoverflow.com/questions/478826/c-sharp-filepath-recasing
static string GetProperDirectoryCapitalization(System.IO.DirectoryInfo dirInfo)
{
var parentDirInfo = dirInfo.Parent;
if (null == parentDirInfo)
return dirInfo.Name;
return Path.Combine(GetProperDirectoryCapitalization(parentDirInfo),
parentDirInfo.GetDirectories(dirInfo.Name)[0].Name);
}
static string GetProperFilePathCapitalization(string filename)
{
FileInfo fileInfo = new FileInfo(filename);
var dirInfo = fileInfo.Directory;
return Path.Combine(GetProperDirectoryCapitalization(dirInfo),
dirInfo.GetFiles(fileInfo.Name)[0].Name);
}
// fix clang warning : non-portable path to file .. specified path differs in case from file name on disk
string PortablePath(string str) {
return GetProperFilePathCapitalization(str).Replace("\\", "/");
}
void GenerateNinjaForNode(Node node, BuildMode build_mode)
{
if(node.visited)
return;
node.visited = true;
foreach(string import in node.entry.imported_modules) {
Node import_node = null;
if(!module_to_node.TryGetValue(import, out import_node)) {
Log.LogMessage(MessageImportance.High, "imported module '{0}' not found in global module map", import);
throw new ModuleNotFoundException();
}
// downward edges are needed in the whole subtree for the references generation
node.imports_nodes.Add(import_node);
GenerateNinjaForNode(import_node, build_mode);
}
// we may not need to create rules for files in other projects
if(build_mode == BuildMode.CurrentTarget && node.target != root_target)
return;
bool is_module = (node.entry.exported_module != "");
string outputs = "";
if(is_module) {
if(IsLLVM()) // for LLVM we generate the cmi file first
outputs = NinjaEscape(node.entry.cmi_file);
else
outputs = NinjaEscape(node.entry.obj_file) + " " + NinjaEscape(node.entry.cmi_file);
} else {
outputs = NinjaEscape(node.entry.obj_file);
}
var inputs = String.Join(" ", node.imports_nodes.Select(n => n.entry.cmi_file)
.Append(node.entry.mdef_file).Select(f => NinjaEscape(f)));
ClearVisited(node);
// todo: write rules for each unique base command and then only write the module args/references for each file
string base_command = node.entry.base_command + " " + GetModuleReferences(node, true);
string command = base_command + GetModuleArgsForNode(node);
node.target.ninja += String.Format("build {0}: cc {1}\n cmd = {2}\n", outputs, inputs, command);
if(is_module && IsLLVM()) {
outputs = NinjaEscape(node.entry.obj_file);
node.target.ninja += String.Format("build {0}: cc {1}\n cmd = {2}\n", outputs, inputs, base_command);
}
// todo: add cmi hashing, maybe use tracker.exe as well ?
}
bool GenerateNinjaFiles(BuildMode build_mode) {
// todo: perhaps we could generate the ninjas for the targets in parallel ?
// todo: for the SelectedFiles build mode we could choose not to generate ninja rules
// for nodes that are not transitive dependencies of the selected files,
// but we might want to avoid regenerating the ninja files all the time
// so the rules should be regenerated for targets that are out of date
var for_targets = build_mode != BuildMode.CurrentTarget ? all_targets :
new List<Target>{root_target};
foreach(var target in for_targets) {
// don't use a shared ninja log because it's not safe to write to that concurrently
// but CMake uses relative paths to the solution dir in the commands
// so keep the working directory unchanged and supply a different path for the ninja logs
target.ninja = "builddir = " + NinjaEscape(target.int_dir) + "\n"
+ "rule cc\n command = $cmd\n";
}
try {
IEnumerable<Node> for_nodes = for_targets.SelectMany(t => t.nodes);
foreach(var node in for_nodes)
GenerateNinjaForNode(node, build_mode);
} catch(ModuleNotFoundException) {
return false;
}
foreach(var target in for_targets)
File.WriteAllText(target.int_dir + NinjaBuildFile, target.ninja);
if(build_mode != BuildMode.CurrentTarget) {
// generate another build file that builds the current project and its transitively referenced projects.
// in the FullSubTree build mode the current target is the dummy all-build project, so we should skip that.
IEnumerable<Target> build_rec_targets = (build_mode != BuildMode.FullSubTree) ? all_targets :
all_targets.Where(t => (t != root_target));
File.WriteAllLines(IntDir + NinjaRecursiveBuildFile, build_rec_targets.Select(
target => "subninja " + NinjaEscape(target.int_dir + NinjaBuildFile)));
}
// todo: in BuildMode.FullSubTree we should also link the libraries/executables
return true;
}
string GetModuleArgsForNode(Node node) {
string args = "";
if(IsLLVM()) {
if(node.entry.exported_module != "") {
if(node.entry.importable_header)
args += "-Xclang -emit-header-module " +
"-Xclang -fmodule-name=" + node.entry.exported_module + " ";
else
args += "-Xclang -emit-module-interface ";
args += "-o \"" + node.entry.cmi_file + "\" ";
}
} else {
if(node.entry.exported_module != "") {
if(node.entry.importable_header)
args += "/module:export /module:name " + node.entry.exported_module + " ";
else
args += "/module:interface ";
args += "/module:output \"" + node.entry.cmi_file + "\" ";
// AdditionalOptions already contains /modules:stdifcdir
}
}
//note: we assume modules are already enabled and the standard is set to latest
return args;
}
string GetSelectedNinjaTargets(ItemSet itemset)
{
var targets = String.Join("\" \"", CL_Input_Manually_Selected.Select(i => {
var entry = itemset.Get(i.GetMetadata("Identity")).node.entry;
string ret = entry.obj_file;
if(IsLLVM() && entry.exported_module != "")
ret += "\" \"" + entry.cmi_file;
return ret;
}));
return targets == "" ? "" : "\"" + targets + "\"";
}
bool Compile(ItemSet itemset, BuildMode build_mode)
{
//Log.LogMessage(MessageImportance.High, "CppM: Compile");
if(!GetNodes(itemset)) {
Log.LogMessage(MessageImportance.High, "failed to read global module map");
return false;
}
if(!GenerateNinjaFiles(build_mode))
return false;
// todo: pass -j N to ninja based on the VS settings
var ninja_file = IntDir + (build_mode == BuildMode.CurrentTarget ? NinjaBuildFile : NinjaRecursiveBuildFile);
var command = String.Format("\"{0}\" -f \"{1}\" {2}",
NinjaExecutablePath, ninja_file, GetSelectedNinjaTargets(itemset));
var exec_res = ExecuteCommand(command, measure_message: "ninja");
if(exec_res.exit_code != 0) {
Log.LogMessage(MessageImportance.High, "ERROR: compilation failed");
return false;
}
return true;
}
public override bool Execute()
{
if(JustCleanItems == "true")
return CleanItems();
InitSharedBuildState();
var build_mode = GetBuildMode();
CreateProjectCollection();//"creating project collection");
var itemset = InitItemSet();
if(build_mode != BuildMode.CurrentTarget)
LoadSubtreeItems(itemset, "building project subtree");
// todo: should be a different set of importable headers for each project when building the subtree
all_importable_headers_tmp = GetImportableHeaders();
return PreProcess(itemset) && Compile(itemset, build_mode);
}
}