Commit d181e4f5 authored by Julien Muchembled's avatar Julien Muchembled

re6st-node: reimplement in Python the part to build files to send to OBS

Makefile was so horrible and unreliable.
I tried https://waf.io/ which is nice but not suitable for this
(see the example 'wscript' at the end).

Functional improvements:
- better detection of what needs to be rebuilt or not
- reproducible tarballs, except for the re6stnet egg
  (and the main tarball if the egg is rebuilt)
- fewer temporary files

And support for OSC is back.

_______________________________________________________________________________

import os, shutil, subprocess, urllib
from waflib import Node, Utils

PREFIX = "opt/re6st"
BOOTSTRAP_URL = "http://downloads.buildout.org/1/bootstrap.py"
repo_dict = dict(
    re6stnet="http://git.erp5.org/repos/re6stnet.git",
    slapos="http://git.erp5.org/repos/slapos.git",
)

def configure(ctx):
    for name, url in repo_dict.iteritems():
        if ctx.path.find_node(name) is None:
            ctx.exec_command(("git", "clone", url), stdout=None, stderr=None)

def cfg(task):
    o, = task.outputs
    bld = task.generator.bld
    o.write(task.inputs[0].read() % dict(
        SLAPOS=bld.srcnode.search_node("slapos").abspath(),
        ROOT="${buildout:directory}/" + bld.bldnode.path_from(o.parent),
        TARGET="/"+PREFIX))

def bootstrap(task):
    b = task.outputs[0]
    d = b.parent.parent
    bootstrap = urllib.urlopen(BOOTSTRAP_URL).read()
    for cache in "download-cache", "extends-cache":
        cache = d.make_node(cache).abspath()
        if os.path.exists(cache):
            shutil.rmtree(cache)
        os.mkdir(cache)
    cwd = d.abspath()
    task.exec_command(("python2", "-S"), input=bootstrap, cwd=cwd)
    task.exec_command((b.abspath(), "buildout:parts=python"), cwd=cwd)

def sdist(task):
    r, p = task.inputs
    d = p.find_node("../../download-cache/dist")
    for x in d.ant_glob("re6stnet-*", quiet=True):
        x.delete()
    task.exec_command((p.abspath(), "setup.py", "sdist", "-d", d.abspath()),
                      cwd=r.abspath())

def build(bld):
    b = bld.bldnode.make_node(PREFIX)
    buildout_cfg = b.make_node("buildout.cfg")
    bld(source="buildout.cfg.in", target=buildout_cfg, rule=cfg)
    tg = bld(source=(buildout_cfg, "slapos"), rule=bootstrap,
             target=map(b.make_node, ("bin/buildout", "bin/python")))
    buildout, python = tg.target
    r = bld.path.find_node("re6stnet")
    tg = bld(source=(r, python), rule=sdist, update_outputs=True,
             target=r.make_node("re6stnet.egg-info/PKG-INFO"))
    bld(name="buildout", source=(buildout_cfg, tg.target),
        target=b.make_node(".installed.cfg"),
        rule=lambda task: task.exec_command((buildout.abspath(),),
            stdout=None, stderr=None, cwd=b.abspath()))

 ###

def h_file(fname, h_file=Utils.h_file):
    if os.path.isdir(fname):
        m = Utils.md5(fname)
        n = len(fname) + 1
        for dirpath, dirs, files in os.walk(fname):
            if dirpath.endswith(("/.git", ".egg-info")):
                del dirs[:]
                continue
            dirs.sort()
            files.sort()
            m.update(dirpath[n:] + '\0')
            for fname in files:
                m.update("%s\0%s\0" % (h_file(os.path.join(dirpath, fname)),
                                       fname))
        return m.digest()
    return h_file(fname)
Utils.h_file = h_file

def find_resource(self, lst):
    if isinstance(lst, str):
        lst = [x for x in Node.split_path(lst) if x and x != '.']
    return self.get_bld().search_node(lst) \
        or self.get_src().find_node(lst)
Node.Node.find_resource = find_resource
parent 0fa14b78
/dist/
/re6stnet/ /re6stnet/
/slapos/ /slapos/
/upstream.mk /osc
/re6st-node_*.tar.gz
/re6st-node_*.dsc
/debian/control
/debian/changelog /debian/changelog
/re6stnet.spec
/PKGBUILD
preparing the package Building the files to be sent to OBS
--------------------- ------------------------------------
First make sure all files are ready and you have all necessary packages installed. First make sure all files are ready and you have all necessary packages installed.
You need in particular an OBS directory (for example, the vifib test directory)::
$ cd <directory_to_contain_prepare_script> $ ./make
$ osc checkout home:VIFIBnexedi:branches:home:VIFIBnexedi/Re6stnet
$ cd home:VIFIBnexedi:branches:home:VIFIBnexedi/Re6stnet
$ osc up
All output files are in the 'dist' folder, which is created automatically.
Upload to OBS
-------------
For this, you need a checkout of the OBS repository, and make a 'osc' symlink
pointing to it. For example, the vifib test directory::
A$ cd <where_you_want>
B$ osc checkout home:VIFIBnexedi:branches:home:VIFIBnexedi/Re6stnet
B$ cd home:VIFIBnexedi:branches:home:VIFIBnexedi/Re6stnet
B$ osc up
B$ cd A
A$ ln -s B/home:VIFIBnexedi:branches:home:VIFIBnexedi/Re6stnet osc
And whenever you want to push updates::
A$ ./make osc
A$ (cd osc; osc commit)
Warning about SlapOS updates
----------------------------
When a SlapOS update would add new files to download-cache or extends-cache,
everything should be rebuilt by deleting the 'build' folder, in order to remove
unused files from the caches.
#!/usr/bin/env python
#
# Copyright (C) 2016 Julien Muchembled <jm@nexedi.com>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License version 3 as
# published by the Free Software Foundation.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
"""
Very basic Python implementation of a build system similar to the well-known
'make'. The main concept is the same:
input files --( recipe )-> output files
with comparison of timestamps to trigger recipes
(i.e. no temporary files generated to track the status of the build).
The main differences are:
- no parallelism
- no implicit rule, no predefined rule
+ inputs can be whole directory
+ change time of inputs is also taken into account
+ multiple list of outputs for a recipe
+ dynamic list of outputs
+ no need to care about wrongly/partially written files in case of failure
+ Python...
"""
import argparse, atexit, errno, gzip, os, shutil, subprocess, sys, tarfile
from contextlib import contextmanager
INF = float("inf")
_INF = - INF
class _task(object):
def __init__(self, *src):
self.outputs = src
def __str__(self):
o = self.outputs
return repr(o[0]) if len(o) == 1 else str(o)
@property
def output(self):
o, = self.outputs
return o
def __call__(self, dry_run):
run = self._run
if callable(run):
self._run = run = run(dry_run)
return run
@staticmethod
def _time(path):
return os.stat(path).st_mtime
def _run(self, dry_run=None):
x = self.outputs
return max(map(self._time, x)) if x else _INF
class files(_task):
def __init__(self, *src, **kw):
if not kw.pop("ctime", True):
self._time = _task._time
_task.__init__(self, *src, **kw)
@staticmethod
def _time(path):
s = os.stat(path)
return max(s.st_ctime, s.st_mtime)
class tree(files):
"""
e.g. @task(tree("some/folder"), "some/output")
"""
def __init__(self, root, ignore=None, **kw):
files.__init__(self, root, **kw)
self.ignore = ignore
def __iter__(self):
root, = self.outputs
yield root
ignore = self.ignore
n = len(root) + 1
for dirpath, dirs, files in os.walk(root):
dirpath += os.sep
if ignore:
x = dirpath[n:]
dirs[:] = (name for name in dirs if not ignore(x + name))
for name in dirs:
yield dirpath + name
for name in files:
x = dirpath + name
if not (ignore and ignore(x[n:])):
yield x
def _run(self, dry_run=None):
return max(map(self._time, self))
class task(_task):
"""
@task(depends, provides)
def mytask(task):
# task.inputs
# task.outputs
'depends' is a sequence of other tasks or file paths. The order is important
because it defines the order of task.inputs: not however that paths are
automatically moved after tasks.
'provides' is a sequence of callables or file paths. Callables are always
called to complete task.outputs. Recipe is always called if task.outputs
is empty.
For both 'depends' and 'provides', if you have only 1 element, you can pass
it directly instead of making a 1-size sequence.
'input' and 'output' properties are shortcut to get the only path.
'why' is a list of tasks explaining why the recipe is called.
"""
def __new__(cls, *args, **kw):
def task_gen(func):
self = _task.__new__(cls)
self.__init__(func, *args, **kw)
return self
return task_gen
def __init__(self, run, depends, provides=(),
__str_or_task = (basestring, _task)):
self.run = run
self.depends = []
f = []
for x in (depends,) if isinstance(depends, __str_or_task) else depends:
(f if isinstance(x, basestring) else self.depends).append(x)
f and self.depends.append(files(*f))
self.provides = ((provides,)
if isinstance(provides, basestring) or callable(provides)
else provides)
self.why = self,
def __str__(self):
return self.run.__name__
@property
def input(self):
i, = self.inputs
return i
def _run(self, dry_run, _otime=INF):
self.inputs = x = []
deps = []
for dep in self.depends:
deps.append((dep, dep(dry_run)))
x += dep.outputs
self.outputs = x = []
for p in self.provides:
if callable(p):
x += p(self)
else:
x.append(p)
if None not in x:
try:
_otime = _task._run(self)
except OSError as e:
if e.errno != errno.ENOENT:
raise
else:
if deps:
self.why = [dep for dep, itime in deps if _otime < itime]
if self.why:
print "# Processing %s: %s -> %s" % (self,
", ".join(map(str, self.depends)),
", ".join("<%s>" % x.__name__ if callable(x) else x
for x in self.provides or "?"))
if not dry_run:
try:
self.run(self)
except:
if _otime is INF:
_otime = max(x[1] for x in deps) - 1
# Files may still be open due to references in tracebacks.
# Make sure they're all closed before reverting mtimes.
atexit.register(self._revert, self.outputs, _otime)
raise
return _task._run(self) if self.outputs else INF
return _otime
@classmethod
def _revert(cls, outputs, mtime):
try:
for x in outputs:
if mtime < cls._time(x):
os.utime(x, (mtime, mtime))
except Exception:
pass
def main():
parser = argparse.ArgumentParser()
_ = parser.add_argument
_("-f", "--file", type=argparse.FileType("r"), default="make.py",
help="Python script describing how to build the project"
" (default: make.py).")
_("-l", "--list", action="store_true",
help="List defined tasks.")
_("-n", "--dry-run", action="store_true",
help="Print the tasks that would be executed.")
_("task", nargs="*", default=("build",),
help="Tasks to process (default: build).")
args = parser.parse_args()
sys.modules["make"] = sys.modules.pop(__name__)
f = args.file
tasks = {"__file__": f.name}
exec(compile(f.read(), f.name, "exec"), tasks)
if args.list:
print " ".join(sorted(k for k, v in tasks.iteritems()
if isinstance(v, _task)))
return
for t in args.task:
if not isinstance(tasks.get(t), _task):
sys.exit("%s is not a valid task." % t)
for t in args.task:
tasks[t](args.dry_run)
##
# Helpers
#
class git(tree):
def __init__(self, root, url=None, ignore=None, **kw):
_ignore = lambda x: x == ".git" or ignore and ignore(x)
tree.__init__(self, root, _ignore, **kw)
self.url = url
def _run(self, dry_run):
root, = self.outputs
if os.path.isdir(root):
return tree._run(self)
dry_run or subprocess.check_call(("git", "clone", self.url, root))
return _INF
def check_output(*args, **kw):
# BBB: 'input' arg is a backport from Python 3.4
input = kw.pop("input", None)
if input is not None:
kw["stdin"] = subprocess.PIPE
p = subprocess.Popen(stdout=subprocess.PIPE, *args, **kw)
out = p.communicate(input)[0]
if p.returncode:
raise subprocess.CalledProcessError(p.returncode,
args[0] if args else kw["args"])
return out
@contextmanager
def cwd(path):
p = os.getcwd()
try:
os.chdir(path)
yield p
finally:
os.chdir(p)
def mkdir(path):
try:
os.makedirs(path)
except OSError as e:
if e.errno != errno.EEXIST or not os.path.isdir(path):
raise
def remove(path):
try:
os.remove(path)
except OSError as e:
if e.errno != errno.ENOENT:
raise
def rmtree(path):
if os.path.exists(path):
shutil.rmtree(path)
@contextmanager
def make_tar_gz(path, mtime, xform=(lambda x: x), **kw):
# Make reproducible tarball. Otherwise, it's really annoying that we can't
# rely on 'osc status' to know whether there are real changes or not.
# BBB: Results differ between Python 2.6 and 2.7 because of tarfile.
__init__ = gzip.GzipFile.__init__
listdir = os.listdir
try:
if sys.version_info >= (2, 7):
gzip.GzipFile.__init__ = lambda *args: __init__(mtime=mtime, *args)
os.listdir = lambda path: sorted(listdir(path))
t = tarfile.open(path, "w:gz", **kw)
_gettarinfo = t.gettarinfo
def gettarinfo(name=None, arcname=None, fileobj=None):
tarinfo = _gettarinfo(name, xform(arcname or name), fileobj)
tarinfo.mtime = mtime
return tarinfo
t.gettarinfo = gettarinfo
yield t
except:
remove(path)
raise
finally:
gzip.GzipFile.__init__ = __init__
os.listdir = listdir
t.close()
if sys.version_info < (2, 7):
with open(path, "r+") as t:
t.seek(4)
gzip.write32u(t, long(mtime))
if __name__ == "__main__":
sys.exit(main())
...@@ -2,17 +2,17 @@ ...@@ -2,17 +2,17 @@
# It automatically clones missing repositories but doesn't automatically pull. # It automatically clones missing repositories but doesn't automatically pull.
# You have to choose by using git manually. # You have to choose by using git manually.
# Run with SLAPOS_EPOCH=<N> (where <N> is an integer > 1) # Run with SLAPOS_EPOCH=<N> environment variable (where <N> is an integer > 1)
# if rebuilding for new SlapOS version but same re6stnet. # if rebuilding for new SlapOS version but same re6stnet.
# Non-obvious dependencies: # Non-obvious dependencies:
# - Debian: python-debian, python-docutils | python3-docutils # - Debian: python-debian, python-docutils | python3-docutils
# We could avoid them by doing like for setuptools, but I'd rather go the # We could avoid them by doing like for setuptools, but I'd rather go the
# opposite way: simplify the upload part by using the system setuptools and # opposite way: simplify the upload part by using the system setuptools.
# recent features from Make 4.
# This Makefile tries to be smart by only rebuilding the necessary parts after # This "makefile" is quite smart at only rebuilding the necessary parts after
# some change. But as always, it does not handle all cases, so once everything # some change. The main exception concerns the download-cache & extends-cache,
# because the 'buildout' step is really long. In doubt, and once everything
# works, you should clean up everything before the final prepare+upload. # works, you should clean up everything before the final prepare+upload.
# TODO: # TODO:
...@@ -33,17 +33,6 @@ ...@@ -33,17 +33,6 @@
# - re6stnet sdist # - re6stnet sdist
# - a tarball of remaining download-cache # - a tarball of remaining download-cache
# - 1 tarball with everything else # - 1 tarball with everything else
# - Make tarballs "reproducible". If the contents does not change, and we don't
# really care about modification times, the result should always be the same.
# It's really annoying that we can't rely on 'osc status' to know whether
# there are real changes or not. 2 ways:
# - This is doable with a very recent of tar (not even in Jessie), in order
# to sort files by name (--sort option). Then there are timestamps:
# see -n option of gzip, and --mtime option of tar.
# - But the best one is probably to use Python instead of tar+gzip.
# And in fact, such Python script should not be limited to that, since many
# intermediate files like re6stnet.spec are so quick to generate that they
# should be produced in RAM.
# #
# Note that package don't contain *.py[co] files and they're not generated # Note that package don't contain *.py[co] files and they're not generated
# at installation. For this package, it's better like this because it minimizes # at installation. For this package, it's better like this because it minimizes
...@@ -51,124 +40,216 @@ ...@@ -51,124 +40,216 @@
# If this way of packaging is reused for other software, postinst scripts # If this way of packaging is reused for other software, postinst scripts
# should be implemented. # should be implemented.
BOOTSTRAP_URL = http://downloads.buildout.org/1/bootstrap.py import os, rfc822, shutil, time, urllib
RE6STNET_URL = http://git.erp5.org/repos/re6stnet.git from glob import glob
SLAPOS_URL = http://git.erp5.org/repos/slapos.git from cStringIO import StringIO
from subprocess import check_call
PACKAGE = re6st-node from make import *
BIN = re6st-conf re6st-registry re6stnet from debian.changelog import Changelog
NOPART = chrpath flex glib lunzip m4 patch perl popt site_perl xz-utils from debian.deb822 import Deb822
TARGET = opt/re6st
BOOTSTRAP_URL = "http://downloads.buildout.org/1/bootstrap.py"
ROOT = build PACKAGE = "re6st-node"
BUILD = $(ROOT)/$(TARGET)
BIN = "re6st-conf re6st-registry re6stnet".split()
BUILD_KEEP = buildout.cfg extends-cache download-cache BUILD_KEEP = "buildout.cfg", "extends-cache", "download-cache"
NOPART = "chrpath flex glib lunzip m4 patch perl popt site_perl xz-utils".split()
all: tarball debian.tar.gz re6stnet.spec PKGBUILD re6stnet.install TARGET = "opt/re6st"
re6stnet: ROOT = "build"
git clone $(RE6STNET_URL) $@ BUILD = ROOT + "/" + TARGET
DIST = "dist"
slapos: OSC = "osc" # usually a symlink to the destination osc folder
git clone $(SLAPOS_URL) $@
re6stnet = git("re6stnet", "http://git.erp5.org/repos/re6stnet.git",
$(BUILD)/buildout.cfg: buildout.cfg.in "docs".__eq__)
mkdir -p $(@D) slapos = git("slapos", "http://git.erp5.org/repos/slapos.git",
python2 -c 'import os; cfg = open("$<").read() % dict( \ ctime=False) # ignore ctime due to hardlinks to *-cache
SLAPOS="$(CURDIR)/slapos", ROOT="$${buildout:directory}/" \
+ os.path.relpath("$(ROOT)", "$(BUILD)"), \ os.environ["TZ"] = "UTC"; time.tzset()
TARGET="/$(TARGET)"); open("$@", "w").write(cfg)'
@task("buildout.cfg.in", BUILD + "/buildout.cfg")
$(BUILD)/bin/python: $(BUILD)/buildout.cfg slapos def cfg(task):
if [ -e $@ ]; then touch $@; else cd $(BUILD) \ cfg = open(task.input).read() % dict(
&& rm -rf extends-cache && mkdir -p download-cache extends-cache \ SLAPOS=os.path.abspath("slapos"),
&& wget -qO - $(BOOTSTRAP_URL) | python2 -S \ ROOT="${buildout:directory}/" + os.path.relpath(ROOT, BUILD),
&& bin/buildout buildout:parts=$(@F); fi TARGET="/"+TARGET)
mkdir(BUILD)
re6stnet/re6stnet.egg-info: $(BUILD)/bin/python re6stnet open(task.output, "w").write(cfg)
rm -f $(BUILD)/download-cache/dist/re6stnet-*
cd re6stnet && ../$< setup.py sdist -d ../$(BUILD)/download-cache/dist @task((cfg, slapos), (BUILD + "/bin/buildout", BUILD + "/bin/python"))
# Touch target because the current directory is used as temporary def bootstrap(task):
# storage, and it is cleaned up after that setup.py runs egg_info. try:
touch $@ os.utime(task.outputs[1], None)
except OSError:
$(BUILD)/.installed.cfg: re6stnet/re6stnet.egg-info bootstrap = urllib.urlopen(BOOTSTRAP_URL).read()
cd $(BUILD) && bin/buildout mkdir(BUILD + "/download-cache")
# Touch target in case that buildout had nothing to do. with cwd(BUILD):
touch $@ rmtree("extends-cache")
os.mkdir("extends-cache")
$(ROOT)/Makefile: Makefile.in Makefile check_output((sys.executable, "-S"), input=bootstrap)
($(foreach x,BIN NOPART BUILD_KEEP TARGET, \ check_call(("bin/buildout", "buildout:parts=python"))
echo $(x) = $($(x)) &&) cat $<) > $@
def sdist_version(egg):
prepare: $(BUILD)/.installed.cfg $(ROOT)/Makefile global MTIME, VERSION
$(eval VERSION = $(shell cd $(BUILD)/download-cache/dist \ MTIME = os.stat(egg).st_mtime
&& set re6stnet-* && set $${1#*-} \ VERSION = "%s+slapos%s.g%s" % (
&& echo -n $${1%.tar.*}+slapos$(SLAPOS_EPOCH).g \ egg.rsplit("-", 1)[1].split(".tar.")[0],
&& cd $(CURDIR)/slapos && git rev-parse --short HEAD)) os.getenv("SLAPOS_EPOCH", ""),
make -C re6stnet check_output(("git", "rev-parse", "--short", "HEAD"),
cwd="slapos").strip())
upstream.mk: re6stnet Makefile tarball.provides = "%s/%s_%s.tar.gz" % (DIST, PACKAGE, VERSION),
(echo 'override PYTHON = /$(TARGET)/parts/python2.7/bin/python' \ deb.provides = deb.provides[0], "%s/%s_%s.dsc" % (DIST, PACKAGE, VERSION)
&& cat $</Makefile) > $@ mkdir(DIST)
return egg
tarball: upstream.mk prepare
tar -caf $(PACKAGE)_$(VERSION).tar.gz \ def sdist(task):
--xform s,^re6stnet/,, \ o = glob(BUILD + "/download-cache/dist/re6stnet-*")
--xform s,^,$(PACKAGE)-$(VERSION)/, \ try:
cleanup install-eggs rebootstrap $< \ return sdist_version(*o),
re6stnet/daemon re6stnet/docs/*.1 re6stnet/docs/*.8 \ except TypeError:
-C $(ROOT) Makefile $(patsubst %,$(TARGET)/%,$(BUILD_KEEP)) return None,
debian/changelog: prepare @task((bootstrap, re6stnet), ("re6stnet/re6stnet.egg-info", sdist))
cd re6stnet && sed s,$@,../$@, debian/common.mk | \ def sdist(task):
make -f - PACKAGE=$(PACKAGE) VERSION=$(VERSION) ../$@ # XXX: We'd like to produce a reproducible tarball, so that 'make_tar_gz'
# is really useful for the main tarball.
debian/control: debian/source/format prepare Makefile d = BUILD + "/download-cache/dist"
$(eval DSC = $(PACKAGE)_$(VERSION).dsc) g = d + "/re6stnet-*"
python2 -c 'from debian.deb822 import Deb822; d = Deb822(); \ map(os.remove, glob(g))
b = open("re6stnet/$@"); s = Deb822(b); b = Deb822(b); \ check_call((os.path.abspath(task.inputs[1]), "setup.py", "sdist",
d["Format"] = open("$<").read().strip(); \ "-d", os.path.abspath(d)), cwd="re6stnet")
d["Source"] = s["Source"] = b["Package"] = "$(PACKAGE)"; \ task.outputs[1] = sdist_version(*glob(g))
d["Version"] = "$(VERSION)"; \ # Touch target because the current directory is used as temporary
d["Architecture"] = b["Architecture"] = "any"; \ # storage, and it is cleaned up after that setup.py runs egg_info.
d["Build-Depends"] = s["Build-Depends"] = \ os.utime(task.outputs[0], None)
"python (>= 2.6), debhelper (>= 8)"; \
b["Depends"] = "$${shlibs:Depends}, iproute2 | iproute"; \ @task(sdist, BUILD + "/.installed.cfg")
b["Conflicts"] = b["Provides"] = b["Replaces"] = "re6stnet"; \ def buildout(task):
open("$@", "w").write("%s\n%s" % (s, b)); \ check_call(("bin/buildout",), cwd=BUILD)
open("$(DSC)", "w").write(str(d))' # Touch target in case that buildout had nothing to do.
os.utime(task.output, None)
debian.tar.gz: $(patsubst %,debian/%,changelog control prerm postinst rules source/*)
# Unfortunately, OBS does not support symlinks. def tarfile_addfileobj(tarobj, name, dataobj, statobj):
set -e; cd re6stnet; [ ! -e debian/postinst ]; \ tarinfo = tarobj.gettarinfo(arcname=name, fileobj=statobj)
x=`find debian ! -type d $(patsubst %,! -path %,$^))`; \ dataobj.seek(0, 2)
tar -chaf ../$@ $$x -C .. $^ tarinfo.size = dataobj.tell()
dataobj.reset()
define SED_SPEC tarobj.addfile(tarinfo, dataobj)
# https://fedoraproject.org/wiki/Packaging:Python_Appendix#Manual_byte_compilation
1i%global __os_install_post %(echo '%{__os_install_post}' |grep -v brp-python-bytecompile) @task(re6stnet)
/^%define (_builddir|ver)/d def upstream(task):
s/^(Name:\s*).*/\1$(PACKAGE)/ check_call(("make", "-C", "re6stnet"))
s/^(Version:\s*).*/\1$(VERSION)/ task.outputs = glob("re6stnet/docs/*.[1-9]")
s/^(Release:\s*).*/\11/
/^BuildArch:/cAutoReqProv: no\nBuildRequires: gcc-c++, make, python\n#!BuildIgnore: rpmlint-Factory\nSource: %{name}_%{version}.tar.gz @task((upstream, buildout, __file__,
/^Requires:/{/iproute/!d} "Makefile.in", "cleanup", "install-eggs", "rebootstrap"))
/^Recommends:/d def tarball(task):
s/^(Conflicts:\s*).*/\1re6stnet/ prefix = "%s-%s/" % (PACKAGE, VERSION)
/^%description$$/a%prep\n%setup -q def xform(path):
/^%preun$$/,/^$$/{/^$$/ifind /$(TARGET) -type f -name '*.py[co]' -delete for p in "re6stnet/", "build/", "":
} if path.startswith(p):
endef return prefix + path[len(p):]
with make_tar_gz(task.output, MTIME, xform) as t:
re6stnet.spec: prepare s = StringIO()
$(eval export SED_SPEC) for k in "BIN", "NOPART", "BUILD_KEEP", "TARGET":
sed -r "$$SED_SPEC" re6stnet/$@ > $@ v = globals()[k]
s.write("%s = %s\n" % (k, v if type(v) is str else " ".join(v)))
PKGBUILD: PKGBUILD.in prepare with open(task.inputs[-4]) as x:
sed 's/%VERSION%/$(VERSION)/' $< > $@ s.write(x.read())
tarfile_addfileobj(t, "Makefile", s, x)
clean: s.truncate(0)
rm -rf "$(ROOT)" *.dsc *.tar.gz re6stnet.spec \ s.write("override PYTHON = /%s/parts/python2.7/bin/python\n" % TARGET)
upstream.mk debian/control debian/changelog with open("re6stnet/Makefile") as x:
s.write(x.read())
tarfile_addfileobj(t, "upstream.mk", s, x)
for x in task.inputs[-3:]:
t.add(x)
t.add("re6stnet/daemon")
for x in upstream.outputs:
t.add(x)
for x in BUILD_KEEP:
t.add(BUILD + "/" + x)
@task(sdist, "debian/changelog")
def dch(task):
with cwd("re6stnet") as p:
p += "/" + task.output
check_output(("make", "-f", "-", p,
"PACKAGE=" + PACKAGE, "VERSION=" + VERSION),
input=open("debian/common.mk").read().replace(task.output, p))
@task((dch, tree("debian")), DIST + "/debian.tar.gz")
def deb(task):
control = open("re6stnet/debian/control")
d = Deb822(); s = Deb822(control); b = Deb822(control)
d["Format"] = open("debian/source/format").read().strip()
d["Source"] = s["Source"] = b["Package"] = PACKAGE
d["Version"] = VERSION
d["Architecture"] = b["Architecture"] = "any"
d["Build-Depends"] = s["Build-Depends"] = \
"python (>= 2.6), debhelper (>= 8)"
b["Depends"] = "${shlibs:Depends}, iproute2 | iproute"
b["Conflicts"] = b["Provides"] = b["Replaces"] = "re6stnet"
patched_control = StringIO(str("%s\n%s" % (s, b))) # BBB: cast to str for Python 2.6
open(task.outputs[1], "w").write(str(d))
date = rfc822.parsedate_tz(Changelog(open(dch.output)).date)
mtime = time.mktime(date[:9]) - date[9]
# Unfortunately, OBS does not support symlinks.
with make_tar_gz(task.outputs[0], mtime, dereference=True) as t:
added = glob("debian/*")
t.add("debian")
x = "debian/control"
tarfile_addfileobj(t, x, patched_control, control)
added.append(x)
with cwd("re6stnet"):
upstream = set(glob("debian/*"))
upstream.difference_update((x, "debian/rules", "debian/source"))
# check we are aware of any upstream file we override
assert upstream.isdisjoint(added), upstream.intersection(added)
map(t.add, sorted(upstream))
@task((sdist, __file__), DIST + "/re6stnet.spec")
def rpm(task):
check_call(("sed", "-r", r"""
# https://fedoraproject.org/wiki/Packaging:Python_Appendix#Manual_byte_compilation
1i%%global __os_install_post %%(echo '%%{__os_install_post}' |grep -v brp-python-bytecompile)
/^%%define (_builddir|ver)/d
s/^(Name:\s*).*/\1%s/
s/^(Version:\s*).*/\1%s/
s/^(Release:\s*).*/\11/
/^BuildArch:/cAutoReqProv: no\
BuildRequires: gcc-c++, make, python\
#!BuildIgnore: rpmlint-Factory\
Source: %%{name}_%%{version}.tar.gz
/^Requires:/{
/iproute/!d
}
/^Recommends:/d
s/^(Conflicts:\s*).*/\1re6stnet/
/^%%description$/a%%prep\n%%setup -q
/^%%preun$/,/^$/{
/^$/ifind /%s -type f -name '*.py[co]' -delete
}
""" % (PACKAGE, VERSION, TARGET), "re6stnet/re6stnet.spec"),
stdout=open(task.output, "w"))
@task((sdist, "PKGBUILD.in"), DIST + "/PKGBUILD")
def arch(task):
pkgbuild = open(task.inputs[-1]).read().replace("%VERSION%", VERSION)
open(task.output, "w").write(pkgbuild)
@task((tarball, deb, rpm, arch, "re6stnet.install"))
def build(task):
pass
@task(build)
def osc(task):
check_call(("osc", "up"), cwd=OSC)
old = set(glob(OSC + "/re6st-node_*"))
for path in build.inputs:
shutil.copy2(path, OSC)
old.discard(OSC + "/" + os.path.basename(path))
for path in old:
os.remove(path)
check_call(("osc", "addremove"), cwd=OSC)
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment