#!/usr/bin/env python3 import sys import os import argparse import tempfile import shutil import subprocess import re import fileinput from collections import OrderedDict import json import traceback # Hack to combine two argparse formatters class CustomFormatter(argparse.ArgumentDefaultsHelpFormatter, argparse.RawDescriptionHelpFormatter): pass parser = argparse.ArgumentParser( description='Prepare build/ files for the app build process', formatter_class=CustomFormatter) parser.add_argument('--source-dir', '-s', required=True, metavar='BUILD_DIR', help='Directory to build from') parser.add_argument('--output-dir', '-o', required=True, metavar='OUTPUT_DIR', help='Directory to write files to') parser.add_argument('-c', '--channel', default='source', help='channel to add to dev build version number (e.g., "beta" for "5.0-beta.3+a5f28ca8"), or "release" or "source" to skip') parser.add_argument('--commit-hash', '-m', metavar='HASH', help='Commit hash (required for non-release builds)') args = parser.parse_args() def main(): try: app_root_dir = os.path.dirname(os.path.dirname(os.path.realpath(__file__))) lastrev_dir = os.path.join(app_root_dir, 'lastrev') if not os.path.exists(lastrev_dir): os.mkdir(lastrev_dir) tmp_dir = os.path.join(app_root_dir, 'tmp') if args.commit_hash: commit_hash = args.commit_hash[0:9] elif args.channel != "release": raise Exception("--commit-hash must be specified for non-release builds") src_dir = args.source_dir if not os.path.isdir(src_dir): raise Exception(src_dir + " is not a directory") output_dir = args.output_dir if not os.path.isdir(output_dir): raise Exception(output_dir + " is not a directory") if os.listdir(output_dir): raise Exception(output_dir + " is not empty") log("Using source directory of " + src_dir) os.chdir(src_dir) if not os.path.exists('version'): raise FileNotFoundError("version file not found in {0}".format(src_dir)) # Extract version number from version file with open('version') as f: rdf = f.read() m = re.search('([0-9].+)\\.SOURCE', rdf) if not m: raise Exception("Version number not found in version file") version = m.group(1) # Remove tmp build directory if it already exists if os.path.exists(tmp_dir): shutil.rmtree(tmp_dir) os.mkdir(tmp_dir) tmp_src_dir = os.path.join(tmp_dir, 'zotero') # Export a clean copy of the source tree subprocess.check_call([ 'rsync', '-aL', # Exclude hidden files '--exclude', '.*', '--exclude', '#*', '--exclude', 'package.json', '--exclude', 'package-lock.json', '.' + os.sep, tmp_src_dir + os.sep ]) # Make sure rsync worked d = os.path.join(tmp_src_dir, 'chrome') if not os.path.isdir(d): raise FileNotFoundError(d + " not found") # Delete CSL locale support files subprocess.check_call([ 'find', os.path.normpath(tmp_src_dir + '/chrome/content/zotero/locale/csl/'), '-mindepth', '1', '!', '-name', '*.xml', '!', '-name', 'locales.json', #'-print', '-delete' ]) # Delete styles build script os.remove(os.path.join(tmp_src_dir, 'styles', 'update')) translators_dir = os.path.join(tmp_src_dir, 'translators') # Move deleted.txt out of translators directory f = os.path.join(translators_dir, 'deleted.txt') if os.path.exists(f): shutil.move(f, tmp_src_dir) # Build translator index index = OrderedDict() for fn in sorted((fn for fn in os.listdir(translators_dir)), key=str.lower): if not fn.endswith('.js'): continue with open(os.path.join(translators_dir, fn), 'r', encoding='utf-8') as f: contents = f.read() # Parse out the JSON metadata block m = re.match('^\s*{[\S\s]*?}\s*?[\r\n]', contents) if not m: raise Exception("Metadata block not found in " + f.name) metadata = json.loads(m.group(0)) index[metadata["translatorID"]] = { "fileName": fn, "label": metadata["label"], "lastUpdated": metadata["lastUpdated"] } # Write translator index as JSON file with open(os.path.join(tmp_src_dir, 'translators.json'), 'w', encoding='utf-8') as f: json.dump(index, f, indent=True, ensure_ascii=False) version_file = os.path.join(tmp_src_dir, 'version') log('') log_line() log('Original version:\n') dump_file(version_file) # Modify version as necessary # The dev build revision number is stored in lastrev/{version}-{channel}. # # If we're including it, get the current version number and increment it. if args.channel not in ["release", "source"]: lastrev_file = os.path.join( lastrev_dir, '{0}-{1}'.format(version, args.channel) ) if not os.path.exists(lastrev_file): with open(lastrev_file, 'w') as f: f.write("0") rev = 1 else: with open(lastrev_file, 'r') as f: rev = f.read() rev = int(rev if rev else 0) + 1 if args.channel == "release": rev_sub_str = "" elif args.channel == "source": rev_sub_str = ".SOURCE.{0}".format(commit_hash) else: rev_sub_str = "-{0}.{1}+{2}".format(args.channel, str(rev), commit_hash) # Update version for line in fileinput.FileInput(version_file, inplace=1): line = line.replace('.SOURCE', rev_sub_str) print(line, file=sys.stdout, end='') log('Modified version:\n') dump_file(version_file) log('') log_line() # Move source files to output directory os.rmdir(output_dir) shutil.move(tmp_src_dir, output_dir) # Update lastrev file with new revision number if args.channel not in ["release", "source"]: with open(lastrev_file, 'w') as f: f.write(str(rev)) return 0 except Exception as err: sys.stderr.write("\n" + traceback.format_exc()) return 1 # Clean up finally: if 'tmp_src_dir' in locals() and os.path.exists(tmp_src_dir): shutil.rmtree(tmp_src_dir) def dump_file(f): with open(f, 'r') as f: log(f.read()) def log(msg): print(msg, file=sys.stdout) def log_line(): log('======================================================\n\n') if __name__ == '__main__': sys.exit(main())