← Back to team overview

launchpad-reviewers team mailing list archive

[Merge] lp:~cjwatson/launchpad/jinja2-templates into lp:launchpad

 

Colin Watson has proposed merging lp:~cjwatson/launchpad/jinja2-templates into lp:launchpad.

Commit message:
Translate buildout templates to Jinja2, using jweede.recipe.template.

Requested reviews:
  Launchpad code reviewers (launchpad-reviewers)

For more details, see:
https://code.launchpad.net/~cjwatson/launchpad/jinja2-templates/+merge/314847

This is part of the process of migrating from buildout to virtualenv/pip, since we won't have buildout recipes available in the latter environment.  Switching to a more widely-available template engine makes the migration easier.  I used jweede.recipe.template as a temporary glue layer, which seems to be the most recent of the various Jinja2 buildout recipes I could find on PyPI.

There are a few unfortunate things about the current recipe implementation, which will go away once we're doing our own Jinja2 templating in a virtualenv/pip environment:

 * the buildout.cfg syntax is awfully verbose - target-executable is particularly unfortunate;
 * every template file has to end with two newlines, since jweede.recipe.template can't be told to pass keep_trailing_newline=True to Jinja2;
 * some of our custom filters should really be plain global functions instead, but jweede.recipe.template only supports declaring custom filters.
-- 
Your team Launchpad code reviewers is requested to review the proposed merge of lp:~cjwatson/launchpad/jinja2-templates into lp:launchpad.
=== modified file 'Makefile'
--- Makefile	2016-09-21 02:51:58 +0000
+++ Makefile	2017-01-16 13:22:42 +0000
@@ -57,7 +57,7 @@
     bin/tags bin/test bin/tracereport bin/twistd bin/update-download-cache \
     bin/watch_jsbuild
 
-BUILDOUT_TEMPLATES = buildout-templates/_pythonpath.py.in
+TEMPLATES = templates/_pythonpath.py.j2
 
 # DO NOT ALTER : this should just build by default
 default: inplace
@@ -229,8 +229,7 @@
 # If we listed every target on the left-hand side, a parallel make would try
 # multiple copies of this rule to build them all.  Instead, we nominally build
 # just $(PY), and everything else is implicitly updated by that.
-$(PY): bin/buildout versions.cfg $(BUILDOUT_CFG) setup.py \
-		$(BUILDOUT_TEMPLATES)
+$(PY): bin/buildout versions.cfg $(BUILDOUT_CFG) setup.py $(TEMPLATES)
 	$(SHHH) PYTHONPATH= ./bin/buildout \
                 configuration:instance_name=${LPCONFIG} -c $(BUILDOUT_CFG)
 	touch $@

=== modified file 'README'
--- README	2011-02-18 18:43:16 +0000
+++ README	2017-01-16 13:22:42 +0000
@@ -47,7 +47,7 @@
     Where you will find scripts intended for developers and admins.  There's
     no rhyme or reason to what goes in bin/ and what goes in utilities/, so
     take a look in both. bin/ will be empty in a fresh checkout, the actual
-    content lives in 'buildout-templates'.
+    content lives in 'templates'.
 
   configs/
     Configuration files for various kinds of Launchpad instances.
@@ -90,11 +90,6 @@
 the files in the top-level directory are for.  However, here's a guide to some
 of the ones that come up from time to time.
 
-  buildout-templates/
-    Templates that are generated into actual files, normally bin/ scripts,
-    when buildout is run. If you want to change the behaviour of bin/test,
-    look here.
-
   bzrplugins/
     Bazaar plugins used in running Launchpad.
 
@@ -102,5 +97,10 @@
     A directory into which we symlink branches of some of Launchpad's
     dependencies.  Don't ask.
 
+  templates/
+    Templates that are generated into actual files, normally bin/ scripts,
+    when buildout is run. If you want to change the behaviour of bin/test,
+    look here.
+
   zcml/
     Various configuration files for the Zope services. Angels fear to tread.

=== modified file 'buildout.cfg'
--- buildout.cfg	2016-11-03 15:19:01 +0000
+++ buildout.cfg	2017-01-16 13:22:42 +0000
@@ -1,10 +1,10 @@
-# Copyright 2009-2011 Canonical Ltd.  This software is licensed under the
+# Copyright 2009-2017 Canonical Ltd.  This software is licensed under the
 # GNU Affero General Public License version 3 (see the file LICENSE).
 
 [buildout]
 parts =
     scripts
-    filetemplates
+    templates
     tags
     iharness
     i18n
@@ -35,9 +35,54 @@
 [configuration]
 instance_name = development
 
-[filetemplates]
-recipe = z3c.recipe.filetemplate
-source-directory = buildout-templates
+[templates]
+recipe = jweede.recipe.template
+template-file =
+    _pythonpath.py.j2
+    bin/bzr.j2
+    bin/combine-css.j2
+    bin/kill-test-services.j2
+    bin/lint.sh.j2
+    bin/retest.j2
+    bin/sprite-util.j2
+    bin/test.j2
+    bin/update-download-cache.j2
+    bin/watch_jsbuild.j2
+    bin/with-xvfb.j2
+    scripts/mlist-sync.py.j2
+target-file =
+    _pythonpath.py
+    bin/bzr
+    bin/combine-css
+    bin/kill-test-services
+    bin/lint.sh
+    bin/retest
+    bin/sprite-util
+    bin/test
+    bin/update-download-cache
+    bin/watch_jsbuild
+    bin/with-xvfb
+    scripts/mlist-sync.py
+base-dir = templates
+target-executable =
+    false
+    true
+    true
+    true
+    true
+    true
+    true
+    true
+    true
+    true
+    true
+    true
+jinja2_filters:
+    templates.filter.executable
+    templates.filter.path_repr
+    templates.filter.python_relative_path_setup
+    templates.filter.shell_path
+    templates.filter.shell_relative_path_setup
 
 [scripts]
 recipe = z3c.recipe.scripts

=== modified file 'doc/buildout.txt'
--- doc/buildout.txt	2014-01-30 15:04:06 +0000
+++ doc/buildout.txt	2017-01-16 13:22:42 +0000
@@ -317,7 +317,7 @@
     Sections other than ``[buildout]`` that are specified as parts always must
     specify a ``recipe``: an identifier that determines what code should
     process that section.  You'll see a variety of recipes in Launchpad's
-    buildout.cfg, including ``z3c.recipe.filetemplate``, ``zc.recipe.egg``, and
+    buildout.cfg, including ``jweede.recipe.template``, ``zc.recipe.egg``, and
     others.
 
 ``versions.cfg``
@@ -368,12 +368,12 @@
     The downside is that adding and upgrading packages takes a small additional
     step, as we'll see below.
 
-``buildout-templates``
-    The last additional item in the checkout is the ``buildout-templates``
-    directory.  This is used to hold templates that are used by the
-    section in buildout.cfg that uses the ``z3c.recipe.filetemplate`` recipe.
-    This can be used for many things, but we are using it as an alternate way
-    for producing scripts when the zc.recipe.egg approach is insufficient.
+``templates``
+    The last additional item in the checkout is the ``templates`` directory.
+    This is used to hold templates that are used by the section in
+    buildout.cfg that uses the ``jweede.recipe.template`` recipe.  This can be
+    used for many things, but we are using it as an alternate way for
+    producing scripts when the zc.recipe.egg approach is insufficient.
 
 In addition to these seven listings, after you have run the Makefile (or
 ``bin/buildout``), you will see an additional listing:
@@ -540,18 +540,19 @@
 recipe is one way, but well out of the scope of this document.  Read the
 zc.buildout documentation for direction.
 
-A much easier, and more limited approach is to use `z3c.recipe.filetemplate`_
-to build the file.  The recipe uses the ``buildout-templates`` directory,
-which is a mirror of the Launchpad source tree.  The recipe searches the tree
-for files ending in '.in', performs variable substitution on them, and then be
-copies them into the Launchpad source tree.
+A much easier, and more limited approach is to use `jweede.recipe.template`_
+to build the file.  The recipe uses the ``templates`` directory, which is a
+mirror of the Launchpad source tree.  The recipe searches the tree for files
+ending in '.j2', processes them as Jinja2 templates, and then copies them into
+the Launchpad source tree.
 
 To add a file using the recipe, simply create mirrors of the source tree
-directories that you need under ``buildout-templates/``, and create a .in file
-template at the desired location.  Take a look at
-``buildout-templates/bin/`` for examples of what is possible.
+directories that you need under ``templates/``, and create a .j2 file template
+at the desired location; then add appropriate entries to the ``[templates]``
+section of ``buildout.cfg``.  Take a look at ``templates/bin/`` for examples
+of what is possible.
 
-.. _`z3c.recipe.filetemplate`: http://pypi.python.org/pypi/z3c.recipe.filetemplate
+.. _`jweede.recipe.template`: http://pypi.python.org/pypi/jweede.recipe.template
 
 Work with Unreleased or Forked Packages
 =======================================

=== modified file 'lib/lp/app/templates/base-layout-macros.pt'
--- lib/lp/app/templates/base-layout-macros.pt	2016-09-14 11:13:06 +0000
+++ lib/lp/app/templates/base-layout-macros.pt	2017-01-16 13:22:42 +0000
@@ -198,7 +198,7 @@
   <tal:comment replace="nothing">
     This macro loads a single css file containing all our stylesheets.
     If you need to include a new css file here, add it to
-    buildout-templates/bin/combine-css.in instead.
+    templates/bin/combine-css.j2 instead.
 
     We load the CSS from the same host that served the HTML in order to optimize
     IE caching over SSL. This is inefficient when you cross subdomains (from

=== renamed directory 'buildout-templates' => 'templates'
=== added file 'templates/__init__.py'
=== renamed file 'buildout-templates/_pythonpath.py.in' => 'templates/_pythonpath.py.j2'
--- buildout-templates/_pythonpath.py.in	2016-12-22 16:32:38 +0000
+++ templates/_pythonpath.py.j2	2017-01-16 13:22:42 +0000
@@ -1,14 +1,14 @@
-# Copyright 2009-2016 Canonical Ltd.  This software is licensed under the
+# Copyright 2009-2017 Canonical Ltd.  This software is licensed under the
 # GNU Affero General Public License version 3 (see the file LICENSE).
 
 # NOTE: This is a generated file.  The original is in
-# buildout-templates/_pythonpath.py.in
+# templates/_pythonpath.py.j2
 
 # This file works if the Python has been started with -S, or if bin/py
 # has been used.
 
 # Auto-generated code to handle relative paths
-${python-relative-path-setup}
+{{context|python_relative_path_setup}}
 
 import os
 import sys
@@ -20,7 +20,7 @@
     'ignore', '.*(md5|sha|sets)', DeprecationWarning,
     )
 
-site_dir = ${scripts:parts-directory|path-repr}
+site_dir = {{parts.scripts['parts-directory']|path_repr}}
 
 if ('site' in sys.modules and
     not sys.modules['site'].__file__.startswith(
@@ -42,3 +42,4 @@
     # to set up its paths.
     sys.path[:] = [p for p in sys.path if 'site-packages' not in p]
 import site  # sets up paths
+

=== renamed file 'buildout-templates/bin/bzr.in' => 'templates/bin/bzr.j2'
--- buildout-templates/bin/bzr.in	2010-11-24 17:27:46 +0000
+++ templates/bin/bzr.j2	2017-01-16 13:22:42 +0000
@@ -1,9 +1,9 @@
-#!${buildout:executable} -S
+#!{{context|executable}} -S
 
 # Initialize our paths.
-${python-relative-path-setup}
+{{context|python_relative_path_setup}}
 import sys
-sys.path.insert(0, ${scripts:parts-directory|path-repr})
+sys.path.insert(0, {{parts.scripts['parts-directory']|path_repr}})
 import site
 
 # Run the script.
@@ -13,3 +13,4 @@
     pkg_resources.Requirement.parse('bzr'))
 
 bzr_distribution.run_script('bzr', globals().copy())
+

=== renamed file 'buildout-templates/bin/combine-css.in' => 'templates/bin/combine-css.j2'
--- buildout-templates/bin/combine-css.in	2013-07-04 01:04:50 +0000
+++ templates/bin/combine-css.j2	2017-01-16 13:22:42 +0000
@@ -1,9 +1,9 @@
-#!${buildout:executable} -S
+#!{{context|executable}} -S
 
 # Initialize our paths.
-${python-relative-path-setup}
+{{context|python_relative_path_setup}}
 import sys
-sys.path.insert(0, ${scripts:parts-directory|path-repr})
+sys.path.insert(0, {{parts.scripts['parts-directory']|path_repr}})
 import site
 
 import os
@@ -11,8 +11,7 @@
 from lp.scripts.utilities.js.jsbuild import ComboFile
 from lp.scripts.utilities.js.combo import combine_files
 
-root = os.path.abspath('.')
-root = os.path.normpath(${buildout:directory|path-repr})
+root = {{'.'|path_repr}}
 icing = os.path.join(root, 'lib/canonical/launchpad/icing')
 target = os.path.join(icing, 'combo.css')
 # It'd probably be nice to have this script find all the CSS files we might
@@ -81,3 +80,4 @@
     f = open(target, 'w')
     f.write(result)
     f.close()
+

=== renamed file 'buildout-templates/bin/kill-test-services.in' => 'templates/bin/kill-test-services.j2'
--- buildout-templates/bin/kill-test-services.in	2011-12-30 01:48:17 +0000
+++ templates/bin/kill-test-services.j2	2017-01-16 13:22:42 +0000
@@ -1,13 +1,13 @@
-#!${buildout:executable} -S
+#!{{context|executable}} -S
 #
-# Copyright 2009 Canonical Ltd.  This software is licensed under the
+# Copyright 2009-2017 Canonical Ltd.  This software is licensed under the
 # GNU Affero General Public License version 3 (see the file LICENSE).
 """Kill all the test services that may persist between test runs."""
 
 # Initialize our paths.
-${python-relative-path-setup}
+{{context|python_relative_path_setup}}
 import sys
-sys.path.insert(0, ${scripts:parts-directory|path-repr})
+sys.path.insert(0, {{parts.scripts['parts-directory']|path_repr}})
 import site
 
 # Tell lp.services.config to use the testrunner config instance, so that
@@ -38,3 +38,4 @@
 
 if __name__ == '__main__':
     sys.exit(main(sys.argv[1:]))
+

=== renamed file 'buildout-templates/bin/lint.sh.in' => 'templates/bin/lint.sh.j2'
--- buildout-templates/bin/lint.sh.in	2012-05-11 05:14:01 +0000
+++ templates/bin/lint.sh.j2	2017-01-16 13:22:42 +0000
@@ -1,14 +1,14 @@
 #!/bin/bash
 #
-# Copyright 2009-2010 Canonical Ltd.  This software is licensed under the
+# Copyright 2009-2017 Canonical Ltd.  This software is licensed under the
 # GNU Affero General Public License version 3 (see the file LICENSE).
 #
 # Runs pocketlint on files changed from parent branch.
 
 
-${shell-relative-path-setup}
+{{context|shell_relative_path_setup}}
 
-utilitiesdir=${buildout:directory/utilities|shell-path}
+utilitiesdir={{'utilities'|shell_path}}
 [ -z "$utilitiesdir" ] && utilitiesdir=.
 
 
@@ -44,3 +44,4 @@
 
 echo ""
 pocketlint $pocketlint_files 2>&1
+

=== renamed file 'buildout-templates/bin/retest.in' => 'templates/bin/retest.j2'
--- buildout-templates/bin/retest.in	2012-01-05 16:22:45 +0000
+++ templates/bin/retest.j2	2017-01-16 13:22:42 +0000
@@ -1,6 +1,6 @@
-#!${buildout:executable}
+#!{{context|executable}}
 #
-# Copyright 2009 Canonical Ltd.  This software is licensed under the
+# Copyright 2009-2017 Canonical Ltd.  This software is licensed under the
 # GNU Affero General Public License version 3 (see the file LICENSE).
 
 """
@@ -30,10 +30,10 @@
 import sys
 from itertools import takewhile, imap
 
-${python-relative-path-setup}
+{{context|python_relative_path_setup}}
 
 # The test script for this branch.
-TEST = "${buildout:directory/bin/test}"
+TEST = {{'bin/test'|path_repr}}
 
 # Regular expression to match numbered stories.
 STORY_RE = re.compile("(.*)/\d{2}-.*")
@@ -109,3 +109,4 @@
             "Usage: %s [test_output_file|-] ...\n\n%s\n\n" % (
                 sys.argv[0], __doc__.strip()))
         sys.exit(1)
+

=== renamed file 'buildout-templates/bin/sprite-util.in' => 'templates/bin/sprite-util.j2'
--- buildout-templates/bin/sprite-util.in	2012-06-02 12:08:17 +0000
+++ templates/bin/sprite-util.j2	2017-01-16 13:22:42 +0000
@@ -1,11 +1,11 @@
-#!${buildout:executable} -S
+#!{{context|executable}} -S
 
 import os
 import sys
 
 # Initialize our paths.
-${python-relative-path-setup}
-sys.path.insert(0, ${scripts:parts-directory|path-repr})
+{{context|python_relative_path_setup}}
+sys.path.insert(0, {{parts.scripts['parts-directory']|path_repr}})
 import site
 
 from lp.services.spriteutils import SpriteUtil
@@ -26,7 +26,7 @@
         print >> sys.stderr, usage()
         sys.exit(2)
 
-icing = ${buildout:directory/lib/canonical/launchpad/icing|path-repr}
+icing = {{'lib/canonical/launchpad/icing'|path_repr}}
 sprite_groups = [
     file_name.replace('.css.in', '')
     for file_name in os.listdir(icing) if file_name.endswith('.css.in')]
@@ -57,3 +57,4 @@
         # The icing/icon-sprites.png file is relative to the css file
         # in the icing/build/ directory.
         sprite_util.saveConvertedCSS(css_file, '../%s.png' % group_name)
+

=== renamed file 'buildout-templates/bin/test.in' => 'templates/bin/test.j2'
--- buildout-templates/bin/test.in	2014-01-24 04:40:36 +0000
+++ templates/bin/test.j2	2017-01-16 13:22:42 +0000
@@ -1,4 +1,4 @@
-#!${buildout:executable} -S
+#!{{context|executable}} -S
 ##############################################################################
 #
 # Copyright (c) 2004 Zope Corporation and Contributors.
@@ -15,26 +15,25 @@
 """Test script
 """
 
-# NOTE: This is a generated file.  The original is in
-# buildout-templates/bin/test.in
+# NOTE: This is a generated file.  The original is in templates/bin/test.j2
 
 import logging, os, re, sys, time, warnings
 
 # Initialize our paths.
-${python-relative-path-setup}
+{{context|python_relative_path_setup}}
 
 
 # The working directory change is just so that the test script
 # can be invoked from places other than the root of the source
 # tree. This is very useful for IDE integration, so an IDE can
 # e.g. run the test that you are currently editing.
-BUILD_DIR = ${buildout:directory|path-repr}
+BUILD_DIR = {{'.'|path_repr}}
 there = os.getcwd()
 os.chdir(BUILD_DIR)
 
 
 import sys
-sys.path.insert(0, ${scripts:parts-directory|path-repr})
+sys.path.insert(0, {{parts.scripts['parts-directory']|path_repr}})
 import site
 
 
@@ -53,7 +52,7 @@
 doctest._SpoofOut = _SpoofOut
 
 
-CUSTOM_SITE_DIR = ${scripts:parts-directory|path-repr}
+CUSTOM_SITE_DIR = {{parts.scripts['parts-directory']|path_repr}}
 
 # Make tests run in a timezone no launchpad developers live in.
 # Our tests need to run in any timezone.
@@ -184,7 +183,7 @@
 defaults = {
     # Find tests in the tests and ftests directories
     'tests_pattern': '^f?tests$',
-    'test_path': [${buildout:directory/lib|path-repr}],
+    'test_path': [{{'lib'|path_repr}}],
     'package': ['canonical', 'lp', 'devscripts', 'launchpad_loggerhead'],
     'layer': ['!(YUIAppServerLayer)'],
     'require_unique_ids': True,
@@ -275,3 +274,4 @@
             raise
     finally:
         os.chdir(there)
+

=== renamed file 'buildout-templates/bin/update-download-cache.in' => 'templates/bin/update-download-cache.j2'
--- buildout-templates/bin/update-download-cache.in	2010-04-20 19:10:35 +0000
+++ templates/bin/update-download-cache.j2	2017-01-16 13:22:42 +0000
@@ -1,4 +1,4 @@
-${shell-relative-path-setup}
+{{context|shell_relative_path_setup}}
 
-bzr up ${buildout:directory/buildout/download-cache|shell-path}
+bzr up {{'buildout/download-cache'|shell_path}}
 

=== renamed file 'buildout-templates/bin/watch_jsbuild.in' => 'templates/bin/watch_jsbuild.j2'
--- buildout-templates/bin/watch_jsbuild.in	2012-02-28 19:40:50 +0000
+++ templates/bin/watch_jsbuild.j2	2017-01-16 13:22:42 +0000
@@ -29,3 +29,4 @@
             meta_jsmodule=meta_name)
 
     builder.run()
+

=== renamed file 'buildout-templates/bin/with-xvfb.in' => 'templates/bin/with-xvfb.j2'
--- buildout-templates/bin/with-xvfb.in	2011-09-09 15:05:54 +0000
+++ templates/bin/with-xvfb.j2	2017-01-16 13:22:42 +0000
@@ -1,6 +1,6 @@
 #!/bin/bash
 #
-# Copyright 2011 Canonical Ltd.  This software is licensed under the
+# Copyright 2011-2017 Canonical Ltd.  This software is licensed under the
 # GNU Affero General Public License version 3 (see the file LICENSE).
 #
 # Wrapper that provides a default Xvfb environment for the given
@@ -23,12 +23,12 @@
     if command="$(PATH="$(dirname "$0")" type -P "$1")"
     then
         # Shift $1 off and set new positional arguments.
-        shift && set -- "$${command}" "$@"
+        shift && set -- "${command}" "$@"
     fi
 # If no command has been given and SHELL is set, spawn a shell.
-elif [ $# -eq 0 -a -n "$${SHELL:-}" ]
+elif [ $# -eq 0 -a -n "${SHELL:-}" ]
 then
-    set -- "$${SHELL}"
+    set -- "${SHELL}"
 fi
 
 #
@@ -44,3 +44,4 @@
 exec xvfb-run \
     --server-args="-ac -screen 0 1024x768x24" \
     --auto-servernum -- "$@"
+

=== added file 'templates/filter.py'
--- templates/filter.py	1970-01-01 00:00:00 +0000
+++ templates/filter.py	2017-01-16 13:22:42 +0000
@@ -0,0 +1,117 @@
+# Copyright 2017 Canonical Ltd.  This software is licensed under the
+# GNU Affero General Public License version 3 (see the file LICENSE).
+#
+# The relative path setup code in this file is borrowed from
+# z3c.recipe.filetemplate, whose copyright and licence notice follows:
+#
+# Copyright (c) 2007-2009 Zope Foundation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+
+"""Custom Jinja2 filters for the Launchpad build system."""
+
+from __future__ import absolute_import, print_function, unicode_literals
+
+__metaclass__ = type
+__all__ = []
+
+import os.path
+import sys
+
+from jinja2 import contextfilter
+
+
+def executable(ignored):
+    return sys.executable
+
+
+PYTHON_RELATIVE_PATH_SETUP_START = '''\
+import imp
+import os.path
+
+# Get path to this file.
+if __name__ == '__main__':
+    _template_filename = __file__
+else:
+    # If this is an imported module, we want the location of the .py
+    # file, not the .pyc, because the .py file may have been symlinked.
+    _template_filename = imp.find_module(__name__)[1]
+# Get the full, non-symbolic-link directory for this file.
+_template_base = os.path.dirname(
+    os.path.abspath(os.path.realpath(_template_filename)))
+'''
+
+
+PYTHON_DIRNAME = '''\
+_template_base = os.path.dirname(_template_base)
+'''
+
+
+PYTHON_RELATIVE_PATH_SETUP_END = '''\
+def _template_path_repr(path):
+    """Return absolute version of buildout-relative path."""
+    return os.path.normpath(os.path.join(_template_base, path))
+'''
+
+
+@contextfilter
+def python_relative_path_setup(context, ignored):
+    depth = context.name.count(os.sep)
+    value = PYTHON_RELATIVE_PATH_SETUP_START
+    if depth:
+        value += "# Ascend to buildout root.\n"
+        value += depth * PYTHON_DIRNAME
+    else:
+        value += "# This is the buildout root.\n"
+    value += PYTHON_RELATIVE_PATH_SETUP_END
+    return value
+
+
+def path_repr(path):
+    path = os.path.realpath(path)
+    base = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
+    if path == base or path.startswith(os.path.join(base, "")):
+        return "_template_path_repr(%r)" % os.path.relpath(path, base)
+    return repr(path)
+
+
+SHELL_RELATIVE_PATH_SETUP = '''\
+# Get full, non-symbolic-link path to this file.
+TEMPLATE_FILENAME=`\\
+    readlink -f "$0" 2>/dev/null || \\
+    realpath "$0" 2>/dev/null || \\
+    type -P "$0" 2>/dev/null`
+# Get directory of file.
+TEMPLATE_BASE=`dirname ${TEMPLATE_FILENAME}`
+'''
+
+
+SHELL_DIRNAME = '''\
+TEMPLATE_BASE=`dirname ${TEMPLATE_BASE}`
+'''
+
+
+@contextfilter
+def shell_relative_path_setup(context, ignored):
+    depth = context.name.count(os.sep)
+    value = SHELL_RELATIVE_PATH_SETUP
+    if depth:
+        value += "# Ascend to buildout root.\n"
+        value += depth * SHELL_DIRNAME
+    else:
+        value += "# This is the buildout root.\n"
+    return value
+
+
+def shell_path(path):
+    path = os.path.realpath(path)
+    base = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
+    if path == base or path.startswith(os.path.join(base, "")):
+        return '"$TEMPLATE_BASE/%s"' % os.path.relpath(path, base)
+    return path

=== renamed file 'buildout-templates/scripts/mlist-sync.py.in' => 'templates/scripts/mlist-sync.py.j2'
--- buildout-templates/scripts/mlist-sync.py.in	2013-01-07 03:29:28 +0000
+++ templates/scripts/mlist-sync.py.j2	2017-01-16 13:22:42 +0000
@@ -1,15 +1,15 @@
-#!${buildout:executable} -S
+#!{{context|executable}} -S
 
-# Copyright 2009-2012 Canonical Ltd.  This software is licensed under the
+# Copyright 2009-2017 Canonical Ltd.  This software is licensed under the
 # GNU Affero General Public License version 3 (see the file LICENSE).
 
 """Sync Mailman data from one Launchpad to another."""
 
 # Initialize our paths.
-${python-relative-path-setup}
+{{context|python_relative_path_setup}}
 
 import sys
-sys.path.insert(0, ${scripts:parts-directory|path-repr})
+sys.path.insert(0, {{parts.scripts['parts-directory']|path_repr}})
 import site
 
 # Run the script.
@@ -233,3 +233,4 @@
     script = MailingListSyncScript('scripts.mlist-sync', 'mlist-sync')
     status = script.lock_and_run()
     sys.exit(status)
+

=== modified file 'versions.cfg'
--- versions.cfg	2016-11-03 15:19:01 +0000
+++ versions.cfg	2017-01-16 13:22:42 +0000
@@ -48,6 +48,7 @@
 iso8601 = 0.1.4
 itsdangerous = 0.24
 jsautobuild = 0.2
+jweede.recipe.template = 1.2.4
 keyring = 0.6.2
 kombu = 3.0.30
 launchpad-buildd = 136
@@ -158,7 +159,6 @@
 wsgiref = 0.1.2
 z3c.pt = 2.2.3
 z3c.ptcompat = 0.5.7
-z3c.recipe.filetemplate = 2.2.0
 z3c.recipe.i18n = 0.8.1
 z3c.recipe.tag = 0.6
 # Also upgrade the zc.buildout version in the Makefile's bin/buildout section.


Follow ups