Rewrite makefile to better track dependencies.

This commit is contained in:
Neil Williams
2011-11-22 12:14:48 -08:00
parent 7aa603d788
commit ffb62ff076
2 changed files with 212 additions and 110 deletions

View File

@@ -20,135 +20,161 @@
# CondeNet, Inc. All Rights Reserved.
################################################################################
# Javascript files to be compressified
js_libs = $(addprefix lib/,json2.js jquery.cookie.js jquery.url.js ui.core.js ui.datepicker.js jquery.flot.js jquery.lazyload.js)
js_sources = $(js_libs) jquery.reddit.js reddit.js login.js ui.js base.js sponsored.js compact.js blogbutton.js flair.js analytics.js utils.js
js_targets = button.js jquery.flot.js sponsored.js
localized_js_targets = reddit.js mobile.js
localized_js_outputs = $(localized_js_targets:.js=.*.js)
# CSS targets
main_css = reddit.css
main_sprite = $(static_dir)/sprite-main.png
compact_css = compact.css
compact_sprite = $(static_dir)/sprite-compact.png
css_targets = $(main_css) $(compact_css) reddit-ie6-hax.css reddit-ie7-hax.css mobile.css
package = r2
static_dir = $(package)/public/static
SHELL=/bin/sh
PYTHON=python
SED=sed
CAT=cat
PYTHON=python
JS_COMPRESS = paster run standalone $(package)/lib/js.py -c "build_reddit_js()"
CSS_COMPRESS = $(SED) -e 's/ \+/ /' -e 's/\/\*.*\*\///g' -e 's/: /:/' | grep -v "^ *$$"
UPDATE_NAMES = $(PYTHON) $(package)/lib/static.py
BUILD_DIR=build
# If admin codebase is install, get its path so that we can build ini
# files against the primary production.ini
PRIVATEREPOS = $(shell $(PYTHON) -c 'exec "try: import r2admin; print r2admin.__path__[0]\nexcept:pass"')
# If reddit-i18n repo is found, use that for r2.pot generation
I18NPATH = $(shell $(PYTHON) -c 'exec "try: import reddit_i18n; print reddit_i18n.__path__[0]\nexcept ImportError: print \"r2/i18n\""')
#------
JSTARGETS = $(foreach js, $(js_targets) $(localized_js_targets), $(static_dir)/$(js))
JSOUTPUTS = $(foreach js, $(js_targets) $(localized_js_outputs), $(static_dir)/$(js))
JSSOURCES = $(foreach js, $(js_sources), $(static_dir)/js/$(js))
CSSTARGETS = $(foreach css, $(css_targets), $(static_dir)/$(css))
SPRITES = $(main_sprite) $(compact_sprite)
RTLCSS = $(CSSTARGETS:.css=-rtl.css)
NAMES = $(static_dir)/names.json
.PHONY: clean
all: pyx static ini
clean: clean_pyx clean_i18n clean_static
#################### Cython
PYX_FILES := $(shell find . -name \*.pyx)
PYXC_FILES := $(PYX_FILES:.pyx=.c)
PYXSO_FILES := $(PYX_FILES:.pyx=.so)
PYX_C_FILES := $(PYX_FILES:.pyx=.c)
PYX_SO_FILES := $(PYX_FILES:.pyx=.so)
PYX_BUILDSTAMP := $(BUILD_DIR)/pyx-buildstamp
NAMED = $(JSOUTPUTS) $(CSSTARGETS) $(RTLCSS) $(SPRITES)
NAMELINKS = $(patsubst %.css,%.*.css,$(patsubst %.js,%.*.js,$(NAMED)))
GZIPPED = $(foreach file, $(filter %.js %.css, $(wildcard $(NAMELINKS))) $(static_dir)/js/lib/jquery.js, $(file).gz)
.PHONY: clean_pyx
INIUPDATE = $(wildcard *.update)
INIS = $(INIUPDATE:.update=.ini)
pyx: $(PYX_BUILDSTAMP)
%.ini: %.update
ifneq (,$(PRIVATEREPOS))
ln -sf `pwd`/$< $(PRIVATEREPOS)/..
make -C $(PRIVATEREPOS)/.. $@
ln -sf $(PRIVATEREPOS)/../$@ .
else
$(PYX_C_FILES): %.c: %.pyx
cython $<
# this won't rebuild pyx if you're a jerk and delete a .so file without deleting the buildstamp
# make clean && make pyx will fix this
$(PYX_BUILDSTAMP): $(PYX_C_FILES)
$(PYTHON) setup.py build_ext --inplace
touch $@
clean_pyx:
rm -f $(PYX_BUILDSTAMP) $(PYX_C_FILES) $(PYX_SO_FILES)
#################### i18n
I18NPATH := $(shell $(PYTHON) -c 'from r2.lib.i18n import I18N_PATH; print I18N_PATH')
STRINGS_FILE := r2/lib/strings.py
RAND_STRINGS_FILE := r2/lib/rand_strings.py
POTFILE := $(I18NPATH)/r2.pot
.PHONY: i18n clean_i18n
i18n: $(RAND_STRINGS_FILE)
$(PYTHON) setup.py extract_messages -o $(POTFILE)
$(RAND_STRINGS_FILE): $(STRINGS_FILE)
paster run standalone $(STRINGS_FILE) -c "print_rand_strings()" > $(RAND_STRINGS_FILE)
clean_i18n:
rm -f $(RAND_STRINGS_FILE)
#################### ini files
INIFILES := $(wildcard *.update)
ini: $(INIFILES)
$(INIFILES): %.ini: %.update
./updateini.py example.ini $< > $@ || rm $@
endif
#################### Static Files
STATIC_DIR := r2/public/static
all: pyx static names $(INIS)
.PHONY: clean_static
.PHONY: pyx js css rtl static names i18n clean clean_static clean_pyx all gzip clean_gzip
static: pyx css js names
$(NAMES): $(JSTARGETS) $(CSSTARGETS) $(RTLCSS) $(SPRITES)
$(UPDATE_NAMES) $(NAMES) $(NAMED)
clean_static: clean_css clean_js clean_names
$(JSTARGETS): $(JSSOURCES)
$(JS_COMPRESS)
#### Stylesheets
CSS_COMPRESS := $(SED) -e 's/ \+/ /' -e 's/\/\*.*\*\///g' -e 's/: /:/' | grep -v "^ *$$"
CSS_SOURCE_DIR := $(STATIC_DIR)/css
$(main_sprite) $(static_dir)/$(main_css): $(static_dir)/css/$(main_css)
rm -f $@ # delete symlink so we don't just overwrite the old mangled file
$(PYTHON) r2/lib/nymph.py $< $(static_dir)/sprite-main.png | $(CSS_COMPRESS) > $@
SPRITED_STYLESHEETS := reddit.css compact.css
PROCESSED_SPRITED_STYLESHEETS := $(addprefix $(STATIC_DIR)/, $(SPRITED_STYLESHEETS))
SPRITES := $(addprefix $(STATIC_DIR)/, $(patsubst %.css,sprite-%.png, $(SPRITED_STYLESHEETS)))
$(compact_sprite) $(static_dir)/$(compact_css) : $(static_dir)/css/$(compact_css)
rm -f $@ # delete symlink so we don't just overwrite the old mangled file
$(PYTHON) r2/lib/nymph.py $< $(static_dir)/sprite-compact.png | $(CSS_COMPRESS) > $@
OTHER_STYLESHEETS := reddit-ie6-hax.css reddit-ie7-hax.css mobile.css
MINIFIED_OTHER_STYLESHEETS := $(addprefix $(STATIC_DIR)/, $(OTHER_STYLESHEETS))
$(static_dir)/%.css : $(static_dir)/css/%.css
PROCESSED_STYLESHEETS := $(PROCESSED_SPRITED_STYLESHEETS) $(MINIFIED_OTHER_STYLESHEETS)
RTL_STYLESHEETS := $(PROCESSED_STYLESHEETS:.css=-rtl.css)
CSS_OUTPUTS = $(PROCESSED_STYLESHEETS) $(RTL_STYLESHEETS) $(SPRITES)
.PHONY: clean_css
css: $(CSS_OUTPUTS)
$(MINIFIED_OTHER_STYLESHEETS): $(STATIC_DIR)/%.css: $(CSS_SOURCE_DIR)/%.css
$(CAT) $< | $(CSS_COMPRESS) > $@
$(RTLCSS): %-rtl.css : %.css
$(STATIC_DIR)/sprite-%.png $(STATIC_DIR)/%.css: $(CSS_SOURCE_DIR)/%.css
# when static file names are mangled, the original becomes a symlink to the mangled name
# remove the original file here in case it's a symlink so we don't just rewrite the old file
rm -f $(STATIC_DIR)/$*.css
$(PYTHON) r2/lib/nymph.py $< $(STATIC_DIR)/sprite-$*.png | $(CSS_COMPRESS) > $(STATIC_DIR)/$*.css
$(RTL_STYLESHEETS): %-rtl.css : %.css
$(SED) -e "s/left/>####</g" \
-e "s/right/left/g" \
-e "s/>####</right/g" \
-e "s/\(margin\|padding\):\s*\([^; ]\+\)\s\+\([^; ]\+\)\s\+\([^; ]\+\)\s\+\([^; ]\+\)/\1:\2 \5 \4 \3/g" $< > $@
%.c: %.pyx
cython $<
clean_css:
rm -f $(CSS_OUTPUTS)
$(PYXSO_FILES): $(PYXC_FILES)
python setup.py build_ext --inplace
#### JS
JS_MODULES := $(shell $(PYTHON) r2/lib/js.py enumerate_modules)
JS_MODULE_BUILDSTAMPS := $(foreach module,$(JS_MODULES),$(BUILD_DIR)/$(module)-js-buildstamp)
JS_OUTPUTS := $(shell $(PYTHON) r2/lib/js.py enumerate_outputs)
pyx: $(PYXSO_FILES)
.PHONY: clean_js
static: js css rtl
js: $(JS_MODULE_BUILDSTAMPS)
js: pyx $(JSTARGETS)
$(JS_OUTPUTS): $(JS_MODULE_BUILDSTAMPS)
css: $(CSSTARGETS) $(SPRITES)
define JS_MODULE_TEMPLATE
$(BUILD_DIR)/$(1)-js-buildstamp: $$(shell $(PYTHON) r2/lib/js.py dependencies $(1))
paster run standalone r2/lib/js.py -c "build_module('$(1)')"
touch $$@
endef
rtl: $(RTLCSS)
# apply the module template to each of the modules
# so they source their deps from js.py and build accordingly
$(foreach module,$(JS_MODULES),$(eval $(call JS_MODULE_TEMPLATE,$(module))))
names: $(NAMES)
clean_js:
rm -f $(JS_MODULE_BUILDSTAMPS) $(STATIC_DIR)/*.js
# Not part of 'all' target as it doesn't need to be run for the site to work,
# only when r2.pot needs updating
i18n:
paster run run.ini r2/lib/strings.py -c "print_rand_strings()" > r2/lib/rand_strings.py
python setup.py extract_messages -o $(I18NPATH)/r2.pot
#### name mangling
MANGLEABLE_FILES := $(CSS_OUTPUTS) $(JS_OUTPUTS)
MANGLE_BUILDSTAMP := $(BUILD_DIR)/mangle-buildstamp
NAMES_FILE := $(STATIC_DIR)/names.json
MANGLED_FILES := $(wildcard $(foreach file,$(MANGLEABLE_FILES),$(basename $(file)).*$(suffix $(file))))
.PHONY: clean_names
names: $(MANGLE_BUILDSTAMP)
$(MANGLE_BUILDSTAMP): $(MANGLEABLE_FILES)
$(PYTHON) r2/lib/static.py $(NAMES_FILE) $(MANGLEABLE_FILES)
touch $@
clean_names:
rm -f $(MANGLE_BUILDSTAMP) $(NAMES_FILES) $(MANGLED_FILES)
#### gzip!
GZIPPABLE := $(filter %.css %.js,$(MANGLED_FILES) $(STATIC_DIR)/js/lib/jquery.js)
GZIPPED := $(addsuffix .gz,$(GZIPPABLE))
.PHONY: clean_gzip
gzip: $(GZIPPED)
%.gz: %
$(GZIPPED): %.gz: %
gzip -c $< > $@
clean: clean_static clean_pyx clean_gzip
rm -f $(INIS) r2/lib/rand_strings.py
clean_pyx:
rm -f $(PYXSO_FILES) $(PYXC_FILES)
clean_static: clean_names
rm -f $(JSTARGETS) $(JSOUTPUTS) $(CSSTARGETS) $(RTLCSS) $(SPRITES)
clean_names:
rm -f $(NAMES) $(NAMELINKS) $(static_dir)/*.md5
clean_gzip:
rm -f $(static_dir)/*.css.gz $(static_dir)/*.js.gz $(static_dir)/js/lib/*.gz
rm -f $(GZIPPED)

View File

@@ -4,7 +4,15 @@ import os.path
from subprocess import Popen, PIPE
import re
import json
from pylons import g, c
from r2.lib.i18n import get_available_languages
if __name__ != "__main__":
from pylons import g, c
STATIC_ROOT = g.paths["static_files"]
else:
REDDIT_ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
STATIC_ROOT = os.path.join(REDDIT_ROOT, "public")
script_tag = '<script type="text/javascript" src="{src}"></script>\n'
inline_script_tag = '<script type="text/javascript">{content}</script>\n'
@@ -35,11 +43,19 @@ class Source(object):
"""An abstract collection of JavaScript code."""
def get_source(self):
"""Return the full JavaScript source code."""
return NotImplementedError
raise NotImplementedError
def use(self):
"""Return HTML to insert the JavaScript source inside a template."""
return NotImplementedError
raise NotImplementedError
@property
def dependencies(self):
raise NotImplementedError
@property
def outputs(self):
raise NotImplementedError
class FileSource(Source):
"""A JavaScript source file on disk."""
@@ -52,7 +68,7 @@ class FileSource(Source):
@property
def path(self):
"""The path to the source file on the filesystem."""
return os.path.join(g.paths["static_files"], "static/js", self.name)
return os.path.join(STATIC_ROOT, "static", "js", self.name)
def use(self):
from r2.lib.template_helpers import static
@@ -61,6 +77,10 @@ class FileSource(Source):
path.insert(1, "js")
return script_tag.format(src=static(os.path.join(*path)))
@property
def dependencies(self):
return [self.path]
class Module(Source):
"""A module of JS code consisting of a collection of sources."""
def __init__(self, name, *sources):
@@ -78,7 +98,7 @@ class Module(Source):
@property
def path(self):
"""The destination path of the module file on the filesystem."""
return os.path.join(g.paths["static_files"], "static", self.name)
return os.path.join(STATIC_ROOT, "static", self.name)
def build(self, closure):
print >> sys.stderr, "Compiling {0}...".format(self.name),
@@ -93,6 +113,17 @@ class Module(Source):
else:
return script_tag.format(src=static(self.name))
@property
def dependencies(self):
deps = []
for source in self.sources:
deps.extend(source.dependencies)
return deps
@property
def outputs(self):
return [self.path]
class StringsSource(Source):
"""A virtual source consisting of localized strings from r2.lib.strings."""
def __init__(self, lang=None, keys=None, prepend="r.strings = "):
@@ -132,6 +163,12 @@ class LocalizedModule(Module):
A StringsSource is created and included which contains localized versions
of the strings referenced in the module.
"""
@staticmethod
def languagize_path(path, lang):
path_name, path_ext = os.path.splitext(path)
return path_name + "." + lang + path_ext
def build(self, closure):
Module.build(self, closure)
@@ -139,11 +176,16 @@ class LocalizedModule(Module):
string_keys = re.findall("r\.strings\.([\w$_]+)", reddit_source)
print >> sys.stderr, "Creating language-specific files:"
path_name, path_ext = os.path.splitext(self.path)
for lang in g.languages:
for lang in get_available_languages():
strings = StringsSource(lang, string_keys)
source = strings.get_source()
lang_path = path_name + "." + lang + path_ext
lang_path = LocalizedModule.languagize_path(self.path, lang)
# make sure we're not rewriting a different mangled file
# via symlink
if os.path.islink(lang_path):
os.unlink(lang_path)
with open(lang_path, "w") as out:
print >> sys.stderr, " " + lang_path
out.write(reddit_source+source)
@@ -155,10 +197,15 @@ class LocalizedModule(Module):
if g.uncompressedJS:
return embed + StringsSource().use()
else:
name, ext = os.path.splitext(self.name)
url = name + "." + get_lang()[0] + ext
url = LocalizedModule.languagize_path(self.name, get_lang()[0])
return script_tag.format(src=static(url))
@property
def outputs(self):
languages = get_available_languages()
for lang in languages:
yield LocalizedModule.languagize_path(self.path, lang)
class JQuery(Module):
def __init__(self, cdn_src=None):
Module.__init__(self, os.path.join("js", "lib", "jquery.js"))
@@ -175,6 +222,14 @@ class JQuery(Module):
ext = ".js" if g.uncompressedJS else ".min.js"
return script_tag.format(src=self.cdn_src+ext)
@property
def dependencies(self):
return []
@property
def outputs(self):
return []
module = {}
module["jquery"] = JQuery()
@@ -218,10 +273,31 @@ module["flot"] = Module("jquery.flot.js",
def use(*names):
return "\n".join(module[name].use() for name in names)
def build_reddit_js():
closure = ClosureCompiler("r2/lib/contrib/closure_compiler/compiler.jar")
for name in module:
module[name].build(closure)
commands = {}
def build_command(fn):
commands[fn.__name__] = fn
return fn
if __name__=="__main__":
build_reddit_js()
@build_command
def enumerate_modules():
for m in module:
print m
@build_command
def dependencies(name):
for dep in module[name].dependencies:
print dep
@build_command
def enumerate_outputs():
for m in module.itervalues():
for output in m.outputs:
print output
@build_command
def build_module(name):
closure = ClosureCompiler("r2/lib/contrib/closure_compiler/compiler.jar")
module[name].build(closure)
if __name__ == "__main__":
commands[sys.argv[1]](*sys.argv[2:])