Bug 903149 - Part 3: Support for minifying packaged JavaScript; r=glandium

This commit is contained in:
Gregory Szorc 2013-09-11 19:54:19 -07:00
Родитель 235c851f0e
Коммит 766b00de4d
7 изменённых файлов: 196 добавлений и 12 удалений

Просмотреть файл

@ -31,6 +31,7 @@ SEARCH_PATHS = [
'python/mozversioncontrol',
'python/blessings',
'python/configobj',
'python/jsmin',
'python/psutil',
'python/which',
'build/pymake',

Просмотреть файл

@ -5,9 +5,9 @@
import errno
import os
import platform
import re
import shutil
import stat
import subprocess
import uuid
import mozbuild.makeutil as makeutil
from mozbuild.preprocessor import Preprocessor
@ -28,7 +28,11 @@ from mozpack.errors import (
from mozpack.mozjar import JarReader
import mozpack.path
from collections import OrderedDict
from tempfile import mkstemp
from jsmin import JavascriptMinify
from tempfile import (
mkstemp,
NamedTemporaryFile,
)
class Dest(object):
@ -594,15 +598,76 @@ class MinifiedProperties(BaseFile):
if not l.startswith('#')))
class MinifiedJavaScript(BaseFile):
'''
File class for minifying JavaScript files.
'''
def __init__(self, file, verify_command=None):
assert isinstance(file, BaseFile)
self._file = file
self._verify_command = verify_command
def open(self):
output = BytesIO()
minify = JavascriptMinify(self._file.open(), output)
minify.minify()
output.seek(0)
if not self._verify_command:
return output
input_source = self._file.open().read()
output_source = output.getvalue()
with NamedTemporaryFile() as fh1, NamedTemporaryFile() as fh2:
fh1.write(input_source)
fh2.write(output_source)
fh1.flush()
fh2.flush()
try:
args = list(self._verify_command)
args.extend([fh1.name, fh2.name])
subprocess.check_output(args, stderr=subprocess.STDOUT)
except subprocess.CalledProcessError as e:
errors.warn('JS minification verification failed for %s:' %
(getattr(self._file, 'path', '<unknown>')))
# Prefix each line with "Warning:" so mozharness doesn't
# think these error messages are real errors.
for line in e.output.splitlines():
errors.warn(line)
return self._file.open()
return output
class BaseFinder(object):
def __init__(self, base, minify=False):
def __init__(self, base, minify=False, minify_js=False,
minify_js_verify_command=None):
'''
Initializes the instance with a reference base directory. The
optional minify argument specifies whether file types supporting
minification (currently only "*.properties") should be minified.
Initializes the instance with a reference base directory.
The optional minify argument specifies whether minification of code
should occur. minify_js is an additional option to control minification
of JavaScript. It requires minify to be True.
minify_js_verify_command can be used to optionally verify the results
of JavaScript minification. If defined, it is expected to be an iterable
that will constitute the first arguments to a called process which will
receive the filenames of the original and minified JavaScript files.
The invoked process can then verify the results. If minification is
rejected, the process exits with a non-0 exit code and the original
JavaScript source is used. An example value for this argument is
('/path/to/js', '/path/to/verify/script.js').
'''
if minify_js and not minify:
raise ValueError('minify_js requires minify.')
self.base = base
self._minify = minify
self._minify_js = minify_js
self._minify_js_verify_command = minify_js_verify_command
def find(self, pattern):
'''
@ -644,11 +709,16 @@ class BaseFinder(object):
instance (file), according to the file type (determined by the given
path), if the FileFinder was created with minification enabled.
Otherwise, just return the given BaseFile instance.
Currently, only "*.properties" files are handled.
'''
if self._minify and not isinstance(file, ExecutableFile):
if path.endswith('.properties'):
return MinifiedProperties(file)
if not self._minify or isinstance(file, ExecutableFile):
return file
if path.endswith('.properties'):
return MinifiedProperties(file)
if self._minify_js and path.endswith(('.js', '.jsm')):
return MinifiedJavaScript(file, self._minify_js_verify_command)
return file

Просмотреть файл

@ -0,0 +1,11 @@
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
import sys
if len(sys.argv) != 4:
raise Exception('Usage: minify_js_verify <exitcode> <orig> <minified>')
sys.exit(int(sys.argv[1]))

Просмотреть файл

@ -15,6 +15,7 @@ from mozpack.files import (
GeneratedFile,
JarFinder,
ManifestFile,
MinifiedJavaScript,
MinifiedProperties,
PreprocessedFile,
XPTFile,
@ -35,6 +36,7 @@ import mozunit
import os
import random
import string
import sys
import mozpack.path
from tempfile import mkdtemp
from io import BytesIO
@ -753,6 +755,49 @@ class TestMinifiedProperties(TestWithTmpDir):
['foo = bar\n', '\n'])
class TestMinifiedJavaScript(TestWithTmpDir):
orig_lines = [
'// Comment line',
'let foo = "bar";',
'var bar = true;',
'',
'// Another comment',
]
def test_minified_javascript(self):
orig_f = GeneratedFile('\n'.join(self.orig_lines))
min_f = MinifiedJavaScript(orig_f)
mini_lines = min_f.open().readlines()
self.assertTrue(mini_lines)
self.assertTrue(len(mini_lines) < len(self.orig_lines))
def _verify_command(self, code):
our_dir = os.path.abspath(os.path.dirname(__file__))
return [
sys.executable,
os.path.join(our_dir, 'support', 'minify_js_verify.py'),
code,
]
def test_minified_verify_success(self):
orig_f = GeneratedFile('\n'.join(self.orig_lines))
min_f = MinifiedJavaScript(orig_f,
verify_command=self._verify_command('0'))
mini_lines = min_f.open().readlines()
self.assertTrue(mini_lines)
self.assertTrue(len(mini_lines) < len(self.orig_lines))
def test_minified_verify_failure(self):
orig_f = GeneratedFile('\n'.join(self.orig_lines))
min_f = MinifiedJavaScript(orig_f,
verify_command=self._verify_command('1'))
mini_lines = min_f.open().readlines()
self.assertEqual(mini_lines, orig_f.open().readlines())
class MatchTestTemplate(object):
def prepare_match_test(self, with_dotfiles=False):
self.add('bar')

Просмотреть файл

@ -0,0 +1,28 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/**
* This script compares the AST of two JavaScript files passed as arguments.
* The script exits with a 0 status code if both files parse properly and the
* ASTs of both files are identical modulo location differences. The script
* exits with status code 1 if any of these conditions don't hold.
*
* This script is used as part of packaging to verify minified JavaScript files
* are identical to their original files.
*/
"use strict";
function ast(filename) {
return JSON.stringify(Reflect.parse(snarf(filename), {loc: 0}));
}
if (scriptArgs.length !== 2) {
throw "usage: js js-compare-ast.js FILE1.js FILE2.js";
}
let ast0 = ast(scriptArgs[0]);
let ast1 = ast(scriptArgs[1]);
quit(ast0 == ast1 ? 0 : 1);

Просмотреть файл

@ -705,6 +705,16 @@ endif
export NO_PKG_FILES USE_ELF_HACK ELF_HACK_FLAGS
# A js binary is needed to perform verification of JavaScript minification.
# We can only use the built binary when not cross-compiling. Environments
# (such as release automation) can provide their own js binary to enable
# verification when cross-compiling.
ifndef JS_BINARY
ifndef CROSS_COMPILE
JS_BINARY = $(wildcard $(DIST)/bin/js)
endif
endif
# Override the value of OMNIJAR_NAME from config.status with the value
# set earlier in this file.
@ -716,6 +726,9 @@ stage-package: $(MOZ_PKG_MANIFEST)
$(addprefix --removals ,$(MOZ_PKG_REMOVALS)) \
$(if $(filter-out 0,$(MOZ_PKG_FATAL_WARNINGS)),,--ignore-errors) \
$(if $(MOZ_PACKAGER_MINIFY),--minify) \
$(if $(MOZ_PACKAGER_MINIFY_JS),--minify-js \
$(addprefix --js-binary ,$(JS_BINARY)) \
) \
$(if $(JARLOG_DIR),$(addprefix --jarlog ,$(wildcard $(JARLOG_FILE_AB_CD)))) \
$(if $(OPTIMIZEJARS),--optimizejars) \
$(addprefix --unify ,$(UNIFY_DIST)) \

Просмотреть файл

@ -248,6 +248,12 @@ def main():
help='Transform errors into warnings.')
parser.add_argument('--minify', action='store_true', default=False,
help='Make some files more compact while packaging')
parser.add_argument('--minify-js', action='store_true',
help='Minify JavaScript files while packaging.')
parser.add_argument('--js-binary',
help='Path to js binary. This is used to verify '
'minified JavaScript. If this is not defined, '
'minification verification will not be performed.')
parser.add_argument('--jarlog', default='', help='File containing jar ' +
'access logs')
parser.add_argument('--optimizejars', action='store_true', default=False,
@ -311,12 +317,22 @@ def main():
launcher.tooldir = buildconfig.substs['LIBXUL_DIST']
with errors.accumulate():
finder_args = dict(
minify=args.minify,
minify_js=args.minify_js,
)
if args.js_binary:
finder_args['minify_js_verify_command'] = [
args.js_binary,
os.path.join(os.path.abspath(os.path.dirname(__file__)),
'js-compare-ast.js')
]
if args.unify:
finder = UnifiedBuildFinder(FileFinder(args.source),
FileFinder(args.unify),
minify=args.minify)
**finder_args)
else:
finder = FileFinder(args.source, minify=args.minify)
finder = FileFinder(args.source, **finder_args)
if 'NO_PKG_FILES' in os.environ:
sinkformatter = NoPkgFilesRemover(formatter,
args.manifest is not None)