Hi folks, In the course of messing with various installer tools, I've ended up writing some python code that reads and writes an XML metadata file that is (basically) an easily-parsable superset of .discinfo. A quick overview of the concepts involved here, from the top down: A "compose" is, basically, an entire distribution - all the installable trees for all the various arches, plus iso sets, plus maybe some SRPMS and debuginfo packages that go along with them. I suppose I could rename this to "distro" but we've been using this terminology for so long that it's just stuck. A "tree" is a directory layout with all the packages and images you need to install from. An "isoset" is (surprise!) a full set of isos that make up the distribution. Okay, here's some examples. The tool that does the composing (pungi, distill, etc.) should create .compose.xml in the top-level of the compose dir. That file looks approximately like this: <compose id="rawhide-20070122" time="1169485348"> <debug arch="i386">development/i386/debug</debug> <debug arch="x86_64">development/x86_64/debug</debug> ... <source arch="i386">development/source/SRPMS</source> <source arch="x86_64">development/source/SRPMS</source> ... <tree arch="i386">development/i386/os</tree> <tree arch="x86_64">development/x86_64/os</tree> </compose> This defines the id of the compose (which should be unique for each compose, and would be nice if it was human-understandable like this one) and a timestamp that lets you know when it was created. Really this file just exists to tell point you to the actual contents of the compose, and where it all lives - there's debuginfo packages here, sources here, trees here, and so on. Later we should also have: <isoset arch="i386">development/i386/iso</isoset> Each of these items points to a directory where another xml file will have further information - a "tree" directory will contain a file named ".tree.xml", ".isoset.xml" for isosets, etc. So here's an example of tree.xml: <tree id="1169482851.57"> <compose>rawhide-20070122</compose> <family>Fedora Core 6.89</family> <version>6.89</version> <time>1169482851.57</time> <arch>i386</arch> <file type="kernel">images/pxeboot/vmlinuz</file> <file type="initrd">images/pxeboot/initrd.img</file> <file type="boot.iso">images/boot.iso</file> </tree> Each tree has a unique ID. Like the composes, it can be any freeform string, but it must be unique among trees. (A better choice might be something like "rawhide-20070122.i386" - this is still open to change.) In the xml structure we've got the name of the parent compose, the 'family' string (the second line of .discinfo), the version (as a floating point number), the timestamp of the tree (which I am using as the tree id, due to the fact that it's unique), and the tree's arch. Finally there's a list of important files that other applications might like to know the location of. For my purposes, those three files are the ones I care about - other applications might want other file items to be included here. Okay, so here's the questions: 1) Is this enough info to model trees and composes? What about iso sets? 2) Does anaconda have all the metadata that I'm writing out here? 3) Does this stuff look sane enough for inclusion in anaconda? Let me know what you think. I'm still not completely sure how to deal with iso sets and such. I'm sure I'm missing some vital piece of information from .discinfo that wasn't needed for my purposes, so please tell me what this lacks. Thanks in advance! -w
#!/usr/bin/python # compose.py - A python class to represent a compose, and read/write XML for it. # Copyright (C) 2007 Red Hat, Inc. # Author: Will Woods <wwoods@xxxxxxxxxx> # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 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 General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA from tree import Debug, Source, Tree, IsoSet import cElementTree as ET import os.path class Compose(object): def __init__(self, **kwargs): self.id="" self.xmldir="" self.time=0.0 # these should be lists of the corresponding objects self.trees=[] self.isos=[] self.debug=[] self.source=[] if 'xml' in kwargs: self.xmldir=os.path.dirname(kwargs['xml']) self.parse(kwargs['xml']) def parse(self,xml): elemtree = ET.parse(xml) r=elemtree.getroot() self.id=r.get('id') self.time=r.get('time') for type, obj, set in (['debug',Debug,self.debug], ['source',Source,self.source], ['tree',Tree,self.trees], ['isos',IsoSet,self.isos]): for e in r.findall(type): c=obj() c.arch=e.get('arch') c.variant=e.get('variant') or "" c.reldir=e.text try: c.parse(os.path.join(self.xmldir,c.reldir,'.'+type+'.xml')) c.compose=self.id except: pass set.append(c) return elemtree def element(self): compose = ET.Element('compose',id=self.id,time=str(self.time)) for type, set in {'debug':self.debug,'source':self.source, 'tree':self.trees,'isos':self.isos}.items(): for i in set: elem = ET.SubElement(compose,type,arch=i.arch) if type in ('tree','isos') and i.variant: elem.attrib['variant'] = i.variant elem.text=i.reldir return compose def xml(self): return ET.tostring(indent(self.element())) def __repr__(self): return "Compose(%s)" % self.id def __str__(self): return "%s%s" % (self.id, str(self.trees))
#!/usr/bin/python # tree.py - objects for distro trees and things like them # Copyright (C) 2007 Red Hat, Inc. # Author: Will Woods <wwoods@xxxxxxxxxx> # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 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 General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA import cElementTree as ET class ComposeTarget(object): '''A generic base class for trees and other things built in a compose''' def __init__(self,**kwargs): self.id="" # unique ID to represent this tree self.compose="" # compose ID self.time=0.0 # timestamp self.arch="" # arch self.reldir="" # (optional) location relative to parent compose # convenience - what type am I? self.type=type(self).__name__ if 'xml' in kwargs: self.parse(kwargs['xml']) elif 'dict' in kwargs: self.__dict__ = kwargs['dict'] def parse(self,xml): elemtree=ET.parse(xml) r=elemtree.getroot() self.id = r.get('id') self.compose=r.find('compose').text self.time=float(r.find('time').text) self.arch=r.find('arch').text return elemtree def element(self,klist=()): root=ET.Element(self.type.lower(),id=self.id) for k,v in self.__dict__.items(): if (k in klist+('compose','time','arch')) and v: e=ET.SubElement(root,k) e.text=str(v) return root def xml(self): return ET.tostring(indent(self.element())) def __str__(self): str='%s/%s' % (self.compose,self.arch) if self.variant: str = str + "/%s" % self.variant return "<%s %s>" % (self.type, str) class Source(ComposeTarget): '''A class for a SRPM dir''' pass class Debug(ComposeTarget): '''A class for a debuginfo dir''' pass class Tree(ComposeTarget): '''A class that represents an installable tree''' # This subclass adds family, version, variant, and files def __init__(self,**kwargs): self.family="" self.version=0.0 self.variant="" self.files={} ComposeTarget.__init__(self,**kwargs) def parse(self,xml): elemtree=ComposeTarget.parse(self,xml) r=elemtree.getroot() self.family=r.find('family').text self.version=float(r.find('version').text) e=r.find('variant') if e: self.variant=e.text for e in r.findall('file'): self.files[e.get('type')]=e.text def element(self): root=ComposeTarget.element(self,('family','version','variant')) for type,path in self.files.items(): e=ET.SubElement(root,'file',type=type) e.text=path return root def __short_family(self): fam_map={"Fedora Core ":"FC", "Red Hat Enterprise Linux ":"RHEL"} s=self.family for longf,shortf in fam_map.items(): if s.startswith(longf): s=s.replace(longf,shortf,1) return s class IsoSet(Tree): '''A class that represents a set of ISO images''' # The difference here is that we add some attributes to the 'files' # list, e.g.: # <file type="cd" number="3">6/i386/iso/FC-6-i386-disc3.iso</file> # other types:"dvd", "rescue" # FIXME not sure how to actually IMPLEMENT that. class ISO(object): ? pass
Attachment:
signature.asc
Description: This is a digitally signed message part