Diff of /trunk/wharfie/wharfie.py [000000] .. [r2]  Maximize  Restore

Switch to side-by-side view

--- a
+++ b/trunk/wharfie/wharfie.py
@@ -0,0 +1,295 @@
+#!/usr/bin/python
+#
+# The file format and the principle are lent from Docker.
+# It's just all a bit simpler, as we only create images
+# and have no infrastructure to provide prebuilt images.
+#
+# What is more complex for embedded systems is the legal
+# part. When s.o. is using docker to build a web service,
+# he doesn't include the used open source software into
+# a product, which he is commercially using, but he uses
+# it himself to provide a web service to his customers.
+#
+# With most embedded systems, which Wharfie is focusing
+# on, this is different. People are usually building
+# products, including the open source software. So they
+# need to respect the licenses of the software, which
+# they are including.
+#
+# For this reason Wharfie only supports debian based
+# systems, as the debian projects takes much care about
+# respecting open source licenses, and makes it easy
+# to get a list of all licenses used in a system.
+#
+# It makes it also easy to get the source packets, fitting
+# to the installed binary version. Therefore, one can
+# easily provide all sources of the open source software,
+# included in his image to his customers.
+#
+# With this background, the supported commands of Wharfie
+# are differing slightly from its big brother Docker.
+#
+# Commands:
+# - FROM: Supports only a fix number of build rules.
+#         e.g.: debian_armhf_sid, debian_etch, ...
+#
+# - RUN: Execute a command inside the image
+#
+# - RUN HOST: Execute a command on the host system,
+#             But inside of the image root.
+#
+# - ADD: Add an archive or file to the root filesystem.
+#
+# - TO: name of the final archive
+#
+# - SOURCE: name of an archive, where the currently installed
+#           source packages are written to.
+#
+# - LICENSE: Extract a list of all licenses, of all currently
+#            installed packages.
+#
+# - TOOLCHAIN: Build a cross-toolchain for the currently
+#              installed root filesystem.
+#
+# Note: Technically everything can be done, using RUN
+#       and RUN HOST. Other commands, like ADD or TO
+#       are only supported for more convenience.
+#       So for example if you want to combine the
+#       resulting image with a special bootloader or
+#       something, it can be flexibly done with RUN HOST.
+
+
+import os
+import sys
+import re
+import binascii
+import argparse
+
+regexCommand = '^[ \t]*(RUN HOST|RUN|FROM|TOOLCHAIN|TO|ADD|SOURCE|LICENSE|ENV)([^\n]*)';
+g_depend = '';
+g_makeTargets = list();
+g_environment = list();
+g_archiveName = 'rootfs.tar';
+g_finalTarget = list();
+# We use templates, which are checking their execution context,
+# to be sure, that target commands are really only executed
+# in the change root of the image.
+g_templateTrgCmd = "#!/bin/bash\n[ -f ../Wharfile ] && exit 2;\nmount -t proc proc /proc\n%s\numount /proc"
+g_templateHostCmd = "#!/bin/bash\n[ ! -f ../Wharfile ] && exit 2;\n%s"
+
+#
+# Read a Wharfile
+#
+def read_wharfile(filename):
+    global g_archiveName
+    content = open(filename, 'r').read()
+    content = content.replace('\\\n', '')
+    bld = ''
+    g_allDepends = '';
+    g_depend = '';
+    for cmd in re.findall(regexCommand, content, flags=re.MULTILINE):
+        # calculate current build target and dependency names
+        dep = list();
+        if bld != '':
+            dep.append(bld);
+        if g_depend != '':
+            dep.append(g_depend);
+
+        if cmd[0] not in ('FROM', 'TO', 'ENV'):
+            g_allDepends += str(cmd);
+            if g_allDepends != '':
+                bld = format(0xFFFFFFFF & binascii.crc32(g_allDepends), '02X') + ".piling.tar";
+
+        # FROM
+        if cmd[0] == 'FROM':
+            g_depend = cmd[1] + ".tar";
+
+        # ENV
+        elif cmd[0] == 'ENV':
+            g_environment.append(cmd[1]);
+
+        # RUN
+        elif cmd[0] == 'RUN':
+            makeTarget = {
+                'comment' : "%s %s" % (cmd[0], cmd[1]),
+                'name': bld,
+                'dep': dep,
+                'trgcmd': cmd[1]
+                };
+            g_makeTargets.append(makeTarget);
+            g_depend = '';
+
+        # RUN HOST
+        elif cmd[0] == 'RUN HOST':
+            g_allDepends += str(cmd);
+        
+            makeTarget = {
+                'comment' : "%s %s" % (cmd[0], cmd[1]),
+                'name': bld,
+                'dep': dep,
+                'hostcmd': cmd[1]
+                };
+            g_makeTargets.append(makeTarget);
+            g_depend = '';
+
+        # ADD (single file)
+        elif cmd[0] == 'ADD':
+            g_allDepends += str(cmd);
+            src,dst = cmd[1].lstrip().split(" ")
+            dep.append(src)
+            myHostCmd = 'dest="$(pwd)"; cd ../; mkdir -p $(dirname "${dest}/%s"); cp -R --preserve=mode %s "${dest}/%s"' % (dst, src, dst)
+        
+            makeTarget = {
+                'comment' : "%s %s" % (cmd[0], cmd[1]),
+                'name': bld,
+                'dep': dep,
+                'hostcmd': myHostCmd
+                };
+            g_makeTargets.append(makeTarget);
+            g_depend = '';
+
+        # TO
+        elif cmd[0] == 'TO':
+            g_archiveName = cmd[1].lstrip();
+
+        # SOURCE
+        elif cmd[0] == 'SOURCE':
+            myTrgCmd = 'mkdir /sources; cd /sources; [ ! -f .pkg.list ] && dpkg-query -f \'${binary:Package}\\n\' -W > .pkg.list; apt-get install -y dpkg-dev; cat .pkg.list | xargs -n 1 apt-get source'
+            myHostCmd = 'tar -cf ../' + cmd[1].lstrip() + ' sources;'
+            makeTarget = {
+                'comment' : "%s %s" % (cmd[0], cmd[1]),
+                'name': bld,
+                'dep': dep,
+                'trgcmd': myTrgCmd,
+                'hostcmd': myHostCmd,
+                'temporary': True
+                };
+            g_makeTargets.append(makeTarget);
+            g_depend = '';
+
+        # LICENSE
+        elif cmd[0] == 'LICENSE':
+            myTrgCmd = 'mkdir /licenses; for i in /usr/share/doc/*/; do [ -f $i/copyright ] && cp $i/copyright licenses/$(basename $i).txt || echo $(basename $i) >> licenses/___MISSING___.txt; done'
+            myHostCmd = 'tar -cf ../' + cmd[1].lstrip() + ' licenses;'
+            makeTarget = {
+                'comment' : "%s %s" % (cmd[0], cmd[1]),
+                'name': bld,
+                'dep': dep,
+                'trgcmd': myTrgCmd,
+                'hostcmd': myHostCmd,
+                'temporary': True
+                };
+            g_makeTargets.append(makeTarget);
+            g_depend = '';
+
+        # TOOLCHAIN
+        elif cmd[0] == 'TOOLCHAIN':
+            myTrgCmd = 'apt-get install -y libc6-dev; cd ./usr/lib/arm-linux-gnueabi*/; ln -s crt1.o crt0.o'
+            myHostCmd = 'rm .tmp.sh; mkdir target; mv ./* target; mkdir host; (cd host; tar -xf ../../debian_toolchain.tar); cp ../debian_toolchain_env.sh env.sh; tar -cf ../' + cmd[1].lstrip() + ' .;'
+            dep.append('debian_toolchain.tar')
+            dep.append('debian_toolchain_env.sh')
+            makeTarget = {
+                'comment' : "%s %s" % (cmd[0], cmd[1]),
+                'name': bld,
+                'dep': dep,
+                'trgcmd': myTrgCmd,
+                'hostcmd': myHostCmd,
+                'temporary': True
+                };
+            g_makeTargets.append(makeTarget);
+            g_depend = '';
+
+        else:
+            print ('unknown command: ' + cmd[0] + ' ' + cmd[1]);
+
+    g_finalTarget.append(bld);
+
+
+#
+# Write a Makefile
+#
+def write_makefile(filename, dry_run, installpath='.'):
+    f = open(filename, 'w');
+    # write header
+    f.write("ifneq (VERBOSE,y)\n")
+    f.write("Q=@\n")
+    f.write("endif\n")
+    f.write("%s: %s\n" % (g_archiveName, "".join(g_finalTarget)));
+    f.write("\tcp $< $@\n");
+    f.write("\n");
+
+    
+    # write all environment variables
+    for env in g_environment:
+        key,val = env.lstrip().split(" ")
+        f.write("export %s=%s\n" % (key, val));
+
+    # write all targets
+    for target in g_makeTargets:
+        if 'comment' in target:
+            f.write("# %s\n" % target['comment']);
+        f.write("%s: %s\n" % (target['name'], " ".join(target['dep'])));
+        f.write("\t${Q}-mkdir $$(basename $@ .tar)\n");
+
+        if 'trgcmd' in target:
+            cmd = g_templateTrgCmd % (target['trgcmd'].replace('$', '\\$$').replace('"', '\\"'));
+            f.write("\t${Q}(cd $$(basename $@ .tar); ${SUDO} tar -xf ../$<;)\n");
+            f.write("\t${Q}echo '******************************'\n");
+            f.write("\t${Q}echo '%s'\n" % target['comment']);
+            f.write("\t${Q}echo '******************************'\n");
+            f.write("\t${Q}(echo -e \"%s\") | ${SUDO} tee ./$$(basename $@ .tar)/.tmp.sh\n" % cmd.replace('\\n', '\\\\\\n').replace('\n', '\\n').replace('!', '"\'!\'"'));
+            f.write("\t${Q}${SUDO} chmod a+x ./$$(basename $@ .tar)/.tmp.sh\n");
+            if not dry_run:
+                f.write("\t${Q}(cd $$(basename $@ .tar); ${SUDO} chroot . ./.tmp.sh)\n");
+            else:
+                f.write("\t${Q}echo 'Skipped the following script:'; cat $$(basename $@ .tar)/.tmp.sh");
+            f.write("\t${Q}-${SUDO} rm ./$$(basename $@ .tar)/.tmp.sh\n");
+
+        if 'hostcmd' in target:
+            cmd = g_templateHostCmd % (target['hostcmd'].replace('$', '\\$$').replace('"', '\\"'));
+            f.write("\t${Q}(cd $$(basename $@ .tar); ${SUDO} tar -xf ../$<;)\n");
+            f.write("\t${Q}(echo -e \"%s\") | ${SUDO} tee ./$$(basename $@ .tar)/.tmp.sh\n" % cmd.replace('\\n', '\\\\\\\\n').replace('\n', '\\n').replace('!', '"\'!\'"'));
+            f.write("\t${Q}${SUDO} chmod a+x ./$$(basename $@ .tar)/.tmp.sh\n");
+            if not dry_run:
+                f.write("\t${Q}(cd $$(basename $@ .tar); ${SUDO} ./.tmp.sh)\n");
+            else:
+                f.write("\t${Q}echo 'Skipped the following script:'; cat $$(basename $@ .tar)/.tmp.sh");
+            f.write("\t${Q}-${SUDO} rm ./$$(basename $@ .tar)/.tmp.sh\n");
+
+
+        if 'temporary' in target and target['temporary']:
+            f.write("\t${Q}cp $< $@\n");
+        else:
+            f.write("\t${Q}cd $$(basename $@ .tar); ${SUDO} tar -cf '../$@' .\n");
+        f.write("\t${Q}-${SUDO} rm -R ./$$(basename $@ .tar)\n");
+        f.write("\n");
+
+    # write footer
+    f.write("include %s/wharfie.mk\n" % installpath);
+    if installpath != '.':
+        f.write("-include wharfie.mk\n");
+    f.write("\n");
+
+        
+#
+# Main
+#
+def main():
+    parser = argparse.ArgumentParser()
+    parser.add_argument('--gen-only', action='store_true', help="Generate makefile only, but don't build it" )
+    parser.add_argument('--dry-run', action='store_true', help="Generate makefile with disabled run actions and don't build it" )
+    parser.add_argument('wharfile', default='Wharfile', nargs='?', help="Filename of a 'Wharfile'. By default ./Wharfile is used." )
+    args = parser.parse_args()
+
+    read_wharfile('Wharfile');
+    if os.path.isfile(os.path.abspath(os.path.dirname(sys.argv[0])) + "/wharfie.mk"):
+        write_makefile('Makefile', args.dry_run);
+    else:
+        write_makefile('Makefile', args.dry_run, os.path.abspath(os.path.dirname(sys.argv[0])) + "/../share/wharfie");
+
+    if not args.gen_only and not args.dry_run:
+        os.system("make");
+
+        
+if __name__ == "__main__":
+    main()