beta code to wrap commands
[catalyst.git] / modules / catalyst_support.py
1 # Distributed under the GNU General Public License version 2
2 # Copyright 2003-2004 Gentoo Technologies, Inc.
3 # $Header: /var/cvsroot/gentoo/src/catalyst/modules/catalyst_support.py,v 1.23 2004/06/16 18:34:23 zhen Exp $
4
5 import sys,string,os,types
6
7 # these should never be touched
8 required_build_targets=["generic_target","generic_stage_target"]
9
10 # new build types should be added here
11 valid_build_targets=["stage1_target","stage2_target","stage3_target","grp_target",
12                                                 "livecd_stage1_target","livecd_stage2_target","embedded_target",
13                                                 "tinderbox_target","snapshot_target"]
14
15 required_config_file_values=["storedir","sharedir","distdir","portdir"]
16 valid_config_file_values=required_config_file_values[:]
17 valid_config_file_values.append("PKGCACHE")
18 valid_config_file_values.append("CCACHE")
19 valid_config_file_values.append("DISTCC")
20 valid_config_file_values.append("ENVSCRIPT")
21 valid_config_file_values.append("options")
22
23 verbosity=1
24
25 def list_bashify(mylist):
26         if type(mylist)==types.StringType:
27                 mypack=[mylist]
28         else:
29                 mypack=mylist[:]
30         for x in range(0,len(mypack)):
31                 # surround args with quotes for passing to bash,
32                 # allows things like "<" to remain intact
33                 mypack[x]="'"+mypack[x]+"'"
34         mypack=string.join(mypack)
35         return mypack
36
37 class CatalystError(Exception):
38         def __init__(self, message):
39                 if message:
40                         print
41                         print "!!! catalyst: "+message
42                         
43 def die(msg=None):
44         warn(msg)
45         sys.exit(1)
46
47 def warn(msg):
48         print "!!! catalyst: "+msg
49
50 def spawn(mystring,debug=0,fd_pipes=None):
51         """
52         apparently, os.system mucks up return values, so this code
53         should fix that.
54
55         Taken from portage.py - thanks to carpaski@gentoo.org
56         """
57         print "Running command \""+mystring+"\""
58         myargs=[]
59         mycommand = "/bin/bash"
60         if debug:
61                 myargs=["bash","-x","-c",mystring]
62         else:
63                 myargs=["bash","-c",mystring]
64         
65         mypid=os.fork()
66         if mypid==0:
67                 if fd_pipes:
68                         os.dup2(fd_pipes[0], 0) # stdin  -- (Read)/Write
69                         os.dup2(fd_pipes[1], 1) # stdout -- Read/(Write)
70                         os.dup2(fd_pipes[2], 2) # stderr -- Read/(Write)
71                 try:
72                         os.execvp(mycommand,myargs)
73                 except Exception, e:
74                         raise CatalystError,myexc
75                 
76                 # If the execve fails, we need to report it, and exit
77                 # *carefully* --- report error here
78                 os._exit(1)
79                 sys.exit(1)
80                 return # should never get reached
81         retval=os.waitpid(mypid,0)[1]
82         if (retval & 0xff)==0:
83                 return (retval >> 8) # return exit code
84         else:
85                 return ((retval & 0xff) << 8) # interrupted by signal
86
87 def cmd(mycmd,myexc=""):
88         try:
89                 retval=spawn(mycmd)
90                 if retval != 0:
91                         raise CatalystError,myexc
92         except KeyboardInterrupt:
93                 raise CatalystError,"Build aborting due to user intervention"
94
95 def file_locate(settings,filelist,expand=1):
96         #if expand=1, non-absolute paths will be accepted and
97         # expanded to os.getcwd()+"/"+localpath if file exists
98         for myfile in filelist:
99                 if not settings.has_key(myfile):
100                         #filenames such as cdtar are optional, so we don't assume the variable is defined.
101                         pass
102                 if len(settings[myfile])==0:
103                         raise CatalystError, "File variable \""+myfile+"\" has a length of zero (not specified.)"
104                 if settings[myfile][0]=="/":
105                         if not os.path.exists(settings[myfile]):
106                                 raise CatalystError, "Cannot locate specified "+myfile+": "+settings[myfile]
107                 elif expand and os.path.exists(os.getcwd()+"/"+settings[myfile]):
108                         settings[myfile]=os.getcwd()+"/"+settings[myfile]
109                 else:
110                         raise CatalystError, "Cannot locate specified "+myfile+": "+settings[myfile]+" (2nd try)"
111
112 """
113 Spec file format:
114
115 The spec file format is a very simple and easy-to-use format for storing data. Here's an example
116 file:
117
118 item1: value1
119 item2: foo bar oni
120 item3:
121         meep
122         bark
123         gleep moop
124         
125 This file would be interpreted as defining three items: item1, item2 and item3. item1 would contain
126 the string value "value1". Item2 would contain an ordered list [ "foo", "bar", "oni" ]. item3
127 would contain an ordered list as well: [ "meep", "bark", "gleep", "moop" ]. It's important to note
128 that the order of multiple-value items is preserved, but the order that the items themselves are
129 defined are not preserved. In other words, "foo", "bar", "oni" ordering is preserved but "item1"
130 "item2" "item3" ordering is not, as the item strings are stored in a dictionary (hash).
131 """
132
133 def parse_spec(mylines):
134         myspec={}
135         pos=0
136         while pos<len(mylines):
137                 if len(mylines[pos])<=1:
138                         #skip blanks
139                         pos += 1
140                         continue
141                 if mylines[pos][0] in ["#"," ","\t"]:
142                         #skip indented lines, comments
143                         pos += 1
144                         continue
145                 else:
146                         myline=mylines[pos].split()
147                         
148                         if (len(myline)==0) or (myline[0][-1] != ":"):
149                                 msg("Skipping invalid spec line "+repr(pos))
150                         #strip colon:
151                         myline[0]=myline[0][:-1]
152                         if len(myline)==2:
153                                 #foo: bar  --> foo:"bar"
154                                 myspec[myline[0]]=myline[1]
155                                 pos += 1
156                         elif len(myline)>2:
157                                 #foo: bar oni --> foo: [ "bar", "oni" ]
158                                 myspec[myline[0]]=myline[1:]
159                                 pos += 1
160                         else:
161                                 #foo:
162                                 # bar
163                                 # oni meep
164                                 # --> foo: [ "bar", "oni", "meep" ]
165                                 accum=[]
166                                 pos += 1
167                                 while (pos<len(mylines)):
168                                         if len(mylines[pos])<=1:
169                                                 #skip blanks
170                                                 pos += 1
171                                                 continue
172                                         if mylines[pos][0] == "#":
173                                                 #skip comments
174                                                 pos += 1
175                                                 continue
176                                         if mylines[pos][0] in [" ","\t"]:
177                                                 #indented line, add to accumulator
178                                                 accum.extend(mylines[pos].split())
179                                                 pos += 1
180                                         else:
181                                                 #we've hit the next item, break out
182                                                 break
183                                 myspec[myline[0]]=accum
184         return myspec
185
186 def read_spec(myspecfile):
187         try:
188                 myf=open(myspecfile,"r")
189         except:
190                 raise CatalystError, "Could not open spec file "+myspecfile
191         mylines=myf.readlines()
192         myf.close()
193         return parse_spec(mylines)
194         
195 def msg(mymsg,verblevel=1):
196         if verbosity>=verblevel:
197                 print mymsg
198
199 def ismount(path):
200         "enhanced to handle bind mounts"
201         if os.path.ismount(path):
202                 return 1
203         a=open("/proc/mounts","r")
204         mylines=a.readlines()
205         a.close()
206         for line in mylines:
207                 mysplit=line.split()
208                 if path == mysplit[1]:
209                         return 1
210         return 0
211
212 def arg_parse(mydict,remaining,argv):
213         "grab settings from argv, storing 'target' in mydict, and everything in remaining for later parsing"
214         global required_config_file_values
215         for x in argv:
216                 foo=string.split(x,"=")
217                 if len(foo)!=2:
218                         raise CatalystError, "Invalid arg syntax: "+x
219                 remaining[foo[0]]=foo[1]
220         if not remaining.has_key("target"):
221                 raise CatalystError, "Required value \"target\" not specified."
222         mydict["target"]=remaining["target"]
223         for x in required_config_file_values:
224                 if not mydict.has_key(x):
225                         raise CatalystError, "Required config file value \""+x+"\" not found."
226
227                 
228 def addl_arg_parse(myspec,addlargs,requiredspec,validspec):
229         "helper function to help targets parse additional arguments"
230         global valid_config_file_values
231         for x in addlargs.keys():
232                 if x not in validspec and x not in valid_config_file_values:
233                         raise CatalystError, "Argument \""+x+"\" not recognized."
234                 else:
235                         myspec[x]=addlargs[x]
236         for x in requiredspec:
237                 if not myspec.has_key(x):
238                         raise CatalystError, "Required argument \""+x+"\" not specified."
239         
240 def spec_dump(myspec):
241         for x in myspec.keys():
242                 print x+": "+repr(myspec[x])
243
244 def touch(myfile):
245         try:
246                 myf=open(myfile,"w")
247                 myf.close()
248         except IOError:
249                 raise CatalystError, "Could not touch "+myfile+"."