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 redhat com>
#
# 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 redhat com>
#
# 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