Home | History | Annotate | Download | only in jsdoc-validator
      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