1717from cfbs .utils import (
1818 canonify ,
1919 cp ,
20+ cp_dry_itemize ,
2021 deduplicate_def_json ,
22+ file_diff_text ,
2123 find ,
2224 merge_json ,
2325 mkdir ,
2426 pad_right ,
2527 read_json ,
2628 rm ,
29+ save_file ,
2730 sh ,
2831 strip_left ,
2932 touch ,
@@ -129,8 +132,68 @@ def _perform_copy_step(args, source, destination, prefix):
129132 dst = ""
130133 print ("%s copy '%s' 'masterfiles/%s'" % (prefix , src , dst ))
131134 src , dst = os .path .join (source , src ), os .path .join (destination , dst )
135+
136+ step_diffs_data = ""
137+
138+ itemization = cp_dry_itemize (src , dst )
139+ noop_overwrites_relpaths = []
140+ actual_overwrites_relpaths = []
141+ for item_string , file_relpath in itemization :
142+ if item_string [1 ] != "f" :
143+ # only consider regular files
144+ continue
145+ if item_string [0 ] == "." or len (item_string ) < 3 :
146+ # the first character being a dot means that it's a no-op overwrite (possibly except file attributes)
147+ # explanation for the `< 3` comparison:
148+ # if all attributes are unchanged, the rsync item string will use spaces instead of dots and they will have been parsed away earlier
149+ noop_overwrites_relpaths .append (file_relpath )
150+ continue
151+ if item_string [2 ] == "+" :
152+ # the copied file is new
153+ continue
154+ if item_string [2 ] == "c" :
155+ # the copied file has a different checksum
156+ actual_overwrites_relpaths .append (file_relpath )
157+ continue
158+ elif item_string [2 ] == "." :
159+ # the copied regular file doesn't have a different checksum
160+ noop_overwrites_relpaths .append (file_relpath )
161+ log .debug ("Novel item string: %s %s" % (item_string , file_relpath ))
162+ for file_relpath in actual_overwrites_relpaths :
163+ if os .path .isfile (src ):
164+ fileA = src
165+ else :
166+ fileA = os .path .join (src , file_relpath )
167+ if os .path .isfile (dst ):
168+ fileB = dst
169+ else :
170+ fileB = os .path .join (dst , file_relpath )
171+ file_diff_data = file_diff_text (fileA , fileB )
172+ step_diffs_data += file_diff_data
173+ if len (noop_overwrites_relpaths ) > 0 :
174+ warning_message = (
175+ "Identical file overwrites occured during copy.\n "
176+ + " Check your modules and their build steps to ascertain whether this is intentional.\n "
177+ + " In most cases, the cause is a file from a latter module already being provided by an earlier module (commonly stock masterfiles).\n "
178+ + " In that case, the file is best deleted from the latter module(s).\n "
179+ + " Identical overwrites count: %s\n " % len (noop_overwrites_relpaths )
180+ )
181+ # display affected files, without flooding the output
182+ if len (noop_overwrites_relpaths ) < 20 :
183+ for overwrite_noop in noop_overwrites_relpaths :
184+ warning_message += " " + overwrite_noop + "\n "
185+ else :
186+ for overwrite_noop in noop_overwrites_relpaths [:9 ]:
187+ warning_message += " " + overwrite_noop + "\n "
188+ warning_message += " ...\n "
189+ for overwrite_noop in noop_overwrites_relpaths [- 9 :]:
190+ warning_message += " " + overwrite_noop + "\n "
191+ # display all the messages as one warning
192+ log .warning (warning_message )
132193 cp (src , dst )
133194
195+ return step_diffs_data
196+
134197
135198def _perform_run_step (args , source , prefix ):
136199 shell_command = " " .join (args )
@@ -316,7 +379,7 @@ def _perform_replace_version_step(module, i, args, name, destination, prefix):
316379 _perform_replacement (n , to_replace , version , filename )
317380
318381
319- def perform_build (config : CFBSConfig ) -> int :
382+ def perform_build (config : CFBSConfig , diffs_filename = None ) -> int :
320383 if not config .get ("build" ):
321384 raise CFBSExitError ("No 'build' key found in the configuration" )
322385
@@ -349,6 +412,8 @@ def perform_build(config: CFBSConfig) -> int:
349412 % (step , expected , actual )
350413 )
351414
415+ diffs_data = ""
416+
352417 print ("\n Steps:" )
353418 max_length = config .longest_module_key_length ("name" )
354419 for module in config ["build" ]:
@@ -362,7 +427,8 @@ def perform_build(config: CFBSConfig) -> int:
362427 prefix = "%03d %s :" % (counter , pad_right (name , max_length ))
363428
364429 if operation == "copy" :
365- _perform_copy_step (args , source , destination , prefix )
430+ step_diffs_data = _perform_copy_step (args , source , destination , prefix )
431+ diffs_data += step_diffs_data
366432 elif operation == "run" :
367433 _perform_run_step (args , source , prefix )
368434 elif operation == "delete" :
@@ -386,6 +452,15 @@ def perform_build(config: CFBSConfig) -> int:
386452 module , i , args , name , destination , prefix
387453 )
388454
455+ if diffs_filename is not None :
456+ try :
457+ save_file (diffs_filename , diffs_data )
458+ except IsADirectoryError :
459+ print ("" )
460+ log .warning (
461+ "An existing directory was provided as the '--diffs' file path - writing the diffs file for the build failed - continuing build..."
462+ )
463+
389464 assert os .path .isdir ("./out/masterfiles/" )
390465 shutil .copyfile ("./cfbs.json" , "./out/masterfiles/cfbs.json" )
391466 if os .path .isfile ("out/masterfiles/def.json" ):
0 commit comments