tolerate ${...} in function names
[bitbake.git] / lib / bb / fetch.py
1 #!/usr/bin/env python
2 # ex:ts=4:sw=4:sts=4:et
3 # -*- tab-width: 4; c-basic-offset: 4; indent-tabs-mode: nil -*-
4 """
5 BitBake 'Fetch' implementations
6
7 Classes for obtaining upstream sources for the
8 BitBake build tools.
9
10 Copyright (C) 2003, 2004  Chris Larson
11
12 This program is free software; you can redistribute it and/or modify it under
13 the terms of the GNU General Public License as published by the Free Software
14 Foundation; either version 2 of the License, or (at your option) any later
15 version.
16
17 This program is distributed in the hope that it will be useful, but WITHOUT
18 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
19 FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
20
21 You should have received a copy of the GNU General Public License along with
22 this program; if not, write to the Free Software Foundation, Inc., 59 Temple
23 Place, Suite 330, Boston, MA 02111-1307 USA. 
24
25 Based on functions from the base bb module, Copyright 2003 Holger Schurig
26 """
27
28 import os, re
29 import bb
30 from   bb import data
31
32 class FetchError(Exception):
33     """Exception raised when a download fails"""
34
35 class NoMethodError(Exception):
36     """Exception raised when there is no method to obtain a supplied url or set of urls"""
37
38 class MissingParameterError(Exception):
39     """Exception raised when a fetch method is missing a critical parameter in the url"""
40
41 #decodeurl("cvs://anoncvs:anonymous@cvs.handhelds.org/cvs;module=familiar/dist/ipkg;tag=V0-99-81")
42 #('cvs', 'cvs.handhelds.org', '/cvs', 'anoncvs', 'anonymous', {'tag': 'V0-99-81', 'module': 'familiar/dist/ipkg'})
43
44 def uri_replace(uri, uri_find, uri_replace, d):
45 #   bb.note("uri_replace: operating on %s" % uri)
46     if not uri or not uri_find or not uri_replace:
47         bb.debug(1, "uri_replace: passed an undefined value, not replacing")
48     uri_decoded = list(bb.decodeurl(uri))
49     uri_find_decoded = list(bb.decodeurl(uri_find))
50     uri_replace_decoded = list(bb.decodeurl(uri_replace))
51     result_decoded = ['','','','','',{}]
52     for i in uri_find_decoded:
53         loc = uri_find_decoded.index(i)
54         result_decoded[loc] = uri_decoded[loc]
55         import types
56         if type(i) == types.StringType:
57             import re
58             if (re.match(i, uri_decoded[loc])):
59                 result_decoded[loc] = re.sub(i, uri_replace_decoded[loc], uri_decoded[loc])
60                 if uri_find_decoded.index(i) == 2:
61                     if d:
62                         localfn = bb.fetch.localpath(uri, d)
63                         if localfn:
64                             result_decoded[loc] = os.path.dirname(result_decoded[loc]) + "/" + os.path.basename(bb.fetch.localpath(uri, d))
65 #                       bb.note("uri_replace: matching %s against %s and replacing with %s" % (i, uri_decoded[loc], uri_replace_decoded[loc]))
66             else:
67 #               bb.note("uri_replace: no match")
68                 return uri
69 #           else:
70 #               for j in i.keys():
71 #                   FIXME: apply replacements against options
72     return bb.encodeurl(result_decoded)
73
74 methods = []
75
76 def init(urls = [], d = None):
77     if d == None:
78         bb.debug(2,"BUG init called with None as data object!!!")
79         return
80
81     for m in methods:
82         m.urls = []
83
84     for u in urls:
85         for m in methods:
86             m.data = d
87             if m.supports(u, d):
88                 m.urls.append(u)
89
90 def go(d):
91     """Fetch all urls"""
92     for m in methods:
93         if m.urls:
94             m.go(d)
95
96 def localpaths(d):
97     """Return a list of the local filenames, assuming successful fetch"""
98     local = []
99     for m in methods:
100         for u in m.urls:
101             local.append(m.localpath(u, d))
102     return local
103
104 def localpath(url, d):
105     for m in methods:
106         if m.supports(url, d):
107             return m.localpath(url, d)
108     return url
109
110 class Fetch(object):
111     """Base class for 'fetch'ing data"""
112
113     def __init__(self, urls = []):
114         self.urls = []
115         for url in urls:
116             if self.supports(bb.decodeurl(url), d) is 1:
117                 self.urls.append(url)
118
119     def supports(url, d):
120         """Check to see if this fetch class supports a given url.
121            Expects supplied url in list form, as outputted by bb.decodeurl().
122         """
123         return 0
124     supports = staticmethod(supports)
125
126     def localpath(url, d):
127         """Return the local filename of a given url assuming a successful fetch.
128         """
129         return url
130     localpath = staticmethod(localpath)
131
132     def setUrls(self, urls):
133         self.__urls = urls
134
135     def getUrls(self):
136         return self.__urls
137
138     urls = property(getUrls, setUrls, None, "Urls property")
139
140     def setData(self, data):
141         self.__data = data
142
143     def getData(self):
144         return self.__data
145
146     data = property(getData, setData, None, "Data property")
147
148     def go(self, urls = []):
149         """Fetch urls"""
150         raise NoMethodError("Missing implementation for url")
151
152 class Wget(Fetch):
153     """Class to fetch urls via 'wget'"""
154     def supports(url, d):
155         """Check to see if a given url can be fetched using wget.
156            Expects supplied url in list form, as outputted by bb.decodeurl().
157         """
158         (type, host, path, user, pswd, parm) = bb.decodeurl(data.expand(url, d))
159         return type in ['http','https','ftp']
160     supports = staticmethod(supports)
161
162     def localpath(url, d):
163 #       strip off parameters
164         (type, host, path, user, pswd, parm) = bb.decodeurl(data.expand(url, d))
165         if "localpath" in parm:
166 #           if user overrides local path, use it.
167             return parm["localpath"]
168         url = bb.encodeurl([type, host, path, user, pswd, {}])
169
170         return os.path.join(data.getVar("DL_DIR", d), os.path.basename(url))
171     localpath = staticmethod(localpath)
172
173     def go(self, d, urls = []):
174         """Fetch urls"""
175         def fetch_uri(uri, basename, dl, md5, d):
176             if os.path.exists(dl):
177 #               file exists, but we didnt complete it.. trying again..
178                 fetchcmd = data.getVar("RESUMECOMMAND", d, 1)
179             else:
180                 fetchcmd = data.getVar("FETCHCOMMAND", d, 1)
181
182             bb.note("fetch " + uri)
183             fetchcmd = fetchcmd.replace("${URI}", uri)
184             fetchcmd = fetchcmd.replace("${FILE}", basename)
185             bb.debug(2, "executing " + fetchcmd)
186             ret = os.system(fetchcmd)
187             if ret != 0:
188                 return False
189
190             # check if sourceforge did send us to the mirror page
191             dl_dir = data.getVar("DL_DIR", d, True)
192             if not os.path.exists(dl):
193                 os.system("rm %s*" % dl) # FIXME shell quote it
194                 bb.debug(2,"sourceforge.net send us to the mirror on %s" % basename)
195                 return False
196
197 #           supposedly complete.. write out md5sum
198             if bb.which(data.getVar('PATH', d), 'md5sum'):
199                 try:
200                     md5pipe = os.popen('md5sum ' + dl)
201                     md5data = (md5pipe.readline().split() or [ "" ])[0]
202                     md5pipe.close()
203                 except OSError:
204                     md5data = ""
205                 md5out = file(md5, 'w')
206                 md5out.write(md5data)
207                 md5out.close()
208             else:
209                 md5out = file(md5, 'w')
210                 md5out.write("")
211                 md5out.close()
212             return True
213
214         if not urls:
215             urls = self.urls
216
217         localdata = data.createCopy(d)
218         data.setVar('OVERRIDES', "wget:" + data.getVar('OVERRIDES', localdata), localdata)
219         data.update_data(localdata)
220
221         for uri in urls:
222             completed = 0
223             (type, host, path, user, pswd, parm) = bb.decodeurl(data.expand(uri, localdata))
224             basename = os.path.basename(path)
225             dl = self.localpath(uri, d)
226             dl = data.expand(dl, localdata)
227             md5 = dl + '.md5'
228
229             if os.path.exists(md5):
230 #               complete, nothing to see here..
231                 continue
232
233             premirrors = [ i.split() for i in (data.getVar('PREMIRRORS', localdata, 1) or "").split('\n') if i ]
234             for (find, replace) in premirrors:
235                 newuri = uri_replace(uri, find, replace, d)
236                 if newuri != uri:
237                     if fetch_uri(newuri, basename, dl, md5, localdata):
238                         completed = 1
239                         break
240
241             if completed:
242                 continue
243
244             if fetch_uri(uri, basename, dl, md5, localdata):
245                 continue
246
247 #           try mirrors
248             mirrors = [ i.split() for i in (data.getVar('MIRRORS', localdata, 1) or "").split('\n') if i ]
249             for (find, replace) in mirrors:
250                 newuri = uri_replace(uri, find, replace, d)
251                 if newuri != uri:
252                     if fetch_uri(newuri, basename, dl, md5, localdata):
253                         completed = 1
254                         break
255
256             if not completed:
257                 raise FetchError(uri)
258
259         del localdata
260
261
262 methods.append(Wget())
263
264 class Cvs(Fetch):
265     """Class to fetch a module or modules from cvs repositories"""
266     def supports(url, d):
267         """Check to see if a given url can be fetched with cvs.
268            Expects supplied url in list form, as outputted by bb.decodeurl().
269         """
270         (type, host, path, user, pswd, parm) = bb.decodeurl(data.expand(url, d))
271         return type in ['cvs', 'pserver']
272     supports = staticmethod(supports)
273
274     def localpath(url, d):
275         (type, host, path, user, pswd, parm) = bb.decodeurl(data.expand(url, d))
276         if "localpath" in parm:
277 #           if user overrides local path, use it.
278             return parm["localpath"]
279
280         if not "module" in parm:
281             raise MissingParameterError("cvs method needs a 'module' parameter")
282         else:
283             module = parm["module"]
284         if 'tag' in parm:
285             tag = parm['tag']
286         else:
287             tag = ""
288         if 'date' in parm:
289             date = parm['date']
290         else:
291             if not tag:
292                 date = data.getVar("CVSDATE", d, 1) or data.getVar("DATE", d, 1)
293             else:
294                 date = ""
295
296         return os.path.join(data.getVar("DL_DIR", d, 1),data.expand('%s_%s_%s_%s.tar.gz' % ( module.replace('/', '.'), host, tag, date), d))
297     localpath = staticmethod(localpath)
298
299     def go(self, d, urls = []):
300         """Fetch urls"""
301         if not urls:
302             urls = self.urls
303
304         localdata = data.createCopy(d)
305         data.setVar('OVERRIDES', "cvs:%s" % data.getVar('OVERRIDES', localdata), localdata)
306         data.update_data(localdata)
307
308         for loc in urls:
309             (type, host, path, user, pswd, parm) = bb.decodeurl(data.expand(loc, localdata))
310             if not "module" in parm:
311                 raise MissingParameterError("cvs method needs a 'module' parameter")
312             else:
313                 module = parm["module"]
314
315             dlfile = self.localpath(loc, localdata)
316             dldir = data.getVar('DL_DIR', localdata, 1)
317 #           if local path contains the cvs
318 #           module, consider the dir above it to be the
319 #           download directory
320 #           pos = dlfile.find(module)
321 #           if pos:
322 #               dldir = dlfile[:pos]
323 #           else:
324 #               dldir = os.path.dirname(dlfile)
325
326 #           setup cvs options
327             options = []
328             if 'tag' in parm:
329                 tag = parm['tag']
330             else:
331                 tag = ""
332
333             if 'date' in parm:
334                 date = parm['date']
335             else:
336                 if not tag:
337                     date = data.getVar("CVSDATE", d, 1) or data.getVar("DATE", d, 1)
338                 else:
339                     date = ""
340
341             if "method" in parm:
342                 method = parm["method"]
343             else:
344                 method = "pserver"
345
346             if "localdir" in parm:
347                 localdir = parm["localdir"]
348             else:
349                 localdir = module
350
351             cvs_rsh = None
352             if method == "ext":
353                 if "rsh" in parm:
354                     cvs_rsh = parm["rsh"]
355
356             tarfn = data.expand('%s_%s_%s_%s.tar.gz' % (module.replace('/', '.'), host, tag, date), localdata)
357             data.setVar('TARFILES', dlfile, localdata)
358             data.setVar('TARFN', tarfn, localdata)
359
360             dl = os.path.join(dldir, tarfn)
361             if os.access(dl, os.R_OK):
362                 bb.debug(1, "%s already exists, skipping cvs checkout." % tarfn)
363                 continue
364
365             pn = data.getVar('PN', d, 1)
366             cvs_tarball_stash = None
367             if pn:
368                 cvs_tarball_stash = data.getVar('CVS_TARBALL_STASH_%s' % pn, d, 1)
369             if cvs_tarball_stash == None:
370                 cvs_tarball_stash = data.getVar('CVS_TARBALL_STASH', d, 1)
371             if cvs_tarball_stash:
372                 fetchcmd = data.getVar("FETCHCOMMAND_wget", d, 1)
373                 uri = cvs_tarball_stash + tarfn
374                 bb.note("fetch " + uri)
375                 fetchcmd = fetchcmd.replace("${URI}", uri)
376                 ret = os.system(fetchcmd)
377                 if ret == 0:
378                     bb.note("Fetched %s from tarball stash, skipping checkout" % tarfn)
379                     continue
380
381             if date:
382                 options.append("-D %s" % date)
383             if tag:
384                 options.append("-r %s" % tag)
385
386             olddir = os.path.abspath(os.getcwd())
387             os.chdir(data.expand(dldir, localdata))
388
389 #           setup cvsroot
390             if method == "dir":
391                 cvsroot = path
392             else:
393                 cvsroot = ":" + method + ":" + user
394                 if pswd:
395                     cvsroot += ":" + pswd
396                 cvsroot += "@" + host + ":" + path
397
398             data.setVar('CVSROOT', cvsroot, localdata)
399             data.setVar('CVSCOOPTS', " ".join(options), localdata)
400             data.setVar('CVSMODULE', module, localdata)
401             cvscmd = data.getVar('FETCHCOMMAND', localdata, 1)
402             cvsupdatecmd = data.getVar('UPDATECOMMAND', localdata, 1)
403
404             if cvs_rsh:
405                 cvscmd = "CVS_RSH=\"%s\" %s" % (cvs_rsh, cvscmd)
406                 cvsupdatecmd = "CVS_RSH=\"%s\" %s" % (cvs_rsh, cvsupdatecmd)
407
408 #           create module directory
409             bb.debug(2, "Fetch: checking for module directory")
410             pkg=data.expand('${PN}', d)
411             pkgdir=os.path.join(data.expand('${CVSDIR}', localdata), pkg)
412             moddir=os.path.join(pkgdir,localdir)
413             if os.access(os.path.join(moddir,'CVS'), os.R_OK):
414                 bb.note("Update " + loc)
415 #               update sources there
416                 os.chdir(moddir)
417                 myret = os.system(cvsupdatecmd)
418             else:
419                 bb.note("Fetch " + loc)
420 #               check out sources there
421                 bb.mkdirhier(pkgdir)
422                 os.chdir(pkgdir)
423                 bb.debug(1, "Running %s" % cvscmd)
424                 myret = os.system(cvscmd)
425
426             if myret != 0:
427                 try:
428                     os.rmdir(moddir)
429                 except OSError:
430                     pass
431                 raise FetchError(module)
432
433             os.chdir(moddir)
434             os.chdir('..')
435 #           tar them up to a defined filename
436             myret = os.system("tar -czf %s %s" % (os.path.join(dldir,tarfn), os.path.basename(moddir)))
437             if myret != 0:
438                 try:
439                     os.unlink(tarfn)
440                 except OSError:
441                     pass
442             os.chdir(olddir)
443         del localdata
444
445 methods.append(Cvs())
446
447 class Bk(Fetch):
448     def supports(url, d):
449         """Check to see if a given url can be fetched via bitkeeper.
450            Expects supplied url in list form, as outputted by bb.decodeurl().
451         """
452         (type, host, path, user, pswd, parm) = bb.decodeurl(data.expand(url, d))
453         return type in ['bk']
454     supports = staticmethod(supports)
455
456 methods.append(Bk())
457
458 class Local(Fetch):
459     def supports(url, d):
460         """Check to see if a given url can be fetched in the local filesystem.
461            Expects supplied url in list form, as outputted by bb.decodeurl().
462         """
463         (type, host, path, user, pswd, parm) = bb.decodeurl(data.expand(url, d))
464         return type in ['file','patch']
465     supports = staticmethod(supports)
466
467     def localpath(url, d):
468         """Return the local filename of a given url assuming a successful fetch.
469         """
470         path = url.split("://")[1]
471         newpath = path
472         if path[0] != "/":
473             filespath = data.getVar('FILESPATH', d, 1)
474             if filespath:
475                 newpath = bb.which(filespath, path)
476             if not newpath:
477                 filesdir = data.getVar('FILESDIR', d, 1)
478                 if filesdir:
479                     newpath = os.path.join(filesdir, path)
480         return newpath
481     localpath = staticmethod(localpath)
482
483     def go(self, urls = []):
484         """Fetch urls (no-op for Local method)"""
485 #       no need to fetch local files, we'll deal with them in place.
486         return 1
487
488 methods.append(Local())
489
490 class Svn(Fetch):
491     """Class to fetch a module or modules from svn repositories"""
492     def supports(url, d):
493         """Check to see if a given url can be fetched with svn.
494            Expects supplied url in list form, as outputted by bb.decodeurl().
495         """
496         (type, host, path, user, pswd, parm) = bb.decodeurl(data.expand(url, d))
497         return type in ['svn']
498     supports = staticmethod(supports)
499
500     def localpath(url, d):
501         (type, host, path, user, pswd, parm) = bb.decodeurl(data.expand(url, d))
502         if "localpath" in parm:
503 #           if user overrides local path, use it.
504             return parm["localpath"]
505
506         if not "module" in parm:
507             raise MissingParameterError("svn method needs a 'module' parameter")
508         else:
509             module = parm["module"]
510         if 'rev' in parm:
511             revision = parm['rev']
512         else:
513             revision = ""
514
515         date = data.getVar("CVSDATE", d, 1) or data.getVar("DATE", d, 1)
516
517         return os.path.join(data.getVar("DL_DIR", d, 1),data.expand('%s_%s_%s_%s.tar.gz' % ( module.replace('/', '.'), host, revision, date), d))
518     localpath = staticmethod(localpath)
519
520     def go(self, d, urls = []):
521         """Fetch urls"""
522         if not urls:
523             urls = self.urls
524
525         localdata = data.createCopy(d)
526         data.setVar('OVERRIDES', "svn:%s" % data.getVar('OVERRIDES', localdata), localdata)
527         data.update_data(localdata)
528
529         for loc in urls:
530             (type, host, path, user, pswd, parm) = bb.decodeurl(data.expand(loc, localdata))
531             if not "module" in parm:
532                 raise MissingParameterError("svn method needs a 'module' parameter")
533             else:
534                 module = parm["module"]
535
536             dlfile = self.localpath(loc, localdata)
537             dldir = data.getVar('DL_DIR', localdata, 1)
538 #           if local path contains the svn
539 #           module, consider the dir above it to be the
540 #           download directory
541 #           pos = dlfile.find(module)
542 #           if pos:
543 #               dldir = dlfile[:pos]
544 #           else:
545 #               dldir = os.path.dirname(dlfile)
546
547 #           setup svn options
548             options = []
549             if 'rev' in parm:
550                 revision = parm['rev']
551             else:
552                 revision = ""
553
554             date = data.getVar("CVSDATE", d, 1) or data.getVar("DATE", d, 1)
555
556             if "method" in parm:
557                 method = parm["method"]
558             else:
559                 method = "pserver"
560
561             if "proto" in parm:
562                 proto = parm["proto"]
563             else:
564                 proto = "svn"
565
566             svn_rsh = None
567             if method == "ext":
568                 if "rsh" in parm:
569                     svn_rsh = parm["rsh"]
570
571             tarfn = data.expand('%s_%s_%s_%s.tar.gz' % (module.replace('/', '.'), host, revision, date), localdata)
572             data.setVar('TARFILES', dlfile, localdata)
573             data.setVar('TARFN', tarfn, localdata)
574
575             dl = os.path.join(dldir, tarfn)
576             if os.access(dl, os.R_OK):
577                 bb.debug(1, "%s already exists, skipping svn checkout." % tarfn)
578                 continue
579
580             svn_tarball_stash = data.getVar('CVS_TARBALL_STASH', d, 1)
581             if svn_tarball_stash:
582                 fetchcmd = data.getVar("FETCHCOMMAND_wget", d, 1)
583                 uri = svn_tarball_stash + tarfn
584                 bb.note("fetch " + uri)
585                 fetchcmd = fetchcmd.replace("${URI}", uri)
586                 ret = os.system(fetchcmd)
587                 if ret == 0:
588                     bb.note("Fetched %s from tarball stash, skipping checkout" % tarfn)
589                     continue
590
591             olddir = os.path.abspath(os.getcwd())
592             os.chdir(data.expand(dldir, localdata))
593
594 #           setup svnroot
595 #            svnroot = ":" + method + ":" + user
596 #            if pswd:
597 #                svnroot += ":" + pswd
598             svnroot = host + path
599
600             data.setVar('SVNROOT', svnroot, localdata)
601             data.setVar('SVNCOOPTS', " ".join(options), localdata)
602             data.setVar('SVNMODULE', module, localdata)
603             svncmd = data.getVar('FETCHCOMMAND', localdata, 1)
604             svncmd = "svn co %s://%s/%s" % (proto, svnroot, module)
605
606             if revision:
607                 svncmd = "svn co -r %s %s://%s/%s" % (revision, proto, svnroot, module)
608             if svn_rsh:
609                 svncmd = "svn_RSH=\"%s\" %s" % (svn_rsh, svncmd)
610
611 #           create temp directory
612             bb.debug(2, "Fetch: creating temporary directory")
613             bb.mkdirhier(data.expand('${WORKDIR}', localdata))
614             data.setVar('TMPBASE', data.expand('${WORKDIR}/oesvn.XXXXXX', localdata), localdata)
615             tmppipe = os.popen(data.getVar('MKTEMPDIRCMD', localdata, 1) or "false")
616             tmpfile = tmppipe.readline().strip()
617             if not tmpfile:
618                 bb.error("Fetch: unable to create temporary directory.. make sure 'mktemp' is in the PATH.")
619                 raise FetchError(module)
620
621 #           check out sources there
622             os.chdir(tmpfile)
623             bb.note("Fetch " + loc)
624             bb.debug(1, "Running %s" % svncmd)
625             myret = os.system(svncmd)
626             if myret != 0:
627                 try:
628                     os.rmdir(tmpfile)
629                 except OSError:
630                     pass
631                 raise FetchError(module)
632
633             os.chdir(os.path.join(tmpfile, os.path.dirname(module)))
634 #           tar them up to a defined filename
635             myret = os.system("tar -czf %s %s" % (os.path.join(dldir,tarfn), os.path.basename(module)))
636             if myret != 0:
637                 try:
638                     os.unlink(tarfn)
639                 except OSError:
640                     pass
641 #           cleanup
642             os.system('rm -rf %s' % tmpfile)
643             os.chdir(olddir)
644         del localdata
645
646 methods.append(Svn())