Merge pull request #15791 from dotnetrt/dstdstsrc
[platform/upstream/coreclr.git] / src / scripts / utilities.py
1 ##
2 ## Licensed to the .NET Foundation under one or more agreements.
3 ## The .NET Foundation licenses this file to you under the MIT license.
4 ## See the LICENSE file in the project root for more information.
5 ##
6 ##  This file provides utility functions to the adjacent python scripts
7
8 from hashlib import sha256
9 from io import StringIO
10 import filecmp
11 import shutil
12 import sys
13 import os
14
15 class WrappedStringIO(StringIO):
16     """A wrapper around StringIO to allow writing str objects"""
17     def write(self, s):
18         if sys.version_info < (3, 0, 0):
19             if isinstance(s, str):
20                 s = unicode(s)
21         super(WrappedStringIO, self).write(s)
22
23 class UpdateFileWriter:
24     """A file-like context object which will only write to a file if the result would be different
25
26     Attributes:
27         filename (str): The name of the file to update
28         stream (WrappedStringIO): The file-like stream provided upon context enter
29
30     Args:
31         filename (str): Sets the filename attribute
32     """
33     filemode = 'w'
34
35     def __init__(self, filename):
36         self.filename = filename
37         self.stream = None
38
39     def __enter__(self):
40         self.stream = WrappedStringIO()
41         return self.stream
42
43     def __exit__(self, exc_type, exc_value, traceback):
44         if exc_value is None:
45             new_content = self.stream.getvalue()
46             new_hash = sha256()
47             cur_hash = sha256()
48
49             try:
50                 with open(self.filename, 'r') as fstream:
51                     cur_hash.update(fstream.read().encode('utf-8'))
52                 file_found = True
53             except IOError:
54                 file_found = False
55
56             if file_found:
57                 new_hash.update(new_content.encode('utf-8'))
58                 update = new_hash.digest() != cur_hash.digest()
59             else:
60                 update = True
61
62             if update:
63                 with open(self.filename, 'w') as fstream:
64                     fstream.write(new_content)
65
66         self.stream.close()
67
68 def open_for_update(filename):
69     return UpdateFileWriter(filename)
70
71 def split_entries(entries, directory):
72     """Given a list of entries in a directory, listing return a set of file and a set of dirs"""
73     files = set([entry for entry in entries if os.path.isfile(os.path.join(directory, entry))])
74     dirs = set([entry for entry in entries if os.path.isdir(os.path.join(directory, entry))])
75
76     return files, dirs
77
78 def update_directory(srcpath, dstpath, recursive=True, destructive=True, shallow=False):
79     """Updates dest directory with files from src directory
80
81     Args:
82         destpath (str): The destination path to sync with the source
83         srcpath (str): The source path to sync to the destination
84         recursive(boolean): If True, descend into and update subdirectories (default: True)
85         destructive(boolean): If True, delete files in the destination which do not exist in the source (default: True)
86         shallow(boolean): If True, only use os.stat to diff files. Do not examine contents (default: False)
87     """
88     srcfiles, srcdirs = split_entries(os.listdir(srcpath), srcpath)
89     dstfiles, dstdirs = split_entries(os.listdir(dstpath), dstpath)
90
91
92     # Update files in both src and destination which are different in destination
93     commonfiles = srcfiles.intersection(dstfiles)
94     _, mismatches, errors = filecmp.cmpfiles(srcpath, dstpath, commonfiles, shallow=shallow)
95
96     if errors:
97         raise RuntimeError("Comparison failed for the following files(s): {}".format(errors))
98
99     for mismatch in mismatches:
100         shutil.copyfile(os.path.join(srcpath, mismatch), os.path.join(dstpath, mismatch))
101
102     # Copy over files from source which do not exist in the destination
103     for missingfile in srcfiles.difference(dstfiles):
104         shutil.copyfile(os.path.join(srcpath, missingfile), os.path.join(dstpath, missingfile))
105
106     #If destructive, delete files in destination which do not exist in sourc
107     if destructive:
108         for deadfile in dstfiles.difference(srcfiles):
109             print(deadfile)
110             os.remove(os.path.join(dstpath, deadfile))
111
112         for deaddir in dstdirs.difference(srcdirs):
113             print(deaddir)
114             shutil.rmtree(os.path.join(dstpath, deaddir))
115
116     #If recursive, do this again for each source directory
117     if recursive:
118         for dirname in srcdirs:
119             dstdir, srcdir = os.path.join(dstpath, dirname), os.path.join(srcpath, dirname)
120             if not os.path.exists(dstdir):
121                 os.makedirs(dstdir)
122             update_directory(srcdir, dstdir, recursive, destructive, shallow)