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