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,67 @@ 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){
37- nix.eval! JSONValue(" " , [], " " );
3879 const params = parseEnv! Params;
80+
3981 JSONValue configurations = nix.flake! JSONValue(" " , [], " show" );
40- string [] machines;
82+
83+ string [] machinesNew;
4184 foreach (string k, JSONValue v; configurations[" nixosConfigurations" ]){
4285 if (k[$- 3 .. $] != " -vm" &&
4386 k != " gitlab-runner-container" &&
4487 k != " minimal-container" )
4588 {
46- machines ~= k;
89+ machinesNew ~= k;
4790 }
4891 }
4992
93+ auto attr = constructCommandAttr(" ./." , machinesNew);
94+ auto machineOptionsRootNew = nix.eval! JSONValue(" " , [" --impure" , " --expr" , attr]);
95+
96+
5097 string gitRoot = getTopLevel();
5198 string worktreeBaseBranch = gitRoot~ " -" ~ params.baseBranch;
5299
@@ -55,56 +102,61 @@ export void compare_disko(string[] args){
55102 }
56103
57104 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);
62105
63- Tuple ! (string , string )[] machineDiffs;
106+ configurations = nix.flake! JSONValue(worktreeBaseBranch, [], " show" );
107+
108+ string [] machinesOld;
109+ foreach (string k, JSONValue v; configurations[" nixosConfigurations" ]){
110+ if (k[$- 3 .. $] != " -vm" &&
111+ k != " gitlab-runner-container" &&
112+ k != " minimal-container" )
113+ {
114+ machinesOld ~= k;
115+ }
116+ }
117+
118+ attr = constructCommandAttr(worktreeBaseBranch, machinesOld);
119+ auto machineOptionsRootOld = nix.eval! JSONValue(" " , [" --impure" , " --expr" , attr]);
120+
64121 MachineChanges[] machineChanges;
65122
123+ string [] machines = uniq(machinesOld ~ machinesNew).array;
124+
66125 foreach (string m; machines){
67126 MachineChanges mc;
68127 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 == " " ){
128+ if (m in machineOptionsRootOld.object && m in machineOptionsRootNew.object) {
129+ static foreach (setting; diskoOptions){
130+ if (machineOptionsRootOld[m][setting] == machineOptionsRootNew[m][setting]){
91131 infof(" ✔ NO DIFFERENCE IN %s" , m);
92- __traits (getMember , mc, setting) = true ;
132+ __traits (getMember , mc, setting) = ChangeStatus.Unchanged ;
93133 }
94134 else {
95135 infof(" ✖ DIFFERENCE IN %s" , m);
96- __traits (getMember , mc, setting) = false ;
136+ __traits (getMember , mc, setting) = ChangeStatus.Changed ;
97137 }
98138 }
99139 }
140+ else if (m in machineOptionsRootOld) {
141+ static foreach (setting; diskoOptions){
142+ infof(" ✖ MACHINE %s NO LONGER EXISTS" , m);
143+ __traits (getMember , mc, setting) = ChangeStatus.Removed;
144+ }
145+ }
146+ else {
147+ static foreach (setting; diskoOptions){
148+ infof(" ✖ MACHINE %s IS NEW" , m);
149+ __traits (getMember , mc, setting) = ChangeStatus.New;
150+ }
151+ }
100152 machineChanges ~= mc;
101153 }
102154 infof(" ------------------------------------------------------" );
103- if (machineDiffs .length == 0 ){
155+ if (machineChanges .length == 0 ){
104156 infof(" ✔✔✔ NO CONFIGS WITH DIFFS" );
105157 }
106158 else {
107- infof(" ✖✖✖ LIST OF CONFIGS WITH DIFFS" );
159+ infof(" ✖✖✖ LIST OF CONFIGS WITH DIFFS" ); // TODO: HANDLE DIFFERENCES ON OPTION LEVEL WITH DIFFERENTIATION OF STATUS
108160 foreach (mc; machineChanges){
109161 infof(mc.machine);
110162 }
@@ -113,7 +165,6 @@ export void compare_disko(string[] args){
113165
114166 // Cleanup
115167 execute([" git" , " worktree" , " remove" , worktreeBaseBranch, " --force" ]);
116- rmdirRecurse(freshTestsDir);
117168}
118169
119170void create_comment (MachineChanges[] machineChanges){
@@ -124,6 +175,11 @@ void create_comment(MachineChanges[] machineChanges){
124175 else {
125176 // TODO: Change the generation of the table to have as many collumns as fields in MachineChanges at compile time
126177 data ~= " \n\n Bellow you will find a summary of machines and whether their disko attributes have differences." ;
178+ data ~= " \n\n **Legend:**" ;
179+ data ~= " \n 🟩 = No changes" ;
180+ data ~= " \n ⚡ = Something is different" ;
181+ data ~= " \n 🗑 = Has been removed" ;
182+ data ~= " \n 🧩 = Has been added" ;
127183
128184 data ~= " \n\n " ;
129185 foreach (string field; __traits (allMembers , MachineChanges)){
@@ -137,18 +193,29 @@ void create_comment(MachineChanges[] machineChanges){
137193
138194 foreach (mc; machineChanges){
139195 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 )){
196+ static if (is (typeof (__traits(getMember, mc, field)) == string )){
144197 data~= " | " ~ __traits(getMember, mc, field) ~ " " ;
145198 }
146- else {
147- assert ( 0 ) ;
199+ else {
200+ data ~= " | " ~ statusToSymbol(__traits(getMember, mc, field)) ~ " " ;
148201 }
149202 }
150203 data ~= " |\n " ;
151204 }
152205 }
153206 write(" comment.md" , data);
154207}
208+
209+
210+ string constructCommandAttr (string flakePath, string [] machines){
211+ string attr = " let flake = (builtins.getFlake (builtins.toString " ~ flakePath ~ " )); in { " ;
212+ foreach (m; machines){
213+ attr ~= m ~ " = { " ;
214+ foreach (option; diskoOptions){
215+ attr ~= option ~ " = flake.nixosConfigurations." ~ m ~ " .config.disko.devices." ~ option ~ " ; " ;
216+ }
217+ attr ~= " }; " ;
218+ }
219+ attr ~= " }" ;
220+ return attr;
221+ }
0 commit comments