add doit.sh
[openwrt/staging/blogic.git] / gentree.py
index 2cfdfa8b00c4cadfe95c2d23b0acef4879e59bd6..816674b3193f4fb0f9a668c1d045668bcd11aa4a 100755 (executable)
@@ -4,13 +4,52 @@
 #
 
 import argparse, sys, os, errno, shutil, re, subprocess
+import tarfile, gzip
 
 # find self
-source_dir = os.path.abspath(os.path.dirname(sys.argv[0]))
-sys.path.append(os.path.join(source_dir, 'lib'))
+source_dir = os.path.abspath(os.path.dirname(__file__))
+sys.path.append(source_dir)
 # and import libraries we have
-import kconfig, git, patch, make
+from lib import kconfig, patch, make
+from lib import bpgit as git
+from lib import bpgpg as gpg
+from lib import bpkup as kup
+from lib.tempdir import tempdir
+from lib import bpreqs as reqs
+from lib import bpversion as gen_version
 
+class Bp_Identity(object):
+    """
+    folks considering multiple integrations may want to
+    consider stuffing versioning info here as well but
+    that will need thought/design on sharing compat and
+    module namespaces.
+
+    Use the *_resafe when combining on regexps, although we currently
+    don't support regexps there perhaps later we will and this will
+    just make things safer for the output regardless. Once and if those
+    are added, how we actually use the others for regular printing will
+    need to be considered.
+    """
+    def __init__(self, integrate=False, kconfig_prefix='CPTCFG_',
+                 project_prefix='', project_dir='',
+                 target_dir='', target_dir_name='',
+                 kconfig_source_var=None):
+        self.integrate = integrate
+        self.kconfig_prefix = kconfig_prefix
+        self.kconfig_prefix_resafe = re.escape(kconfig_prefix)
+        self.project_prefix = project_prefix
+        self.project_prefix_resafe = re.escape(project_prefix)
+        self.full_prefix = kconfig_prefix + project_prefix
+        self.full_prefix_resafe = re.escape(self.full_prefix)
+        self.project_dir = project_dir
+        self.target_dir = target_dir
+        self.target_dir_name = target_dir_name
+        self.kconfig_source_var = kconfig_source_var
+        if self.kconfig_source_var:
+            self.kconfig_source_var_resafe = re.escape(self.kconfig_source_var)
+        else:
+            self.kconfig_source_var_resafe = None
 
 def read_copy_list(copyfile):
     """
@@ -41,20 +80,35 @@ def read_dependencies(depfilename):
     """
     Read a (the) dependency file and return the list of
     dependencies as a dictionary, mapping a Kconfig symbol
-    to a list of kernel version dependencies. While reading
-    ignore blank/commented lines.
+    to a list of kernel version dependencies.
+    
+    If a backported feature that an upstream backported driver
+    depends on had kconfig limitations (ie, debugging feature not
+    available) a built constaint restriction can be expressed
+    by using a kconfig expression. The kconfig expressions can
+    be specified by using the "kconfig: " prefix.
+    
+    While reading ignore blank or commented lines.
     """
     ret = {}
     depfile = open(depfilename, 'r')
     for item in depfile:
+        kconfig_exp = ""
         item = item.strip()
         if not item or item[0] == '#':
             continue
-        sym, dep = item.split()
-        if not sym in ret:
-            ret[sym] = [dep, ]
+        if "kconfig:" in item:
+            sym, kconfig_exp = item.split(" ", 1)
+            if not sym in ret:
+                ret[sym] = [kconfig_exp, ]
+            else:
+                ret[sym].append(kconfig_exp)
         else:
-            ret[sym].append(dep)
+            sym, dep = item.split()
+            if not sym in ret:
+                ret[sym] = [dep, ]
+            else:
+                ret[sym].append(dep)
     return ret
 
 
@@ -69,7 +123,7 @@ def check_output_dir(d, clean):
         shutil.rmtree(d, ignore_errors=True)
     try:
         os.rmdir(d)
-    except OSError, e:
+    except OSError as e:
         if e.errno != errno.ENOENT:
             raise
 
@@ -137,10 +191,7 @@ def copy_files(srcpath, copy_list, outdir):
             def copy_ignore(dir, entries):
                 r = []
                 for i in entries:
-                    if (not i[-1] in ('c', 'h') and
-                        i[-4:] != '.awk' and
-                        not i in ('Kconfig', 'Makefile') and
-                        not os.path.isdir(os.path.join(dir, i))):
+                    if i[-2:] == '.o' or i[-1] == '~':
                         r.append(i)
                 return r
             copytree(os.path.join(srcpath, srcitem),
@@ -149,7 +200,7 @@ def copy_files(srcpath, copy_list, outdir):
         else:
             try:
                 os.makedirs(os.path.join(outdir, os.path.dirname(tgtitem)))
-            except OSError, e:
+            except OSError as e:
                 # ignore dirs we might have created just now
                 if e.errno != errno.EEXIST:
                     raise
@@ -165,7 +216,7 @@ def copy_git_files(srcpath, copy_list, rev, outdir):
     for srcitem, tgtitem in copy_list:
         for m, t, h, f in git.ls_tree(rev=rev, files=(srcitem,), tree=srcpath):
             assert t == 'blob'
-            f = os.path.join(outdir, f)
+            f = os.path.join(outdir, f.replace(srcitem, tgtitem))
             d = os.path.dirname(f)
             if not os.path.exists(d):
                 os.makedirs(d)
@@ -174,6 +225,70 @@ def copy_git_files(srcpath, copy_list, rev, outdir):
             outf.close()
             os.chmod(f, int(m, 8))
 
+def automatic_backport_mangle_c_file(name):
+    return name.replace('/', '-')
+
+
+def add_automatic_backports(args):
+    disable_list = []
+    export = re.compile(r'^EXPORT_SYMBOL(_GPL)?\((?P<sym>[^\)]*)\)')
+    bpi = kconfig.get_backport_info(os.path.join(args.bpid.target_dir, 'compat', 'Kconfig'))
+    configtree = kconfig.ConfigTree(os.path.join(args.bpid.target_dir, 'Kconfig'), args.bpid)
+    ignore=['Kconfig.kernel', 'Kconfig.versions']
+    configtree.verify_sources(ignore=ignore)
+    git_debug_snapshot(args, "verify sources for automatic backports")
+    all_selects = configtree.all_selects()
+    for sym, vals in bpi.items():
+        if sym.startswith('BPAUTO_BUILD_'):
+            if not sym[13:] in all_selects:
+                disable_list.append(sym)
+                continue
+        symtype, module_name, c_files, h_files = vals
+
+        # first copy files
+        files = []
+        for f in c_files:
+            files.append((f, os.path.join('compat', automatic_backport_mangle_c_file(f))))
+        for f in h_files:
+            files.append((os.path.join('include', f),
+                          os.path.join('include', os.path.dirname(f), 'backport-' + os.path.basename(f))))
+        if args.git_revision:
+            copy_git_files(args.kerneldir, files, args.git_revision, args.bpid.target_dir)
+        else:
+            copy_files(args.kerneldir, files, args.bpid.target_dir)
+
+        # now add the Makefile line
+        mf = open(os.path.join(args.bpid.target_dir, 'compat', 'Makefile'), 'a+')
+        o_files = [automatic_backport_mangle_c_file(f)[:-1] + 'o' for f in c_files]
+        if symtype == 'tristate':
+            if not module_name:
+                raise Exception('backporting a module requires a #module-name')
+            for of in o_files:
+                mf.write('%s-objs += %s\n' % (module_name, of))
+            mf.write('obj-$(%s%s) += %s.o\n' % (args.bpid.full_prefix, sym, module_name))
+        elif symtype == 'bool':
+            mf.write('compat-$(%s%s) += %s\n' % (args.bpid.full_prefix, sym, ' '.join(o_files)))
+
+        # finally create the include file
+        syms = []
+        for f in c_files:
+            for l in open(os.path.join(args.bpid.target_dir, 'compat',
+                                       automatic_backport_mangle_c_file(f)), 'r'):
+                m = export.match(l)
+                if m:
+                    syms.append(m.group('sym'))
+        for f in h_files:
+            outf = open(os.path.join(args.bpid.target_dir, 'include', f), 'w')
+            outf.write('/* Automatically created during backport process */\n')
+            outf.write('#ifndef %s%s\n' % (args.bpid.full_prefix, sym))
+            outf.write('#include_next <%s>\n' % f)
+            outf.write('#else\n');
+            for s in syms:
+                outf.write('#undef %s\n' % s)
+                outf.write('#define %s LINUX_BACKPORT(%s)\n' % (s, s))
+            outf.write('#include <%s>\n' % (os.path.dirname(f) + '/backport-' + os.path.basename(f), ))
+            outf.write('#endif /* %s%s */\n' % (args.bpid.full_prefix, sym))
+    return disable_list
 
 def git_debug_init(args):
     """
@@ -183,8 +298,11 @@ def git_debug_init(args):
     """
     if not args.gitdebug:
         return
-    git.init(tree=args.outdir)
-    git.commit_all("Copied code", tree=args.outdir)
+    # Git supports re-initialization, although not well documented it can
+    # reset config stuff, lets avoid that if the tree already exists.
+    if not os.path.exists(os.path.join(args.bpid.project_dir, '.git')):
+        git.init(tree=args.bpid.project_dir)
+    git.commit_all("Copied backport", tree=args.bpid.project_dir)
 
 
 def git_debug_snapshot(args, name):
@@ -193,10 +311,325 @@ def git_debug_snapshot(args, name):
     """
     if not args.gitdebug:
         return
-    git.commit_all(name, tree=args.outdir)
+    git.commit_all(name, tree=args.bpid.project_dir)
+
+def get_rel_spec_stable(rel):
+    """
+    Returns release specs for a linux-stable backports based release.
+    """
+    if ("rc" in rel):
+        m = re.match(r"(?P<VERSION>\d+)\.+" \
+                     "(?P<PATCHLEVEL>\d+)[.]*" \
+                     "(?P<SUBLEVEL>\d*)" \
+                     "[-rc]+(?P<RC_VERSION>\d+)\-+" \
+                     "(?P<RELMOD_UPDATE>\d+)[-]*" \
+                     "(?P<RELMOD_TYPE>[usnpc]*)", \
+                     rel)
+    else:
+        m = re.match(r"(?P<VERSION>\d+)\.+" \
+                     "(?P<PATCHLEVEL>\d+)[.]*" \
+                     "(?P<SUBLEVEL>\d*)\-+" \
+                     "(?P<RELMOD_UPDATE>\d+)[-]*" \
+                     "(?P<RELMOD_TYPE>[usnpc]*)", \
+                     rel)
+    if (not m):
+        return m
+    return m.groupdict()
+
+def get_rel_spec_next(rel):
+    """
+    Returns release specs for a linux-next backports based release.
+    """
+    m = re.match(r"(?P<DATE_VERSION>\d+)[-]*" \
+                 "(?P<RELMOD_UPDATE>\d*)[-]*" \
+                 "(?P<RELMOD_TYPE>[usnpc]*)", \
+                 rel)
+    if (not m):
+        return m
+    return m.groupdict()
+
+def get_rel_prep(rel):
+    """
+    Returns a dict with prep work details we need prior to
+    uploading a backports release to kernel.org
+    """
+    rel_specs = get_rel_spec_stable(rel)
+    is_stable = True
+    rel_tag = ""
+    paths = list()
+    if (not rel_specs):
+        rel_specs = get_rel_spec_next(rel)
+        if (not rel_specs):
+            sys.stdout.write("rel: %s\n" % rel)
+            return None
+        if (rel_specs['RELMOD_UPDATE'] == '0' or
+            rel_specs['RELMOD_UPDATE'] == '1'):
+            return None
+        is_stable = False
+        date = rel_specs['DATE_VERSION']
+        year = date[0:4]
+        if (len(year) != 4):
+            return None
+        month = date[4:6]
+        if (len(month) != 2):
+            return None
+        day = date[6:8]
+        if (len(day) != 2):
+            return None
+        paths.append(year)
+        paths.append(month)
+        paths.append(day)
+        rel_tag = "backports-" + rel.replace(rel_specs['RELMOD_TYPE'], "")
+    else:
+        ignore = "-"
+        if (not rel_specs['RELMOD_UPDATE']):
+            return None
+        if (rel_specs['RELMOD_UPDATE'] == '0'):
+            return None
+        ignore += rel_specs['RELMOD_UPDATE']
+        if (rel_specs['RELMOD_TYPE'] != ''):
+            ignore += rel_specs['RELMOD_TYPE']
+        base_rel = rel.replace(ignore, "")
+        paths.append("v" + base_rel)
+        rel_tag = "v" + rel.replace(rel_specs['RELMOD_TYPE'], "")
+
+    rel_prep = dict(stable = is_stable,
+                    expected_tag = rel_tag,
+                    paths_to_create = paths)
+    return rel_prep
+
+def create_tar_and_gz(tar_name, dir_to_tar):
+    """
+    We need both a tar file and gzip for kernel.org, the tar file
+    gets signed, then we upload the compressed version, kup-server
+    in the backend decompresses and verifies the tarball against
+    our signature.
+    """
+    basename = os.path.basename(dir_to_tar)
+    tar = tarfile.open(tar_name, "w")
+    tar.add(dir_to_tar, basename)
+    tar.close()
+
+    tar_file = open(tar_name, "r")
+
+    gz_file = gzip.GzipFile(tar_name + ".gz", 'wb')
+    gz_file.write(tar_file.read())
+    gz_file.close()
+
+def upload_release(args, rel_prep, logwrite=lambda x:None):
+    """
+    Given a path of a relase make tarball out of it, PGP sign it, and
+    then upload it to kernel.org using kup.
+
+    The linux-next based release do not require a RELMOD_UPDATE
+    given that typically only one release is made per day. Using
+    RELMOD_UPDATE for these releases is allowed though and if
+    present it must be > 1.
+
+    The linux-stable based releases require a RELMOD_UPDATE.
+
+    RELMOD_UPDATE must be numeric and > 0 just as the RC releases
+    of the Linux kernel.
+
+    The tree must also be tagged with the respective release, without
+    the RELMOD_TYPE. For linux-next based releases this consists of
+    backports- followed by DATE_VERSION and if RELMOD_TYPE is present.
+    For linux-stable releases this consists of v followed by the
+    full release version except the RELMOD_TYPE.
+
+    Uploads will not be allowed if these rules are not followed.
+    """
+    korg_path = "/pub/linux/kernel/projects/backports"
+
+    if (rel_prep['stable']):
+        korg_path += "/stable"
+
+    parent = os.path.dirname(args.bpid.project_dir)
+    release = os.path.basename(args.bpid.project_dir)
+    tar_name = parent + '/' + release + ".tar"
+    gzip_name = tar_name + ".gz"
+
+    create_tar_and_gz(tar_name, args.bpid.project_dir)
+
+    logwrite(gpg.sign(tar_name, extra_args=['--armor', '--detach-sign']))
+
+    logwrite("------------------------------------------------------")
+
+    if (not args.kup_test):
+        logwrite("About to upload, current target path contents:")
+    else:
+        logwrite("kup-test: current target path contents:")
+
+    logwrite(kup.ls(path=korg_path))
+
+    for path in rel_prep['paths_to_create']:
+        korg_path += '/' + path
+        if (not args.kup_test):
+            logwrite("create directory: %s" % korg_path)
+            logwrite(kup.mkdir(korg_path))
+    korg_path += '/'
+    if (not args.kup_test):
+        logwrite("upload file %s to %s" % (gzip_name, korg_path))
+        logwrite(kup.put(gzip_name, tar_name + '.asc', korg_path))
+        logwrite("\nFinished upload!\n")
+        logwrite("Target path contents:")
+        logwrite(kup.ls(path=korg_path))
+    else:
+        kup_cmd = "kup put /\n\t\t%s /\n\t\t%s /\n\t\t%s" % (gzip_name, tar_name + '.asc', korg_path)
+        logwrite("kup-test: skipping cmd: %s" % kup_cmd)
+
+def apply_patches(args, desc, source_dir, patch_src, target_dir, logwrite=lambda x:None):
+    """
+    Given a path of a directories of patches and SmPL patches apply
+    them on the target directory. If requested refresh patches, or test
+    a specific SmPL patch.
+    """
+    logwrite('Applying patches from %s to %s ...' % (patch_src, target_dir))
+    test_cocci = args.test_cocci or args.profile_cocci
+    test_cocci_found = False
+    patches = []
+    sempatches = []
+    for root, dirs, files in os.walk(os.path.join(source_dir, patch_src)):
+        for f in files:
+            if not test_cocci and f.endswith('.patch'):
+                patches.append(os.path.join(root, f))
+            if f.endswith('.cocci'):
+                if test_cocci:
+                    if f not in test_cocci:
+                        continue
+                    test_cocci_found = True
+                    if args.test_cocci:
+                        logwrite("Testing Coccinelle SmPL patch: %s" % test_cocci)
+                    elif args.profile_cocci:
+                        logwrite("Profiling Coccinelle SmPL patch: %s" % test_cocci)
+                sempatches.append(os.path.join(root, f))
+    patches.sort()
+    prefix_len = len(os.path.join(source_dir, patch_src)) + 1
+    for pfile in patches:
+        print_name = pfile[prefix_len:]
+        # read the patch file
+        p = patch.fromfile(pfile)
+        # complain if it's not a patch
+        if not p:
+            raise Exception('No patch content found in %s' % print_name)
+        # leading / seems to be stripped?
+        if 'dev/null' in p.items[0].source:
+            raise Exception('Patches creating files are not supported (in %s)' % print_name)
+        # check if the first file the patch touches exists, if so
+        # assume the patch needs to be applied -- otherwise continue
+        patched_file = '/'.join(p.items[0].source.split('/')[1:])
+        fullfn = os.path.join(target_dir, patched_file)
+        if not os.path.exists(fullfn):
+            if args.verbose:
+                logwrite("Not applying %s, not needed" % print_name)
+            continue
+        if args.verbose:
+            logwrite("Applying patch %s" % print_name)
+
+        if args.refresh:
+            # but for refresh, of course look at all files the patch touches
+            for patchitem in p.items:
+                patched_file = '/'.join(patchitem.source.split('/')[1:])
+                fullfn = os.path.join(target_dir, patched_file)
+                shutil.copyfile(fullfn, fullfn + '.orig_file')
+
+        process = subprocess.Popen(['patch', '-p1'], stdout=subprocess.PIPE,
+                                   stderr=subprocess.STDOUT, stdin=subprocess.PIPE,
+                                   close_fds=True, universal_newlines=True,
+                                   cwd=target_dir)
+        output = process.communicate(input=open(pfile, 'r').read())[0]
+        output = output.split('\n')
+        if output[-1] == '':
+            output = output[:-1]
+        if args.verbose:
+            for line in output:
+                logwrite('> %s' % line)
+        if process.returncode != 0:
+            if not args.verbose:
+                logwrite("Failed to apply changes from %s" % print_name)
+                for line in output:
+                    logwrite('> %s' % line)
+            return 2
+
+        if args.refresh:
+            pfilef = open(pfile + '.tmp', 'a')
+            pfilef.write(p.top_header)
+            pfilef.flush()
+            for patchitem in p.items:
+                patched_file = '/'.join(patchitem.source.split('/')[1:])
+                fullfn = os.path.join(target_dir, patched_file)
+                process = subprocess.Popen(['diff', '-p', '-u', patched_file + '.orig_file', patched_file,
+                                            '--label', 'a/' + patched_file,
+                                            '--label', 'b/' + patched_file],
+                                           stdout=pfilef, close_fds=True,
+                                           universal_newlines=True, cwd=target_dir)
+                process.wait()
+                os.unlink(fullfn + '.orig_file')
+                if not process.returncode in (0, 1):
+                    logwrite("Failed to diff to refresh %s" % print_name)
+                    pfilef.close()
+                    os.unlink(pfile + '.tmp')
+                    return 2
+            pfilef.close()
+            os.rename(pfile + '.tmp', pfile)
+
+        # remove orig/rej files that patch sometimes creates
+        for root, dirs, files in os.walk(target_dir):
+            for f in files:
+                if f[-5:] == '.orig' or f[-4:] == '.rej':
+                    os.unlink(os.path.join(root, f))
+        git_debug_snapshot(args, "apply %s patch %s" % (desc, print_name))
+
+    sempatches.sort()
+    prefix_len = len(os.path.join(source_dir, patch_src)) + 1
+
+    for cocci_file in sempatches:
+        # Until Coccinelle picks this up
+        pycocci = os.path.join(source_dir, 'devel/pycocci')
+        cmd = [pycocci, cocci_file]
+        extra_spatch_args = []
+        if args.profile_cocci:
+            cmd.append('--profile-cocci')
+        cmd.append(os.path.abspath(target_dir))
+        print_name = cocci_file[prefix_len:]
+        if args.verbose:
+            logwrite("Applying SmPL patch %s" % print_name)
+            logwrite(" %s" % cmd)
+        sprocess = subprocess.Popen(cmd,
+                                    stdout=subprocess.PIPE, stderr=subprocess.STDOUT,
+                                    close_fds=True, universal_newlines=True,
+                                    cwd=target_dir)
+        output = sprocess.communicate()[0]
+        sprocess.wait()
+        if sprocess.returncode != 0:
+            logwrite("Failed to process SmPL patch %s with %i" % (print_name, sprocess.returncode))
+            return 2
+        output = output.split('\n')
+        if output[-1] == '':
+            output = output[:-1]
+        if args.verbose:
+            for line in output:
+                logwrite('> %s' % line)
+
+        # remove cocci_backup files
+        for root, dirs, files in os.walk(target_dir):
+            for f in files:
+                if f.endswith('.cocci_backup'):
+                    os.unlink(os.path.join(root, f))
+        git_debug_snapshot(args, "apply %s SmPL patch %s" % (desc, print_name))
 
+    if test_cocci and test_cocci_found:
+        logwrite('Done!')
+        sys.exit(0)
+
+def _main():
+    # Our binary requirements go here
+    req = reqs.Req()
+    req.require('git')
+    req.coccinelle('1.0.6')
+    if not req.reqs_match():
+        sys.exit(1)
 
-def main():
     # set up and parse arguments
     parser = argparse.ArgumentParser(description='generate backport tree')
     parser.add_argument('kerneldir', metavar='<kernel tree>', type=str,
@@ -212,187 +645,400 @@ def main():
                              'and we use git ls-tree to get the files.')
     parser.add_argument('--clean', const=True, default=False, action="store_const",
                         help='Clean output directory instead of erroring if it isn\'t empty')
+    parser.add_argument('--integrate', const=True, default=False, action="store_const",
+                        help='Integrate a future backported kernel solution into ' +
+                             'an older kernel tree source directory.')
     parser.add_argument('--refresh', const=True, default=False, action="store_const",
                         help='Refresh patches as they are applied, the source dir will be modified!')
     parser.add_argument('--base-name', metavar='<name>', type=str, default='Linux',
                         help='name of base tree, default just "Linux"')
-    parser.add_argument('--gitdebug', const=True, default=False, action="store_const",
+    parser.add_argument('--gitdebug', '--git-debug', const=True, default=False, action="store_const",
                         help='Use git, in the output tree, to debug the various transformation steps ' +
                              'that the tree generation makes (apply patches, ...)')
     parser.add_argument('--verbose', const=True, default=False, action="store_const",
                         help='Print more verbose information')
     parser.add_argument('--extra-driver', nargs=2, metavar=('<source dir>', '<copy-list>'), type=str,
                         action='append', default=[], help='Extra driver directory/copy-list.')
+    parser.add_argument('--kup', const=True, default=False, action="store_const",
+                        help='For maintainers: upload a release to kernel.org')
+    parser.add_argument('--kup-test', const=True, default=False, action="store_const",
+                        help='For maintainers: do all the work as if you were about to ' +
+                             'upload to kernel.org but do not do the final `kup put` ' +
+                             'and also do not run any `kup mkdir` commands. This will ' +
+                             'however run `kup ls` on the target paths so ' +
+                             'at the very least we test your kup configuration. ' +
+                             'If this is your first time uploading use this first!')
+    parser.add_argument('--test-cocci', metavar='<sp_file>', type=str, default=None,
+                        help='Only use the cocci file passed for Coccinelle, don\'t do anything else, ' +
+                             'also creates a git repo on the target directory for easy inspection ' +
+                             'of changes done by Coccinelle.')
+    parser.add_argument('--profile-cocci', metavar='<sp_file>', type=str, default=None,
+                        help='Only use the cocci file passed and pass --profile  to Coccinelle, ' +
+                             'also creates a git repo on the target directory for easy inspection ' +
+                             'of changes done by Coccinelle.')
     args = parser.parse_args()
 
+    # When building a package we use CPTCFG as we can rely on the
+    # fact that kconfig treats CONFIG_ as an environment variable
+    # requring less changes on code. For kernel integration we use
+    # the longer CONFIG_BACKPORT given that we'll be sticking to
+    # the kernel symbol namespace, to address that we do a final
+    # search / replace. Technically its possible to rely on the
+    # same prefix for packaging as with kernel integration but
+    # there are already some users of the CPTCFG prefix.
+    bpid = None
+    if args.integrate:
+        bpid = Bp_Identity(integrate = args.integrate,
+                           kconfig_prefix = 'CONFIG_',
+                           project_prefix = 'BACKPORT_',
+                           project_dir = args.outdir,
+                           target_dir = os.path.join(args.outdir, 'backports/'),
+                           target_dir_name = 'backports/',
+                           kconfig_source_var = '$BACKPORT_DIR',
+                           )
+    else:
+        bpid = Bp_Identity(integrate = args.integrate,
+                           kconfig_prefix = 'CPTCFG_',
+                           project_prefix = '',
+                           project_dir = args.outdir,
+                           target_dir = args.outdir,
+                           target_dir_name = '',
+                           kconfig_source_var = '$BACKPORT_DIR',
+                           )
+
+    def logwrite(msg):
+        sys.stdout.write(msg)
+        sys.stdout.write('\n')
+        sys.stdout.flush()
+
+    return process(args.kerneldir, args.copy_list,
+                   git_revision=args.git_revision,
+                   bpid=bpid,
+                   clean=args.clean,
+                   refresh=args.refresh, base_name=args.base_name,
+                   gitdebug=args.gitdebug, verbose=args.verbose,
+                   extra_driver=args.extra_driver,
+                   kup=args.kup,
+                   kup_test=args.kup_test,
+                   test_cocci=args.test_cocci,
+                   profile_cocci=args.profile_cocci,
+                   logwrite=logwrite)
+
+def process(kerneldir, copy_list_file, git_revision=None,
+            bpid=None,
+            clean=False, refresh=False, base_name="Linux", gitdebug=False,
+            verbose=False, extra_driver=[], kup=False,
+            kup_test=False,
+            test_cocci=None,
+            profile_cocci=None,
+            logwrite=lambda x:None,
+            git_tracked_version=False):
+    class Args(object):
+        def __init__(self, kerneldir, copy_list_file,
+                     git_revision, bpid, clean, refresh, base_name,
+                     gitdebug, verbose, extra_driver, kup,
+                     kup_test,
+                     test_cocci,
+                     profile_cocci):
+            self.kerneldir = kerneldir
+            self.copy_list = copy_list_file
+            self.git_revision = git_revision
+            self.bpid = bpid
+            self.clean = clean
+            self.refresh = refresh
+            self.base_name = base_name
+            self.gitdebug = gitdebug
+            self.verbose = verbose
+            self.extra_driver = extra_driver
+            self.kup = kup
+            self.kup_test = kup_test
+            self.test_cocci = test_cocci
+            self.profile_cocci = profile_cocci
+            if self.test_cocci or self.profile_cocci:
+                self.gitdebug = True
+    def git_paranoia(tree=None, logwrite=lambda x:None):
+        data = git.paranoia(tree)
+        if (data['r'] != 0):
+            logwrite('Cannot use %s' % tree)
+            logwrite('%s' % data['output'])
+            sys.exit(data['r'])
+        else:
+            logwrite('Validated tree: %s' % tree)
+
+    args = Args(kerneldir, copy_list_file,
+                git_revision, bpid, clean, refresh, base_name,
+                gitdebug, verbose, extra_driver, kup, kup_test,
+                test_cocci, profile_cocci)
+    rel_prep = None
+
+    if bpid.integrate:
+        if args.kup_test or args.test_cocci or args.profile_cocci or args.refresh:
+            logwrite('Cannot use integration with:\n\tkup_test\n\ttest_cocci\n\tprofile_cocci\n\trefresh\n');
+            sys.exit(1)
+
     # start processing ...
+    if (args.kup or args.kup_test):
+        git_paranoia(source_dir, logwrite)
+        git_paranoia(kerneldir, logwrite)
+
+        rel_describe = git.describe(rev=None, tree=source_dir, extra_args=['--dirty'])
+        release = os.path.basename(bpid.target_dir)
+        version = release.replace("backports-", "")
+
+        rel_prep = get_rel_prep(version)
+        if (not rel_prep):
+            logwrite('Invalid backports release name: %s' % release)
+            logwrite('For rules on the release name see upload_release()')
+            sys.exit(1)
+        rel_type = "linux-stable"
+        if (not rel_prep['stable']):
+            rel_type = "linux-next"
+        if (rel_prep['expected_tag'] != rel_describe):
+            logwrite('Unexpected %s based backports release tag on' % rel_type)
+            logwrite('the backports tree tree: %s\n' % rel_describe)
+            logwrite('You asked to make a release with this ')
+            logwrite('directory name: %s' % release)
+            logwrite('The actual expected tag we should find on')
+            logwrite('the backports tree then is: %s\n' % rel_prep['expected_tag'])
+            logwrite('For rules on the release name see upload_release()')
+            sys.exit(1)
 
     copy_list = read_copy_list(args.copy_list)
     deplist = read_dependencies(os.path.join(source_dir, 'dependencies'))
 
     # validate output directory
-    check_output_dir(args.outdir, args.clean)
+    check_output_dir(bpid.target_dir, args.clean)
 
     # do the copy
+    backport_integrate_files = [
+            ('Makefile.kernel', 'Makefile'),
+            ('Kconfig.integrate', 'Kconfig'),
+            ]
+    backport_package_files = [(x, x) for x in [
+        'Makefile',
+        'kconf/',
+        'Makefile.real',
+        'Makefile.kernel',
+        'Kconfig.package.hacks',
+        'scripts/',
+        '.blacklist.map',
+        '.gitignore',
+        'Makefile.build'] ]
+    backport_package_files += [
+            ('Kconfig.package', 'Kconfig'),
+            ]
+    backport_files = [(x, x) for x in [
+        'Kconfig.sources',
+        'compat/',
+        'backport-include/',
+    ]]
+
+    if not bpid.integrate:
+        backport_files += backport_package_files
+    else:
+        backport_files += backport_integrate_files
+
     if not args.git_revision:
-        print 'Copy original source files ...'
-        copy_files(os.path.join(source_dir, 'backport'), [('', '')], args.outdir)
-        copy_files(args.kerneldir, copy_list, args.outdir)
+        logwrite('Copy original source files ...')
     else:
-        print 'Get original source files from git ...'
-        copy_files(os.path.join(source_dir, 'backport'), [('', '')], args.outdir)
-        copy_git_files(args.kerneldir, copy_list, args.git_revision, args.outdir)
+        logwrite('Get original source files from git ...')
+    
+    copy_files(os.path.join(source_dir, 'backport'), backport_files, bpid.target_dir)
+
+    git_debug_init(args)
+
+    if not args.git_revision:
+        copy_files(args.kerneldir, copy_list, bpid.target_dir)
+    else:
+        copy_git_files(args.kerneldir, copy_list, args.git_revision, bpid.target_dir)
 
     # FIXME: should we add a git version of this (e.g. --git-extra-driver)?
     for src, copy_list in args.extra_driver:
-        copy_files(src, read_copy_list(open(copy_list, 'r')), args.outdir)
+        if (args.kup or args.kup_test):
+            git_paranoia(src)
+        copy_files(src, read_copy_list(open(copy_list, 'r')), bpid.target_dir)
 
-    git_debug_init(args)
+    git_debug_snapshot(args, 'Add driver sources')
 
-    print 'Apply patches ...'
-    patchdirs = []
-    for root, dirs, files in os.walk(os.path.join(source_dir, 'patches')):
-        if not dirs:
-            patchdirs.append(root)
-    patchdirs.sort()
-    for pdir in patchdirs:
-        l = os.listdir(pdir)
-        printed = False
-        for pfile in l:
-            # FIXME: again, use .gitignore?
-            if pfile[-1] == '~':
-                continue
-            pfile = os.path.join(pdir, pfile)
-            # read the patch file
-            p = patch.fromfile(pfile)
-            # if it is one ...
-            if not p:
-                continue
-            # check if the first file the patch touches exists, if so
-            # assume the patch needs to be applied -- otherwise continue
-            patched_file = '/'.join(p.items[0].source.split('/')[1:])
-            fullfn = os.path.join(args.outdir, patched_file)
-            if not os.path.exists(fullfn):
-                continue
-            if not printed:
-                if args.verbose:
-                    print "Applying changes from", os.path.basename(pdir)
-                printed = True
-            if args.refresh:
-                # but for refresh, of course look at all files the patch touches
-                for patchitem in p.items:
-                    patched_file = '/'.join(patchitem.source.split('/')[1:])
-                    fullfn = os.path.join(args.outdir, patched_file)
-                    shutil.copyfile(fullfn, fullfn + '.orig_file')
-
-            process = subprocess.Popen(['patch', '-p1'], stdout=subprocess.PIPE,
-                                       stderr=subprocess.STDOUT, stdin=subprocess.PIPE,
-                                       close_fds=True, universal_newlines=True,
-                                       cwd=args.outdir)
-            output = process.communicate(input=open(pfile, 'r').read())[0]
-            output = output.split('\n')
-            if output[-1] == '':
-                output = output[:-1]
-            if args.verbose:
-                for line in output:
-                    print '>', line
-            if process.returncode != 0:
-                if not args.verbose:
-                    print "Failed to apply changes from", os.path.basename(pdir)
-                    for line in output:
-                        print '>', line
-                return 2
-
-            if args.refresh:
-                pfilef = open(pfile + '.tmp', 'w')
-                for patchitem in p.items:
-                    patched_file = '/'.join(patchitem.source.split('/')[1:])
-                    fullfn = os.path.join(args.outdir, patched_file)
-                    process = subprocess.Popen(['diff', '-u', patched_file + '.orig_file', patched_file,
-                                                '--label', 'a/' + patched_file,
-                                                '--label', 'b/' + patched_file],
-                                               stdout=pfilef, close_fds=True,
-                                               universal_newlines=True, cwd=args.outdir)
-                    process.wait()
-                    os.unlink(fullfn + '.orig_file')
-                    if not process.returncode in (0, 1):
-                        print "Diffing for refresh failed!"
-                        pfilef.close()
-                        os.unlink(pfile + '.tmp')
-                        return 3
-                pfilef.close()
-                os.rename(pfile + '.tmp', pfile)
+    disable_list = add_automatic_backports(args)
+    if git_tracked_version:
+        backports_version = "(see git)"
+        kernel_version = "(see git)"
+    else:
+        backports_version = git.describe(tree=source_dir, extra_args=['--long'])
+        kernel_version = git.describe(rev=args.git_revision or 'HEAD',
+                                      tree=args.kerneldir,
+                                      extra_args=['--long'])
 
-        # remove orig/rej files that patch sometimes creates
-        for root, dirs, files in os.walk(args.outdir):
-            for f in files:
-                if f[-5:] == '.orig' or f[-4:] == '.rej':
-                    os.unlink(os.path.join(root, f))
-        if not printed:
-            if args.verbose:
-                print "Not applying changes from %s, not needed" % (os.path.basename(pdir),)
-        else:
-            git_debug_snapshot(args, "apply backport patches from %s" % (os.path.basename(pdir),))
+    if not bpid.integrate:
+        f = open(os.path.join(bpid.target_dir, 'versions'), 'w')
+        f.write('BACKPORTS_VERSION="%s"\n' % backports_version)
+        f.write('BACKPORTED_KERNEL_VERSION="%s"\n' % kernel_version)
+        f.write('BACKPORTED_KERNEL_NAME="%s"\n' % args.base_name)
+        if git_tracked_version:
+            f.write('BACKPORTS_GIT_TRACKED="backport tracker ID: $(shell git rev-parse HEAD 2>/dev/null || echo \'not built in git tree\')"\n')
+        f.close()
+        git_debug_snapshot(args, "add versions files")
+    else:
+        kconf_regexes = [
+                (re.compile(r'.*(?P<key>%%BACKPORT_DIR%%)'), '%%BACKPORT_DIR%%', 'backports/'),
+                (re.compile(r'.*(?P<key>%%BACKPORTS_VERSION%%).*'), '%%BACKPORTS_VERSION%%', backports_version),
+                (re.compile(r'.*(?P<key>%%BACKPORTED_KERNEL_VERSION%%).*'), '%%BACKPORTED_KERNEL_VERSION%%', kernel_version),
+                (re.compile(r'.*(?P<key>%%BACKPORTED_KERNEL_NAME%%).*'), '%%BACKPORTED_KERNEL_NAME%%', args.base_name),
+        ]
+        out = ''
+        for l in open(os.path.join(bpid.target_dir, 'Kconfig'), 'r'):
+            for r in kconf_regexes:
+                m = r[0].match(l)
+                if m:
+                    l = re.sub(r'(' + r[1] + ')', r'' + r[2] + '', l)
+            out += l
+        outf = open(os.path.join(bpid.target_dir, 'Kconfig'), 'w')
+        outf.write(out)
+        outf.close()
+        git_debug_snapshot(args, "modify top level backports/Kconfig with backports identity")
+
+    if disable_list:
+        # No need to verify_sources() as compat's Kconfig has no 'source' call
+        bpcfg = kconfig.ConfigTree(os.path.join(bpid.target_dir, 'compat', 'Kconfig'), bpid)
+        bpcfg.disable_symbols(disable_list)
+    git_debug_snapshot(args, 'Add automatic backports')
+
+    failure = apply_patches(args, "backport", source_dir, 'patches', bpid.target_dir, logwrite)
+    if failure:
+        return failure
+
+    # Kernel integration requires Kconfig.versions already generated for you,
+    # we cannot do this for a package as we have no idea what kernel folks
+    # will be using.
+    if bpid.integrate:
+        kver = gen_version.kernelversion(bpid.project_dir)
+        rel_specs = gen_version.get_rel_spec_stable(kver)
+        if not rel_specs:
+            logwrite('Cannot parse source kernel version, update parser')
+            sys.exit(1)
+        data = gen_version.genkconfig_versions(rel_specs)
+        fo = open(os.path.join(bpid.target_dir, 'Kconfig.versions'), 'w')
+        fo.write(data)
+        fo.close()
+        git_debug_snapshot(args, "generate kernel version requirement Kconfig file")
 
     # some post-processing is required
-    configtree = kconfig.ConfigTree(os.path.join(args.outdir, 'Kconfig'))
-    print 'Modify Kconfig tree ...'
-    configtree.prune_sources(ignore=['Kconfig.kernel', 'Kconfig.versions'])
+    configtree = kconfig.ConfigTree(os.path.join(bpid.target_dir, 'Kconfig'), bpid)
+    ignore=['Kconfig.kernel', 'Kconfig.versions', 'Kconfig.local']
+
+    configtree.verify_sources(ignore=ignore)
+    git_debug_snapshot(args, "verify sources on top level backports Kconfig")
+
+    orig_symbols = configtree.symbols()
+
+    logwrite('Modify Kconfig tree ...')
+    configtree.prune_sources(ignore=ignore)
     git_debug_snapshot(args, "prune Kconfig tree")
-    configtree.force_tristate_modular()
-    git_debug_snapshot(args, "force tristate options modular")
+
+    if not bpid.integrate:
+        configtree.force_tristate_modular()
+        git_debug_snapshot(args, "force tristate options modular")
+
+    ignore = [os.path.join(bpid.target_dir, x) for x in [
+                'Kconfig.package.hacks',
+                'Kconfig.versions',
+                'Kconfig.local',
+                'Kconfig',
+                ]
+            ]
+    configtree.adjust_backported_configs(ignore=ignore, orig_symbols=orig_symbols)
+    git_debug_snapshot(args, "adjust backports config symbols we port")
+
     configtree.modify_selects()
     git_debug_snapshot(args, "convert select to depends on")
 
-    # write the versioning file
-    backports_version = git.describe(tree=source_dir)
-    kernel_version = git.describe(tree=args.kerneldir)
-    f = open(os.path.join(args.outdir, 'versions'), 'w')
-    f.write('BACKPORTS_VERSION="%s"\n' % backports_version)
-    f.write('KERNEL_VERSION="%s"\n' % kernel_version)
-    f.write('KERNEL_NAME="%s"\n' % args.base_name)
-    f.close()
-
     symbols = configtree.symbols()
 
-    # write local symbol list -- needed during build
-    f = open(os.path.join(args.outdir, '.local-symbols'), 'w')
+    # write local symbol list -- needed during packaging build
+    if not bpid.integrate:
+        f = open(os.path.join(bpid.target_dir, 'local-symbols'), 'w')
+        for sym in symbols:
+            f.write('%s=\n' % sym)
+        f.close()
+        git_debug_snapshot(args, "add symbols files")
+    # also write Kconfig.local, representing all local symbols
+    # with a BACKPORTED_ prefix
+    f = open(os.path.join(bpid.target_dir, 'Kconfig.local'), 'w')
     for sym in symbols:
-        f.write('%s=\n' % sym)
+        f.write('config BACKPORTED_%s\n' % sym)
+        f.write('\ttristate\n')
+        f.write('\tdefault %s\n' % sym)
     f.close()
+    git_debug_snapshot(args, "add Kconfig.local")
+
+    # add defconfigs that we want
+    defconfigs_dir = os.path.join(source_dir, 'backport', 'defconfigs')
+    os.mkdir(os.path.join(bpid.target_dir, 'defconfigs'))
+    for dfbase in os.listdir(defconfigs_dir):
+        copy_defconfig = True
+        dfsrc = os.path.join(defconfigs_dir, dfbase)
+        for line in open(dfsrc, 'r'):
+            if not '=' in line:
+                continue
+            line_ok = False
+            for sym in symbols:
+                if sym + '=' in line:
+                    line_ok = True
+                    break
+            if not line_ok:
+                copy_defconfig = False
+                break
+        if copy_defconfig:
+            shutil.copy(dfsrc, os.path.join(bpid.target_dir, 'defconfigs', dfbase))
 
-    git_debug_snapshot(args, "add versions/symbols files")
+    git_debug_snapshot(args, "add (useful) defconfig files")
 
-    print 'Rewrite Makefiles and Kconfig files ...'
+    logwrite('Rewrite Makefiles and Kconfig files ...')
 
     # rewrite Makefile and source symbols
+
+    # symbols we know only we can provide under the backport project prefix
+    # for which we need an exception.
+    skip_orig_syms = [ bpid.project_prefix + x for x in [
+            'INTEGRATE',
+            ]
+    ]
+    parse_orig_syms = [x for x in orig_symbols if x not in skip_orig_syms ]
     regexes = []
-    for some_symbols in [symbols[i:i + 50] for i in range(0, len(symbols), 50)]:
+    for some_symbols in [parse_orig_syms[i:i + 50] for i in range(0, len(parse_orig_syms), 50)]:
         r = 'CONFIG_((' + '|'.join([s + '(_MODULE)?' for s in some_symbols]) + ')([^A-Za-z0-9_]|$))'
         regexes.append(re.compile(r, re.MULTILINE))
-    for root, dirs, files in os.walk(args.outdir):
+    for root, dirs, files in os.walk(bpid.target_dir):
         # don't go into .git dir (possible debug thing)
         if '.git' in dirs:
             dirs.remove('.git')
         for f in files:
             data = open(os.path.join(root, f), 'r').read()
             for r in regexes:
-                data = r.sub(r'CPTCFG_\1', data)
+                data = r.sub(r'' + bpid.full_prefix + '\\1', data)
+            # we have an absolue path in $(src) since we compile out of tree
+            data = re.sub(r'\$\(srctree\)/\$\(src\)', '$(src)', data)
+            data = re.sub(r'\$\(srctree\)', '$(backport_srctree)', data)
+            data = re.sub(r'-Idrivers', '-I$(backport_srctree)/drivers', data)
+            if bpid.integrate:
+                data = re.sub(r'CPTCFG_', bpid.full_prefix, data)
             fo = open(os.path.join(root, f), 'w')
             fo.write(data)
             fo.close()
 
-    git_debug_snapshot(args, "rename config symbol usage")
+    git_debug_snapshot(args, "rename config symbol / srctree usage")
 
     # disable unbuildable Kconfig symbols and stuff Makefiles that doesn't exist
-    maketree = make.MakeTree(os.path.join(args.outdir, 'Makefile.kernel'))
+    if bpid.integrate:
+        maketree = make.MakeTree(os.path.join(bpid.target_dir, 'Makefile'))
+    else:
+        maketree = make.MakeTree(os.path.join(bpid.target_dir, 'Makefile.kernel'))
     disable_kconfig = []
     disable_makefile = []
     for sym in maketree.get_impossible_symbols():
-        if sym[:7] == 'CPTCFG_':
-            disable_kconfig.append(sym[7:])
-        else:
-            disable_makefile.append(sym[7:])
+        disable_kconfig.append(sym[7:])
+        disable_makefile.append(sym[7:])
 
     configtree.disable_symbols(disable_kconfig)
     git_debug_snapshot(args, "disable impossible kconfig symbols")
@@ -402,8 +1048,17 @@ def main():
     for sym in tuple(deplist.keys()):
         new = []
         for dep in deplist[sym]:
-            new.append('!BACKPORT_KERNEL_%s' % dep.replace('.', '_'))
-        deplist[sym] = new
+            if "kconfig:" in dep:
+                    kconfig_expr = dep.replace('kconfig: ', '')
+                    new.append(kconfig_expr)
+            elif (dep == "DISABLE"):
+                    new.append('BACKPORT_DISABLED_KCONFIG_OPTION')
+            else:
+                    new.append('!KERNEL_%s' % dep.replace('.', '_'))
+        if bpid.integrate:
+            deplist[sym] = ["BACKPORT_" + x for x in new]
+        else:
+            deplist[sym] = new
     configtree.add_dependencies(deplist)
     git_debug_snapshot(args, "add kernel version dependencies")
 
@@ -417,19 +1072,39 @@ def main():
     # groups -- 50 seemed safer and is still fast)
     regexes = []
     for some_symbols in [disable_makefile[i:i + 50] for i in range(0, len(disable_makefile), 50)]:
-        r = '(CONFIG_(' + '|'.join([s for s in some_symbols]) + '))'
+        r = '^(([^#].*((' + bpid.full_prefix_resafe + '|CONFIG_)(' + '|'.join([s for s in some_symbols]) + ')))\W)'
         regexes.append(re.compile(r, re.MULTILINE))
     for f in maketree.get_makefiles():
         data = open(f, 'r').read()
         for r in regexes:
-            data = r.sub(r'IMPOSSIBLE_\2', data)
+            data = r.sub(r'#\1', data)
         fo = open(f, 'w')
         fo.write(data)
         fo.close()
     git_debug_snapshot(args, "disable unsatisfied Makefile parts")
 
-    print 'Done!'
+    if bpid.integrate:
+        f = open(os.path.join(bpid.project_dir, 'Kconfig'), 'a')
+        f.write('source "backports/Kconfig"\n')
+        f.close()
+        git_debug_snapshot(args, "hooked backport to top level Kconfig")
+
+        failure = apply_patches(args, "integration", source_dir, 'integration-patches/',
+                                bpid.project_dir, logwrite)
+        if failure:
+            return failure
+
+    if (args.kup or args.kup_test):
+        req = reqs.Req()
+        req.kup()
+        if not req.reqs_match():
+            sys.exit(1)
+        upload_release(args, rel_prep, logwrite=logwrite)
+
+    logwrite('Done!')
+    return 0
 
-ret = main()
-if ret:
-    sys.exit(ret)
+if __name__ == '__main__':
+    ret = _main()
+    if ret:
+        sys.exit(ret)