11module mcl.commands.compare_disko ;
22
3+ import core.cpuid : threadsPerCPU;
4+
35import mcl.utils.env : optional, parseEnv;
46import mcl.utils.nix : nix;
57import mcl.utils.path : getTopLevel;
8+ import mcl.utils.path : rootDir, resultDir, gcRootsDir, createResultDirs;
69import mcl.utils.process : execute;
10+ import mcl.utils.log : errorAndExit;
711
812import std.typecons : Tuple , tuple;
913import std.file : mkdirRecurse, exists, rmdirRecurse;
@@ -13,11 +17,14 @@ import std.json : JSONValue, parseJSON, JSONOptions;
1317import std.file : write;
1418import std.logger : tracef, errorf, infof;
1519import std.process : environment;
16- import mcl.utils.log : errorAndExit;
20+ import std.algorithm : map, filter, reduce, chunkBy, find, any, sort, startsWith, each, canFind, fold, uniq;
21+ import std.array ;
22+ import std.meta : Filter;
1723
1824struct Params
1925{
2026 @optional() string baseBranch;
27+ @optional() int maxWorkers;
2128
2229 void setup (){
2330 if (baseBranch == null ){
@@ -26,27 +33,68 @@ struct Params
2633 }
2734}
2835
36+ enum ChangeStatus{
37+ Unchanged,
38+ Changed,
39+ Removed,
40+ New
41+ }
42+
2943struct MachineChanges {
3044 string machine;
31- bool _config;
32- bool _create;
45+ ChangeStatus _config;
46+ ChangeStatus _create;
47+ }
48+
49+ template FilterFields (S) {
50+ template isNotExcluded (string memberName) {
51+ enum isNotExcluded = (memberName != " machine" );
52+ }
53+ alias AllMembers = __traits(allMembers, S);
54+ alias FilteredMembers = Filter! (isNotExcluded, AllMembers);
55+ enum FieldNamesExcept = [ FilteredMembers ];
56+ }
57+
58+ enum string [] diskoOptions = FilterFields! (MachineChanges).FieldNamesExcept;
59+
60+ string statusToSymbol (ChangeStatus s) {
61+ final switch (s){
62+ case ChangeStatus.Unchanged:
63+ return " 🟩" ;
64+ break ;
65+ case ChangeStatus.Changed:
66+ return " ⚡" ;
67+ break ;
68+ case ChangeStatus.Removed:
69+ return " 🗑" ;
70+ break ;
71+ case ChangeStatus.New:
72+ return " 🧩" ;
73+ break ;
74+ }
3375}
3476
3577// TODO: handle case where a machine is missing from one branch
3678export void compare_disko (string [] args){
3779 nix.eval! JSONValue(" " , [], " " );
3880 const params = parseEnv! Params;
81+
3982 JSONValue configurations = nix.flake! JSONValue(" " , [], " show" );
40- string [] machines;
83+
84+ string [] machinesNew;
4185 foreach (string k, JSONValue v; configurations[" nixosConfigurations" ]){
4286 if (k[$- 3 .. $] != " -vm" &&
4387 k != " gitlab-runner-container" &&
4488 k != " minimal-container" )
4589 {
46- machines ~= k;
90+ machinesNew ~= k;
4791 }
4892 }
4993
94+ auto attr = constructCommandAttr(" ./." , machinesNew);
95+ auto machineOptionsRootNew = nix.eval! JSONValue(" " , [" --impure" , " --expr" , attr]);
96+
97+
5098 string gitRoot = getTopLevel();
5199 string worktreeBaseBranch = gitRoot~ " -" ~ params.baseBranch;
52100
@@ -55,56 +103,61 @@ export void compare_disko(string[] args){
55103 }
56104
57105 execute([" git" , " worktree" , " add" , worktreeBaseBranch, params.baseBranch]);
58- string freshTestsDir = gitRoot~ " /disko-tests" ;
59- string staleTestsDir = worktreeBaseBranch~ " /disko-tests" ;
60- mkdirRecurse(staleTestsDir);
61- mkdirRecurse(freshTestsDir);
62106
63- Tuple ! (string , string )[] machineDiffs;
107+ configurations = nix.flake! JSONValue(worktreeBaseBranch, [], " show" );
108+
109+ string [] machinesOld;
110+ foreach (string k, JSONValue v; configurations[" nixosConfigurations" ]){
111+ if (k[$- 3 .. $] != " -vm" &&
112+ k != " gitlab-runner-container" &&
113+ k != " minimal-container" )
114+ {
115+ machinesOld ~= k;
116+ }
117+ }
118+
119+ attr = constructCommandAttr(worktreeBaseBranch, machinesOld);
120+ auto machineOptionsRootOld = nix.eval! JSONValue(" " , [" --impure" , " --expr" , attr]);
121+
64122 MachineChanges[] machineChanges;
65123
124+ string [] machines = uniq(machinesOld ~ machinesNew).array;
125+
66126 foreach (string m; machines){
67127 MachineChanges mc;
68128 mc.machine = m;
69- foreach (setting; __traits (allMembers , MachineChanges)){
70- static if (is (typeof (__traits(getMember, mc, setting)) == bool )){
71- string new_setting =
72- nix.eval(gitRoot~ " #nixosConfigurations." ~ m~ " .config.disko.devices." ~ setting, [
73- " --option" , " warn-dirty" , " false" ,
74- " --accept-flake-config" ]);
75- tracef(" CREATING %s_%s_new FILE" , m, setting);
76- write(freshTestsDir~ " /" ~ m~ " _" ~ setting~ " _new" , new_setting);
77- string old_setting =
78- nix.eval(worktreeBaseBranch~ " #nixosConfigurations." ~ m~ " .config.disko.devices." ~ setting, [
79- " --option" , " warn-dirty" , " false" ,
80- " --accept-flake-config" ]);
81- tracef(" CREATING %s_%s_old FILE" , m, setting);
82- write(staleTestsDir~ " /" ~ m~ " _" ~ setting~ " _old" , old_setting);
83-
84-
85- string diff = execute([
86- " git" , " --no-pager" , " diff" , " --no-index" ,
87- staleTestsDir~ " /" ~ m~ " _" ~ setting~ " _old" ,
88- freshTestsDir~ " /" ~ m~ " _" ~ setting~ " _new" ]);
89-
90- if (diff == " " ){
129+ if (m in machineOptionsRootOld.object && m in machineOptionsRootNew.object) {
130+ static foreach (setting; diskoOptions){
131+ if (machineOptionsRootOld[m][setting] == machineOptionsRootNew[m][setting]){
91132 infof(" ✔ NO DIFFERENCE IN %s" , m);
92- __traits (getMember , mc, setting) = true ;
133+ __traits (getMember , mc, setting) = ChangeStatus.Unchanged ;
93134 }
94135 else {
95136 infof(" ✖ DIFFERENCE IN %s" , m);
96- __traits (getMember , mc, setting) = false ;
137+ __traits (getMember , mc, setting) = ChangeStatus.Changed ;
97138 }
98139 }
99140 }
141+ else if (m in machineOptionsRootOld) {
142+ static foreach (setting; diskoOptions){
143+ infof(" ✖ MACHINE %s NO LONGER EXISTS" , m);
144+ __traits (getMember , mc, setting) = ChangeStatus.Removed;
145+ }
146+ }
147+ else {
148+ static foreach (setting; diskoOptions){
149+ infof(" ✖ MACHINE %s IS NEW" , m);
150+ __traits (getMember , mc, setting) = ChangeStatus.New;
151+ }
152+ }
100153 machineChanges ~= mc;
101154 }
102155 infof(" ------------------------------------------------------" );
103- if (machineDiffs .length == 0 ){
156+ if (machineChanges .length == 0 ){
104157 infof(" ✔✔✔ NO CONFIGS WITH DIFFS" );
105158 }
106159 else {
107- infof(" ✖✖✖ LIST OF CONFIGS WITH DIFFS" );
160+ infof(" ✖✖✖ LIST OF CONFIGS WITH DIFFS" ); // TODO: HANDLE DIFFERENCES ON OPTION LEVEL WITH DIFFERENTIATION OF STATUS
108161 foreach (mc; machineChanges){
109162 infof(mc.machine);
110163 }
@@ -113,7 +166,6 @@ export void compare_disko(string[] args){
113166
114167 // Cleanup
115168 execute([" git" , " worktree" , " remove" , worktreeBaseBranch, " --force" ]);
116- rmdirRecurse(freshTestsDir);
117169}
118170
119171void create_comment (MachineChanges[] machineChanges){
@@ -124,6 +176,11 @@ void create_comment(MachineChanges[] machineChanges){
124176 else {
125177 // TODO: Change the generation of the table to have as many collumns as fields in MachineChanges at compile time
126178 data ~= " \n\n Bellow you will find a summary of machines and whether their disko attributes have differences." ;
179+ data ~= " \n\n **Legend:**" ;
180+ data ~= " \n 🟩 = No changes" ;
181+ data ~= " \n ⚡ = Something is different" ;
182+ data ~= " \n 🗑 = Has been removed" ;
183+ data ~= " \n 🧩 = Has been added" ;
127184
128185 data ~= " \n\n " ;
129186 foreach (string field; __traits (allMembers , MachineChanges)){
@@ -137,18 +194,29 @@ void create_comment(MachineChanges[] machineChanges){
137194
138195 foreach (mc; machineChanges){
139196 foreach (string field; __traits (allMembers , MachineChanges)){
140- static if (is (typeof (__traits(getMember, mc, field)) == bool )){
141- data~= " | " ~ (__traits(getMember, mc, field) ? " 🟩" : " ⚠️" ) ~ " " ;
142- }
143- else static if (is (typeof (__traits(getMember, mc, field)) == string )){
197+ static if (is (typeof (__traits(getMember, mc, field)) == string )){
144198 data~= " | " ~ __traits(getMember, mc, field) ~ " " ;
145199 }
146- else {
147- assert ( 0 ) ;
200+ else {
201+ data ~= " | " ~ statusToSymbol(__traits(getMember, mc, field)) ~ " " ;
148202 }
149203 }
150204 data ~= " |\n " ;
151205 }
152206 }
153207 write(" comment.md" , data);
154208}
209+
210+
211+ string constructCommandAttr (string flakePath, string [] machines){
212+ string attr = " let flake = (builtins.getFlake (builtins.toString " ~ flakePath ~ " )); in { " ;
213+ foreach (m; machines){
214+ attr ~= m ~ " = { " ;
215+ foreach (option; diskoOptions){
216+ attr ~= option ~ " = flake.nixosConfigurations." ~ m ~ " .config.disko.devices." ~ option ~ " ; " ;
217+ }
218+ attr ~= " }; " ;
219+ }
220+ attr ~= " }" ;
221+ return attr;
222+ }
0 commit comments