blob: 1a4aa558a5736b6c43b3dab9302782866292791a [file] [log] [blame]
#!/usr/bin/env python
# Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file
# for details. All rights reserved. Use of this source code is governed by a
# BSD-style license that can be found in the LICENSE file.
"""
Legpad is used to compile .dart files to javascript, using the dart2js compiler.
This is accomplished by creating an html file (usually called
<something>.legpad.html) that executes the dart2js compiler when the page
is loaded by a web browser (or DumpRenderTree).
The <something>.legpad.html file contains:
1. all the dart files that compose a user's dart program
2. all the dart files of dart:core and other standard dart libraries
(or any other symbol that can follow "dart:" in an import statement
3. legpad.dart (compiled to javascript)
The contents of each dart file is placed in a separate <script> tag.
When the html page is loaded by a browser, the leg compiler is invoked
and the dart program is compiled to javascript. The generated javascript is
placed in a <pre> element with id "output".
When the html page is passed to DumpRenderTree, the dumped output will
have the generated javascript.
See 'example.sh' for an example of how to run legpad.
"""
import logging
import optparse
import os.path
import platform
import re
import subprocess
import sys
class FileNotFoundException(Exception):
def __init__(self, file_name):
self._name = file_name
def __str__(self):
return self._name
class CommandFailedException(Exception):
def __init__(self, message):
self._message = message
def GetMessage(self):
return self._message
# Template for the legpad.html page we're going to generate.
HTML = """<!DOCTYPE html>
<html>
<head>
<style type="text/css">
textarea {
width: 100%;
height: 200px;
}
.label {
margin-top: 5px;
}
pre {
border: 2px solid black;
}
</style>
{{script_tags}}
<script type="text/javascript">
if (window.layoutTestController) {
layoutTestController.dumpAsText();
}
</script>
</head>
<body>
<h1>Legpad</h1>
<div class="label">Input:</div>
<textarea id="input"></textarea>
<div class="label">Compiler Messages:</div>
<pre id="warnings"></pre>
<div class="label">Timing:</div>
<pre id="timing"></pre>
<div class="label">Output:</div>
<pre id="output"></pre>
<script type="text/javascript">
{{LEGPAD_JS}}
</script>
</body>
</html>
"""
# This finds everything after the word "Output:" in the html page.
# (Note, because the javascript we're fishing out spans multiple lines
# we need to use the DOTALL switch here.)
OUTPUT_JAVASCRIPT_REGEX = re.compile(".*\nOutput:\n(.*)\n#EOF", re.DOTALL)
# If the legpad.dart encounters a compilation error, the generated
# javascript will contains the words "dart2js compilation error".
COMPILATION_ERROR_REGEX = re.compile(".*dart2js compilation error.*", re.DOTALL)
# We use "application/inert" here to make the browser ignore the
# these script tags. (legpad.dart will fish out the contents as needed.)
#
SCRIPT_TAG = """<script type="application/inert" id="{{id}}">
{{contents}}
</script>
"""
# Regex that finds #import, #source and #native directives in .dart files.
# match.group(1) = "import", "source" or "native"
# match.group(2) = url of file being imported
DIRECTIVE_RE = re.compile(r"^#(import|source|native)\([\"']([^\"']*)[\"']")
# id of script tag that holds name of the top dart file to be compiled,
# (This file name passed will be passed to the leg compiler by legpad.dart.)
MAIN_ID = "main_id"
# TODO(mattsh): read this from some config file once ahe/zundel create it
DART_LIBRARIES = {
"core": "lib/compiler/implementation/lib/core.dart",
"_js_helper": "lib/compiler/implementation/lib/js_helper.dart",
"_interceptors": "lib/compiler/implementation/lib/interceptors.dart",
"dom": "lib/dom/frog/dom_frog.dart",
"html": "lib/html/frog/html_frog.dart",
"io": "lib/compiler/implementation/lib/io.dart",
"isolate": "lib/isolate/isolate_leg.dart",
"json": "lib/json/json.dart",
"uri": "lib/uri/uri.dart",
"utf": "lib/utf/utf.dart",
}
class Pad(object):
"""
Accumulates all source files that are needed to compile a dart program,
and places them in <script> tags on an html page.
"""
def __init__(self, argv):
parser = optparse.OptionParser(usage=
"%prog [options] file_to_compile.dart"
)
parser.add_option("-o", "--out",
help="name of javascript output file")
parser.add_option("-v", "--verbose", action="store_true",
help="more verbose logging")
(options, args) = parser.parse_args(argv)
log_level = logging.INFO
if options.verbose:
log_level = logging.DEBUG
logging.basicConfig(level=log_level)
if len(args) < 2:
parser.print_help()
sys.exit(1)
self.main_file = os.path.abspath(args[1])
# directory of this script
self.legpad_dir = os.path.abspath(os.path.dirname(argv[0]))
# root of dart source repo
self.dart_dir = os.path.dirname(os.path.dirname(os.path.dirname(
self.legpad_dir)))
logging.debug("dart_dir: '%s'" % self.dart_dir)
if options.out:
# user has specified an output file name
self.js_file = os.path.abspath(options.out)
else:
# User didn't specify an output file, so use the input
# file name as the base of the output file name.
self.js_file = self.main_file + ".legpad.js"
logging.debug("js_file: '%s" % self.js_file)
# this is the html file that we pass to DumpRenderTree
self.html_file = self.main_file + ".legpad.html"
logging.debug("html_file: '%s'" % self.html_file)
# map from file name to File object (contains entries for all corelib
# and all other dart files needed to compile main_file)
self.name_to_file = {}
# map from script tag id to File object
self.id_to_file = {}
self.load_libraries()
self.load_file(self.main_file)
html = self.generate_html()
write_file(self.html_file, html)
js = self.generate_js()
write_file(self.js_file, js)
line_count = len(js.splitlines())
logging.debug("generated '%s' (%d lines)", self.js_file, line_count)
match = COMPILATION_ERROR_REGEX.match(js)
if match:
sys.exit(1)
def generate_html(self):
tags = []
for f in self.id_to_file.values():
tags.append(self._create_tag(f.id, f.contents))
tags.append(self._create_tag(MAIN_ID, self.shorten(self.main_file)))
html = HTML.replace("{{script_tags}}", "".join(tags))
legpad_js = os.path.join(self.legpad_dir, "legpad.dart.js")
check_exists(legpad_js)
html = html.replace("{{LEGPAD_JS}}", read_file(legpad_js))
return html
def generate_js(self):
drt = os.path.join(self.dart_dir, "client/tests/drt/DumpRenderTree")
if platform.system() == 'Darwin':
drt += ".app"
elif platform.system() == 'Windows':
raise Exception("legpad does not run on Windows")
check_exists(drt)
args = []
args.append(drt)
args.append(self.html_file)
stdout = run_command(args)
match = OUTPUT_JAVASCRIPT_REGEX.match(stdout)
if not match:
raise Exception("can't find regex in DumpRenderTree output")
return match.group(1)
@staticmethod
def _create_tag(id, contents):
s = SCRIPT_TAG
s = s.replace("{{id}}", id)
# TODO(mattsh) - need to html escape here
s = s.replace("{{contents}}", contents)
return s
def dart_library(self, name):
path = DART_LIBRARIES[name]
if not path:
raise Exception("unrecognized 'dart:%s'", name)
return os.path.join(self.dart_dir, path)
def load_libraries(self):
for name in DART_LIBRARIES:
self.load_file(self.dart_library(name))
def load_file(self, name):
name = os.path.abspath(name)
if name in self.name_to_file:
return
f = File(self, name)
self.name_to_file[f.name] = f
if f.id in self.id_to_file:
raise Exception("ambiguous id '%s'" % f.id)
self.id_to_file[f.id] = f
f.directives()
def shorten(self, name):
"""
Change that full path of the dart svn repo to simply "dartdir"
"""
return name.replace(self.dart_dir, "dartdir")
def make_id(self, name):
"""
Generates an id (based on the file name) for the <script> tag that will
hold the contents of this file.
"""
return self.shorten(name).replace("/", "_").replace(".", "_")
class File(object):
def __init__(self, pad, name):
self.pad = pad
self.name = name
self.id = pad.make_id(name)
self.contents = read_file(name)
def directives(self):
"""Load files referenced by #source, #import and #native directives."""
lines = self.contents.split("\n")
self.line_number = 0
for line in lines:
self.line_number += 1
self._directive(line)
def _directive(self, line):
match = DIRECTIVE_RE.match(line)
if not match:
return
url = match.group(2)
if url.startswith("dart:"):
path = self.pad.dart_library(url[len("dart:"):])
else:
path = os.path.join(os.path.dirname(self.name), url)
self.pad.load_file(path)
def read_file(file_name):
check_exists(file_name)
with open(file_name, "r") as input:
contents = input.read()
logging.debug("read_file '%s' (%d bytes)" % (file_name, len(contents)))
return contents
def write_file(file_name, contents):
with open(file_name, "w") as output:
output.write(contents)
check_exists(file_name)
logging.debug("write_file '%s' (%d bytes)" % (file_name, len(contents)))
def check_exists(file_name):
if not os.path.exists(file_name):
raise FileNotFoundException(file_name)
def format_command(args):
return ' '.join(args)
def run_command(args):
"""
Args:
command: comamnd with arguments to exec
Returns:
all output that this command sent to stdout
"""
command = format_command(args)
logging.info("RUNNING: '%s'" % command)
child = subprocess.Popen(args,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
close_fds=True)
(stdout, stderr) = child.communicate()
exit_code = child.wait()
if exit_code:
for line in stderr.splitlines():
logging.info(line)
msg = "FAILURE (exit_code=%d): '%s'" % (exit_code, command)
logging.error(msg)
raise CommandFailedException(msg)
logging.debug("SUCCEEDED (%d bytes)" % len(stdout))
return stdout
def main(argv):
Pad(argv)
if __name__ == "__main__":
sys.exit(main(sys.argv))