🔈 A
make
library in Python.
Kake is a library that can track and update dependencies, like the
unix make tool and its various descendants. But unlike make
and
other tools, kake is designed as a library, and not a commandline
tool. Kake is "make as hot-loader".
Kake is written in Python, and is thus (in library form, though see below) only usable from Python apps. Your app defines a set of dependency rules using some simple Python code, and then you it can execute code like tihs:
import kake.make
kake.make.build('genfiles/compressed_javascript/foo/main.js')
which will rebuild that file if needed, or be a noop otherwise. At that point, your webserver can either load that file or just serve a reference to it in its html.
Kake can also be used as a dependency server, where you talk HTTP to a kake server asking it to rebuild a file if necessary.
Finally, it can be used as a normal commandline tool, as a replacement
for make
and similar tools.
The original motivation for kake was this situation: Khan Academy
developers would change a .less
file, say, and want to see the
effect on our app by using our development webserver. The workflow
was: change the file, type make
, reload the webapge. We wanted to
automate the rebuilding so you could just change the file and reload
the webpage.
It was easy to do this by having our development webserver call out to
make
every time you loaded a page. But this was slow: you have to
pay the cost to fork a new binary, and then make
had to process its
configuration file listing all the dependencies -- and for Khan
Academy there are a lot -- and then it had to collect last-modified
times so it could decide what to rebuild. It gave us a multi-second
pause on every page.
Instead, what we needed was a dependency library that was part of our development webserver. It would just load the dependency tree once, when the dev webserver started. It could cache some of the last-modified times. And it could respond to rebuild requests instantly, without needing to fork a new process.
Kake was built to fill this need. Our development webserver liberally
uses kake.make.build
to build any artifacts it needs before
consuming or serving them. As a result, our development webserver
environment closely mirrors our production webserver. All this
happens without any need for manual intervention.
There are many many tools out there that wish to manage dependencies.
There are even many of them written in Python. But it's rare to find
a dependency system written to be used as a library. While it's
possible waf
or SCons
could be used as a library, for instance,
it's clear reading the source that the authors did not anticipate that
use-case, and neither project is written in a way that would be easy
(if it's even possible).
To use kake as a library, first you need to specify your dependencies. This process is described in more detail below, but here is a simple example:
"""A simple compile-rule to construct a calculator javascript widget.
The calculator is constructed using yacc rules, and compiled with jison.
We do that compilation here, and also add a footer.
"""
from kake.lib import compile_rule
class CompileCalculator(compile_rule.CompileBase):
def version(self):
"""Update every time build() changes in a way that affects output."""
return 1
def build(self, outfile_name, infile_names, _, context):
assert infile_names[0].endswith('.jison'), infile_names
compiler = infile_names[1]
self.call(['node', compiler, '-m', 'js',
self.abspath(infile_names[0]),
'-o', self.abspath(outfile_name)])
# The rest of the infile-names are just appended to the outfile.
with open(self.abspath(outfile_name), 'a') as f:
for copy_from in infile_names[2:]:
with open(self.abspath(copy_from)) as f2:
f.write(f2.read())
compile_rule.register_compile(
'COMPILED CALCULATOR',
'genfiles/widgets/en/calculator.js',
['javascript/widget-package/calculator.jison',
'/bin/jison',
'javascript/widget-package/calculator.js-tail'],
CompileCalculator(),
)
register_compile()
adds a dependency rule to kake. You specify an
arbitrary label (used for debugging), the output filename, the input
filenames, and the class that's responsible for the actual building.
That class just needs to define a build()
method that does the
actual work. build()
has access to several utility functions, such
as self.call()
to call out to a subprocess.
Note that the output file starts with genfiles/
. This is one of the
rules of kake: all generated files must live in a particular directory
under your project-root. No checked-in files should live there.
Once you have a rule like that you need to import it in your
kake/make.py
file, so kake
knows about it:
import kake_rules.compile_calculator # @UnusedImport
import kake.make_template
CompileFailure = kake.make_template.CompileFailure
BadRequestFailure = kake.make_template.BadRequestFailure
build = kake.make_template.build
build_many = kake.make_template.build_many
Then your webserver app can have code like this:
@flask.route('/render_calculator')
def render_calculator():
# In production, the calculator is pre-built, but on dev it's
# built on demand
if IS_DEVELOPMENT_WEBSERVER:
import kake.make
kake.make.build('genfiles/widgets/en/calculator.js')
return ('<title>Happy calculator</title>'
'<iframe src="/genfiles/widgets/en/calculator.js">')
To use kake as a server, just run kake/server.py
. Then you can send
it HTTP requests like this:
curl http://localhost:5000/genfiles/widgets/en/calculator.js
It will return the contents of calculator.js
as a string, rebuilding
it if necessary. The server respects the If-modified-since
and
If-none-match
headers. You can also send a HEAD
request if you
don't need the full contents, but just want to force the file to be
rebuilt on disk.
You can run:
kake/kake.py genfiles/widgets/en/calculator.js
and it will rebuild calculator.js
if needed.
You can add "fake" rules such as kake.py test
or kake.py build_js
. Like the compile-rules, these rules are written in
Python.
TODO
TODO
TODO
TODO (including version()
and context
)
TODO
TODO
TODO
TODO
TODO