17db96d56Sopenharmony_ci""" 27db96d56Sopenharmony_ciA script that replaces an old file with a new one, only if the contents 37db96d56Sopenharmony_ciactually changed. If not, the new file is simply deleted. 47db96d56Sopenharmony_ci 57db96d56Sopenharmony_ciThis avoids wholesale rebuilds when a code (re)generation phase does not 67db96d56Sopenharmony_ciactually change the in-tree generated code. 77db96d56Sopenharmony_ci""" 87db96d56Sopenharmony_ci 97db96d56Sopenharmony_ciimport contextlib 107db96d56Sopenharmony_ciimport os 117db96d56Sopenharmony_ciimport os.path 127db96d56Sopenharmony_ciimport sys 137db96d56Sopenharmony_ci 147db96d56Sopenharmony_ci 157db96d56Sopenharmony_ci@contextlib.contextmanager 167db96d56Sopenharmony_cidef updating_file_with_tmpfile(filename, tmpfile=None): 177db96d56Sopenharmony_ci """A context manager for updating a file via a temp file. 187db96d56Sopenharmony_ci 197db96d56Sopenharmony_ci The context manager provides two open files: the source file open 207db96d56Sopenharmony_ci for reading, and the temp file, open for writing. 217db96d56Sopenharmony_ci 227db96d56Sopenharmony_ci Upon exiting: both files are closed, and the source file is replaced 237db96d56Sopenharmony_ci with the temp file. 247db96d56Sopenharmony_ci """ 257db96d56Sopenharmony_ci # XXX Optionally use tempfile.TemporaryFile? 267db96d56Sopenharmony_ci if not tmpfile: 277db96d56Sopenharmony_ci tmpfile = filename + '.tmp' 287db96d56Sopenharmony_ci elif os.path.isdir(tmpfile): 297db96d56Sopenharmony_ci tmpfile = os.path.join(tmpfile, filename + '.tmp') 307db96d56Sopenharmony_ci 317db96d56Sopenharmony_ci with open(filename, 'rb') as infile: 327db96d56Sopenharmony_ci line = infile.readline() 337db96d56Sopenharmony_ci 347db96d56Sopenharmony_ci if line.endswith(b'\r\n'): 357db96d56Sopenharmony_ci newline = "\r\n" 367db96d56Sopenharmony_ci elif line.endswith(b'\r'): 377db96d56Sopenharmony_ci newline = "\r" 387db96d56Sopenharmony_ci elif line.endswith(b'\n'): 397db96d56Sopenharmony_ci newline = "\n" 407db96d56Sopenharmony_ci else: 417db96d56Sopenharmony_ci raise ValueError(f"unknown end of line: {filename}: {line!a}") 427db96d56Sopenharmony_ci 437db96d56Sopenharmony_ci with open(tmpfile, 'w', newline=newline) as outfile: 447db96d56Sopenharmony_ci with open(filename) as infile: 457db96d56Sopenharmony_ci yield infile, outfile 467db96d56Sopenharmony_ci update_file_with_tmpfile(filename, tmpfile) 477db96d56Sopenharmony_ci 487db96d56Sopenharmony_ci 497db96d56Sopenharmony_cidef update_file_with_tmpfile(filename, tmpfile, *, create=False): 507db96d56Sopenharmony_ci try: 517db96d56Sopenharmony_ci targetfile = open(filename, 'rb') 527db96d56Sopenharmony_ci except FileNotFoundError: 537db96d56Sopenharmony_ci if not create: 547db96d56Sopenharmony_ci raise # re-raise 557db96d56Sopenharmony_ci outcome = 'created' 567db96d56Sopenharmony_ci os.replace(tmpfile, filename) 577db96d56Sopenharmony_ci else: 587db96d56Sopenharmony_ci with targetfile: 597db96d56Sopenharmony_ci old_contents = targetfile.read() 607db96d56Sopenharmony_ci with open(tmpfile, 'rb') as f: 617db96d56Sopenharmony_ci new_contents = f.read() 627db96d56Sopenharmony_ci # Now compare! 637db96d56Sopenharmony_ci if old_contents != new_contents: 647db96d56Sopenharmony_ci outcome = 'updated' 657db96d56Sopenharmony_ci os.replace(tmpfile, filename) 667db96d56Sopenharmony_ci else: 677db96d56Sopenharmony_ci outcome = 'same' 687db96d56Sopenharmony_ci os.unlink(tmpfile) 697db96d56Sopenharmony_ci return outcome 707db96d56Sopenharmony_ci 717db96d56Sopenharmony_ci 727db96d56Sopenharmony_ciif __name__ == '__main__': 737db96d56Sopenharmony_ci import argparse 747db96d56Sopenharmony_ci parser = argparse.ArgumentParser() 757db96d56Sopenharmony_ci parser.add_argument('--create', action='store_true') 767db96d56Sopenharmony_ci parser.add_argument('--exitcode', action='store_true') 777db96d56Sopenharmony_ci parser.add_argument('filename', help='path to be updated') 787db96d56Sopenharmony_ci parser.add_argument('tmpfile', help='path with new contents') 797db96d56Sopenharmony_ci args = parser.parse_args() 807db96d56Sopenharmony_ci kwargs = vars(args) 817db96d56Sopenharmony_ci setexitcode = kwargs.pop('exitcode') 827db96d56Sopenharmony_ci 837db96d56Sopenharmony_ci outcome = update_file_with_tmpfile(**kwargs) 847db96d56Sopenharmony_ci if setexitcode: 857db96d56Sopenharmony_ci if outcome == 'same': 867db96d56Sopenharmony_ci sys.exit(0) 877db96d56Sopenharmony_ci elif outcome == 'updated': 887db96d56Sopenharmony_ci sys.exit(1) 897db96d56Sopenharmony_ci elif outcome == 'created': 907db96d56Sopenharmony_ci sys.exit(2) 917db96d56Sopenharmony_ci else: 927db96d56Sopenharmony_ci raise NotImplementedError 93