Files
zulip/tools/minify-js
Greg Price a099e698e2 py3: Switch almost all shebang lines to use python3.
This causes `upgrade-zulip-from-git`, as well as a no-option run of
`tools/build-release-tarball`, to produce a Zulip install running
Python 3, rather than Python 2.  In particular this means that the
virtualenv we create, in which all application code runs, is Python 3.

One shebang line, on `zulip-ec2-configure-interfaces`, explicitly
keeps Python 2, and at least one external ops script, `wal-e`, also
still runs on Python 2.  See discussion on the respective previous
commits that made those explicit.  There may also be some other
third-party scripts we use, outside of this source tree and running
outside our virtualenv, that still run on Python 2.
2017-08-16 17:54:43 -07:00

153 lines
6.0 KiB
Python
Executable File

#!/usr/bin/env python3
# Minifies JavaScripts, creating source maps
from __future__ import absolute_import
from __future__ import print_function
import os
import subprocess
import argparse
import sys
import six
parser = argparse.ArgumentParser()
parser.add_argument('--prev-deploy', metavar='DIR',
help='a previous deploy from which to reuse files if possible')
args = parser.parse_args()
prev_deploy = args.prev_deploy
# We have to pull out JS_SPECS, defined in our settings file, so we know what
# JavaScript source files to minify (and what output files to create).
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..'))
import scripts.lib.setup_path_on_import
from typing import Any, Dict, Optional, Set
os.environ['DJANGO_SETTINGS_MODULE'] = 'zproject.settings'
from django.conf import settings
os.chdir(settings.DEPLOY_ROOT)
STATIC_PATH = 'static/'
# Compile Handlebars templates
subprocess.check_call(['tools/compile-handlebars-templates'])
# Create webpack bundle
subprocess.check_call(['tools/webpack'])
def get_changed_source_files(other_checkout):
# type: (str) -> Optional[Set[str]]
""" Get list of changed static files since other_checkout.
If git fails to return a reasonable looking list, this returns None,
in which case it should be assumed no files can be reused from
other_checkout. """
try:
git_dir = os.path.join(other_checkout, '.git')
old_commit_sha1 = subprocess.check_output(['git', 'rev-parse', 'HEAD'],
env={'GIT_DIR': git_dir}, universal_newlines=True)
old_commit_sha1 = old_commit_sha1.rstrip()
git_diff = subprocess.check_output(['git', 'diff', '--name-only',
old_commit_sha1], universal_newlines=True)
except subprocess.CalledProcessError:
# If git returned an error, assume we can't reuse any files, and
# regenerate everything.
print("Warning: git returned an error when comparing to the previous")
print("deploy in %s. Will re-minify JavaScript instead of reusing"
% (other_checkout,))
return None
changed = set() # type: Set[str]
for filename in git_diff.split('\n'):
if filename in ["package.json", "zproject/settings.py", "tools/minify-js"]:
print("Changed a core JS pipeline file; not reusing cached minification results")
return None
if not filename.startswith(STATIC_PATH):
continue # Ignore non-static files.
if filename.endswith('.handlebars'):
continue
changed.add(filename)
return changed
changed_files = set() # type: Set[str]
if prev_deploy:
changed_files_tmp = get_changed_source_files(prev_deploy)
if changed_files_tmp is None:
prev_deploy = None
else:
changed_files = changed_files_tmp
# Always use the newly compiled handlebars templates and webpack bundle.
if prev_deploy:
changed_files.add(os.path.join(STATIC_PATH, 'templates/compiled.js'))
JS_SPECS = settings.JS_SPECS
CLOSURE_BINARY = '/usr/bin/closure-compiler'
if not os.path.exists(CLOSURE_BINARY):
CLOSURE_BINARY = 'tools/closure-compiler/run'
if not os.path.exists(CLOSURE_BINARY):
print("closure-compiler not installed; the Vagrant tools/provision installs it via apt "
"or you can manually unpack http://dl.google.com/closure-compiler/compiler-latest.zip to "
"tools/closure-compiler")
sys.exit(1)
# Where to put minified JS and source maps
MIN_DIR = os.path.join(STATIC_PATH, 'min/')
MAP_DIR = os.path.join(STATIC_PATH, 'source-map/')
subprocess.check_call(['mkdir', '-p', MIN_DIR, MAP_DIR])
for js_group_filespec_pair in six.iteritems(JS_SPECS):
# JS_SPECS is not typed, so forcefully type keys and values being read from JS_SPECS
js_group = js_group_filespec_pair[0] # type: str
filespec = js_group_filespec_pair[1] # type: Dict[str, Any]
# JS_SPECS look like 'js/foobar.js'.
# changed_files look like 'static/js/foobar.js'.
# So we prepend 'static/' to the JS_SPECS so these match up.
in_files = [os.path.join(STATIC_PATH, filename)
for filename in filespec['source_filenames']]
min_files = [os.path.join(STATIC_PATH, filename)
for filename in filespec.get('minifed_source_filenames', [])]
out_file = os.path.join(MIN_DIR, os.path.basename(filespec['output_filename']))
map_file = os.path.join(MAP_DIR, os.path.basename(filespec['output_filename']) +
'.map')
if ('force_minify' not in filespec) and \
(prev_deploy and len(set(in_files) & changed_files) == 0):
# Try to reuse the output file from previous deploy
try:
for dest in [out_file, map_file]:
src = os.path.join(prev_deploy, dest)
os.path.getsize(src) # Just to throw error if it doesn't exist.
if os.path.abspath(src) != os.path.abspath(dest):
subprocess.check_call(['cp', src, dest])
continue # Copy succeeded, so go on to next file.
except (subprocess.CalledProcessError, OSError):
pass # Copy failed, so fall through to minification instead.
# No previous deploy, or a source file has changed, or copying was
# supposed to work but failed. Thus, minify the JS anew.
# (N.B. we include STATIC_HEADER_FILE before the JavaScripts.
# This way it doesn't throw off the source map.)
cmd = '%s --language_in ECMASCRIPT5 --create_source_map %s %s %s' % (
CLOSURE_BINARY, map_file,
settings.STATIC_HEADER_FILE, ' '.join(in_files))
js = subprocess.check_output(cmd, shell=True)
# Write out the JS
with open(out_file, 'wb') as fp:
# Minified source files (most likely libraries) should be loaded
# first to prevent any dependency errors.
for file in min_files:
with open(file, 'rb') as f:
fp.write(f.read())
fp.write(js)