1 #!/usr/bin/python 2 3 import hashlib 4 import operator 5 import os 6 import shutil 7 import stat 8 import subprocess 9 import sys 10 import tempfile 11 12 13 def rel_to_abs(rel_path): 14 return os.path.join(script_path, rel_path) 15 16 17 java_bin_path = os.getenv('JAVA_HOME', '') 18 if java_bin_path: 19 java_bin_path = os.path.join(java_bin_path, 'bin') 20 21 main_class = 'org.chromium.devtools.jsdoc.JsDocValidator' 22 jar_name = 'jsdoc-validator.jar' 23 hashes_name = 'hashes' 24 src_dir = 'src' 25 script_path = os.path.dirname(os.path.abspath(__file__)) 26 closure_jar_relpath = os.path.join('..', 'closure', 'compiler.jar') 27 src_path = rel_to_abs(src_dir) 28 hashes_path = rel_to_abs(hashes_name) 29 30 31 def get_file_hash(file, blocksize=65536): 32 sha = hashlib.sha256() 33 buf = file.read(blocksize) 34 while len(buf) > 0: 35 sha.update(buf) 36 buf = file.read(blocksize) 37 return sha.hexdigest() 38 39 40 def traverse(hasher, path): 41 abs_path = rel_to_abs(path) 42 info = os.lstat(abs_path) 43 quoted_name = repr(path.replace('\\', '/')) 44 if stat.S_ISDIR(info.st_mode) and not os.path.basename(path).startswith('.'): 45 hasher.update('d ' + quoted_name + '\n') 46 for entry in sorted(os.listdir(abs_path)): 47 traverse(hasher, os.path.join(path, entry)) 48 elif stat.S_ISREG(info.st_mode) and path.endswith('.java'): 49 hasher.update('r ' + quoted_name + ' ') 50 hasher.update(str(info.st_size) + ' ') 51 with open(abs_path, 'Ur') as file: 52 f_hash = get_file_hash(file) 53 hasher.update(f_hash + '\n') 54 55 56 def get_src_dir_hash(dir): 57 sha = hashlib.sha256() 58 traverse(sha, dir) 59 return sha.hexdigest() 60 61 62 def get_actual_hashes(): 63 hashed_files = [(jar_name, True)] 64 hashes = {} 65 for (file_name, binary) in hashed_files: 66 try: 67 hash = get_file_hash(open(file_name, 'rb' if binary else 'r')) 68 hashes[file_name] = hash 69 except IOError: 70 hashes[file_name] = '0' 71 hashes[src_dir] = get_src_dir_hash(src_dir) 72 return hashes 73 74 75 def get_expected_hashes(): 76 try: 77 with open(hashes_path, 'r') as file: 78 return {file_name: hash for (file_name, hash) in [(name.strip(), hash.strip()) for (hash, name) in [line.split(' ', 1) for line in file]]} 79 except: 80 return None 81 82 83 def run_and_communicate(command, error_template): 84 proc = subprocess.Popen(command, stdout=subprocess.PIPE, shell=True) 85 proc.communicate() 86 if proc.returncode: 87 print >> sys.stderr, error_template % proc.returncode 88 sys.exit(proc.returncode) 89 90 91 def build_artifacts(): 92 print 'Compiling...' 93 java_files = [] 94 for root, dirs, files in sorted(os.walk(src_path)): 95 for file_name in files: 96 if file_name.endswith('.java'): 97 java_files.append(os.path.join(root, file_name)) 98 99 bin_path = tempfile.mkdtemp() 100 manifest_file = tempfile.NamedTemporaryFile(mode='wt', delete=False) 101 try: 102 manifest_file.write('Class-Path: %s\n' % closure_jar_relpath) 103 manifest_file.close() 104 javac_path = os.path.join(java_bin_path, 'javac') 105 javac_command = '%s -d %s -cp %s %s' % (javac_path, bin_path, rel_to_abs(closure_jar_relpath), ' '.join(java_files)) 106 run_and_communicate(javac_command, 'Error: javac returned %d') 107 108 print 'Building jar...' 109 artifact_path = rel_to_abs(jar_name) 110 jar_path = os.path.join(java_bin_path, 'jar') 111 jar_command = '%s cvfme %s %s %s -C %s .' % (jar_path, artifact_path, manifest_file.name, main_class, bin_path) 112 run_and_communicate(jar_command, 'Error: jar returned %d') 113 finally: 114 os.remove(manifest_file.name) 115 shutil.rmtree(bin_path, True) 116 117 118 def update_hashes(): 119 print 'Updating hashes...' 120 with open(hashes_path, 'w') as file: 121 file.writelines(['%s %s\n' % (hash, name) for (name, hash) in get_actual_hashes().iteritems()]) 122 123 124 def hashes_modified(): 125 expected_hashes = get_expected_hashes() 126 if not expected_hashes: 127 return [('<no expected hashes>', 1, 0)] 128 actual_hashes = get_actual_hashes() 129 results = [] 130 for name, expected_hash in expected_hashes.iteritems(): 131 actual_hash = actual_hashes.get(name) 132 if expected_hash != actual_hash: 133 results.append((name, expected_hash, actual_hash)) 134 return results 135 136 137 def help(): 138 print 'usage: %s [option]' % os.path.basename(__file__) 139 print 'Options:' 140 print '--force-rebuild: Rebuild classes and jar even if there are no source file changes' 141 print '--no-rebuild: Do not rebuild jar, just update hashes' 142 143 144 def main(): 145 no_rebuild = False 146 force_rebuild = False 147 148 if len(sys.argv) > 1: 149 if sys.argv[1] == '--help': 150 help() 151 return 152 no_rebuild = sys.argv[1] == '--no-rebuild' 153 force_rebuild = sys.argv[1] == '--force-rebuild' 154 155 if not hashes_modified() and not force_rebuild: 156 print 'No modifications found, rebuild not required.' 157 return 158 if not no_rebuild: 159 build_artifacts() 160 161 update_hashes() 162 print 'Done.' 163 164 if __name__ == '__main__': 165 main() 166