#!/usr/bin/python # # makeupdates - Generate an updates.img containing changes since the last # tag, but only changes to the main anaconda runtime. # initrd/stage1 updates have to be created separately. # # Copyright (C) 2009 Red Hat, Inc. # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published # by the Free Software Foundation; either version 2.1 of the License, or # (at your option) any later version. # # 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 Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with this program. If not, see . # # Author: David Cantrell import getopt import os import shutil import subprocess import sys def getArchiveTag(configure, spec): tag = "" f = open(configure) lines = f.readlines() f.close() for line in lines: if line.startswith('AC_INIT('): fields = line.split('[') tag += fields[1].split(']')[0] + '-' + fields[2].split(']')[0] break else: continue f = open(spec) lines = f.readlines() f.close() for line in lines: if line.startswith('Release:'): tag += '-' + line.split()[1].split('%')[0] else: continue return tag def getArchiveTagOffset(configure, spec, offset): tag = getArchiveTag(configure, spec) if not tag.count("-") >= 2: return tag ldash = tag.rfind("-") bldash = tag[:ldash].rfind("-") ver = tag[bldash+1:ldash] if not ver.count(".") >= 1: return tag ver = ver[:ver.rfind(".")] if not len(ver) > 0: return tag globstr = "refs/tags/" + tag[:bldash+1] + ver + ".*" proc = subprocess.Popen(['git', 'for-each-ref', '--sort=taggerdate', '--format=%(tag)', globstr], stdout=subprocess.PIPE, stderr=subprocess.PIPE).communicate() lines = proc[0].strip("\n").split('\n') lines.reverse() try: return lines[offset] except IndexError: return tag def doGitDiff(tag, args=[]): proc = subprocess.Popen(['git', 'diff', '--name-status', tag] + args, stdout=subprocess.PIPE, stderr=subprocess.PIPE).communicate() lines = proc[0].split('\n') return lines def copyUpdatedFiles(tag, updates, cwd): def pruneFile(src, names): lst = [] for name in names: if name.startswith('Makefile') or name.endswith('.pyc'): lst.append(name) return lst def install_to_dir(fname, relpath): sys.stdout.write("Including %s\n" % fname) outdir = os.path.join(updates, relpath) if not os.path.isdir(outdir): os.makedirs(outdir) shutil.copy2(file, outdir) subdirs = [] # Updates get overlaid onto the runtime filesystem. Anaconda expects them # to be in /run/install/updates, so put them in # $updatedir/run/install/updates. tmpupdates = updates.rstrip('/') if not tmpupdates.endswith("/run/install/updates"): tmpupdates = os.path.join(tmpupdates, "run/install/updates") lines = doGitDiff(tag) for line in lines: fields = line.split() if len(fields) < 2: continue status = fields[0] file = fields[1] if status == "D": continue if file.endswith('.spec.in') or (file.find('Makefile') != -1) or \ file.endswith('.c') or file.endswith('.h') or \ file.endswith('.sh') or file == 'configure.ac': continue if file.endswith('.glade'): # Some UI files should go under ui/ where dir is the # directory above the file.glade dir_parts = os.path.dirname(file).split(os.path.sep) g_idx = dir_parts.index("gui") uidir = os.path.sep.join(dir_parts[g_idx+1:]) path_comps = [tmpupdates, "ui"] if uidir: path_comps.append(uidir) install_to_dir(file, os.path.join(*path_comps)) elif file.startswith('pyanaconda/'): # pyanaconda stuff goes into /tmp/updates/[path] dirname = os.path.join(tmpupdates, os.path.dirname(file)) install_to_dir(file, dirname) elif file == 'anaconda': # anaconda itself we just overwrite install_to_dir(file, "usr/sbin") elif file.endswith('.service') or file.endswith(".target"): # same for systemd services install_to_dir(file, "lib/systemd/system") elif file.endswith('/anaconda-generator'): # yeah, this should probably be more clever.. install_to_dir(file, "lib/systemd/system-generators") elif file == "data/tmux.conf": install_to_dir(file, "usr/share/anaconda") elif file == "data/interactive-defaults.ks": install_to_dir(file, "usr/share/anaconda") elif file == "data/liveinst/liveinst": install_to_dir(file, "usr/sbin") elif file == "data/70-anaconda.rules": install_to_dir(file, "lib/udev/rules.d") elif file.startswith("data/pixmaps"): install_to_dir(file, "usr/share/anaconda/pixmaps") elif file.startswith("data/ui/"): install_to_dir(file, "usr/share/anaconda/ui") elif file.startswith("data/post-scripts/"): install_to_dir(file, "usr/share/anaconda/post-scripts") elif file.find('/') != -1: fields = file.split('/') subdir = fields[0] if subdir in ['po', 'scripts','command-stubs', 'tests', 'docs', 'fonts', 'utils', 'liveinst', 'dracut', 'data']: continue else: sys.stdout.write("Including %s\n" % (file,)) install_to_dir(file, tmpupdates) else: sys.stdout.write("Including %s\n" % (file,)) install_to_dir(file, tmpupdates) def _compilableChanged(tag, compilable): lines = doGitDiff(tag, [compilable]) for line in lines: fields = line.split() if len(fields) < 2: continue status = fields[0] file = fields[1] if status == "D": continue if file.startswith('Makefile') or file.endswith('.h') or \ file.endswith('.c') or file.endswith('.py'): return True return False def isysChanged(tag): return _compilableChanged(tag, 'pyanaconda/isys') def widgetsChanged(tag): return _compilableChanged(tag, 'widgets') def copyUpdatedIsys(updates, cwd): os.chdir(cwd) if not os.path.isfile('Makefile'): if not os.path.isfile('configure'): os.system('./autogen.sh') os.system('./configure') os.system('make') # Updates get overlaid onto the runtime filesystem. Anaconda expects them # to be in /run/install/updates, so put them in # $updatedir/run/install/updates. tmpupdates = updates.rstrip('/') if not tmpupdates.endswith("/run/install/updates/pyanaconda"): tmpupdates = os.path.join(tmpupdates, "run/install/updates/pyanaconda/isys") if not os.path.isdir(tmpupdates): os.makedirs(tmpupdates) isysmodule = os.path.realpath(cwd + '/pyanaconda/isys/.libs/_isys.so') isysinit = os.path.realpath(cwd + '/pyanaconda/isys/__init__.py') if os.path.isfile(isysmodule): shutil.copy2(isysmodule, tmpupdates) if os.path.isfile(isysinit): shutil.copy2(isysinit, tmpupdates) def copyUpdatedWidgets(updates, cwd): os.chdir(cwd) if os.path.isdir("/lib64"): libdir = "/lib64/" else: libdir = "/lib/" if not os.path.isdir(updates + libdir): os.makedirs(updates + libdir) if not os.path.isdir(updates + libdir + "girepository-1.0"): os.makedirs(updates + libdir + "girepository-1.0") if not os.path.isfile('Makefile'): if not os.path.isfile('configure'): os.system('./autogen.sh') os.system('./configure --enable-gtk-doc --enable-introspection') os.system('make') files = ["libAnacondaWidgets.so", "libAnacondaWidgets.so.0", "libAnacondaWidgets.so.0.0.0"] for f in files: path = os.path.normpath(cwd + "/widgets/src/.libs/" + f) if os.path.islink(path) and not os.path.exists(updates + libdir + os.path.basename(path)): os.symlink(os.readlink(path), updates + libdir + os.path.basename(path)) elif os.path.isfile(path): shutil.copy2(path, updates + libdir) typelib = os.path.realpath(cwd + "/widgets/src/AnacondaWidgets-1.0.typelib") if os.path.isfile(typelib): shutil.copy2(typelib, updates + libdir + "girepository-1.0") def addRpms(updates, add_rpms): for rpm in add_rpms: cmd = "cd %s && rpm2cpio %s | cpio -dium" % (updates, rpm) sys.stdout.write(cmd+"\n") os.system(cmd) def createUpdatesImage(cwd, updates): os.chdir(updates) os.system("find . | cpio -c -o | gzip -9cv > %s/updates.img" % (cwd,)) sys.stdout.write("updates.img ready\n") def usage(cmd): sys.stdout.write("Usage: %s [OPTION]...\n" % (cmd,)) sys.stdout.write("Options:\n") sys.stdout.write(" -k, --keep Do not delete updates subdirectory.\n") sys.stdout.write(" -c, --compile Compile code if there are isys changes.\n") sys.stdout.write(" -h, --help Display this help and exit.\n") sys.stdout.write(" -t, --tag Make image from TAG to HEAD.\n") sys.stdout.write(" -o, --offset Make image from (latest_tag - OFFSET) to HEAD.\n") sys.stdout.write(" -a, --add Add contents of rpm to the update\n") def main(argv): prog = os.path.basename(sys.argv[0]) cwd = os.getcwd() configure = os.path.realpath(cwd + '/configure.ac') spec = os.path.realpath(cwd + '/anaconda.spec.in') updates = cwd + '/updates' keep, compile, help, unknown = False, False, False, False tag = None opts = [] offset = 0 add_rpms = [] try: opts, args = getopt.getopt(sys.argv[1:], 'a:t:o:kc?', ['add=', 'tag=', 'offset=', 'keep', 'compile', 'help']) except getopt.GetoptError: help = True for o, a in opts: if o in ('-k', '--keep'): keep = True elif o in ('-c', '--compile'): compile = True elif o in ('-?', '--help'): help = True elif o in ('-t', '--tag'): tag = a elif o in ('-o', '--offset'): offset = int(a) elif o in ('-a', '--add'): add_rpms.append(os.path.abspath(a)) else: unknown = True if help: usage(prog) sys.exit(0) elif unknown: sys.stderr.write("%s: extra operand `%s'" % (prog, sys.argv[1],)) sys.stderr.write("Try `%s --help' for more information." % (prog,)) sys.exit(1) if not os.path.isfile(configure) and not os.path.isfile(spec): sys.stderr.write("You must be at the top level of the anaconda source tree.\n") sys.exit(1) if not tag: if offset < 1: tag = getArchiveTag(configure, spec) else: tag = getArchiveTagOffset(configure, spec, offset) sys.stdout.write("Using tag: %s\n" % tag) if not os.path.isdir(updates): os.makedirs(updates) copyUpdatedFiles(tag, updates, cwd) if compile: if isysChanged(tag): copyUpdatedIsys(updates, cwd) if widgetsChanged(tag): copyUpdatedWidgets(updates, cwd) if add_rpms: addRpms(updates, add_rpms) createUpdatesImage(cwd, updates) if not keep: shutil.rmtree(updates) if __name__ == "__main__": main(sys.argv)