bitbake/lib/bb/fetch.py
[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     for m in methods:
78         m.urls = []
79
80     for u in urls:
81         for m in methods:
82             m.data = d
83             if m.supports(u, d):
84                 m.urls.append(u)
85
86 def go(d):
87     """Fetch all urls"""
88     for m in methods:
89         if m.urls:
90             m.go(d)
91
92 def localpaths(d):
93     """Return a list of the local filenames, assuming successful fetch"""
94     local = []
95     for m in methods:
96         for u in m.urls:
97             local.append(m.localpath(u, d))
98     return local
99
100 def localpath(url, d):
101     for m in methods:
102         if m.supports(url, d):
103             return m.localpath(url, d)
104     return url
105
106 class Fetch(object):
107     """Base class for 'fetch'ing data"""
108
109     def __init__(self, urls = []):
110         self.urls = []
111         for url in urls:
112             if self.supports(bb.decodeurl(url), d) is 1:
113                 self.urls.append(url)
114
115     def supports(url, d):
116         """Check to see if this fetch class supports a given url.
117            Expects supplied url in list form, as outputted by bb.decodeurl().
118         """
119         return 0
120     supports = staticmethod(supports)
121
122     def localpath(url, d):
123         """Return the local filename of a given url assuming a successful fetch.
124         """
125         return url
126     localpath = staticmethod(localpath)
127
128     def setUrls(self, urls):
129         self.__urls = urls
130
131     def getUrls(self):
132         return self.__urls
133
134     urls = property(getUrls, setUrls, None, "Urls property")
135
136     def setData(self, data):
137         self.__data = data
138
139     def getData(self):
140         return self.__data
141
142     data = property(getData, setData, None, "Data property")
143
144     def go(self, urls = []):
145         """Fetch urls"""
146         raise NoMethodError("Missing implementation for url")
147
148 class Wget(Fetch):
149     """Class to fetch urls via 'wget'"""
150     def supports(url, d):
151         """Check to see if a given url can be fetched using wget.
152            Expects supplied url in list form, as outputted by bb.decodeurl().
153         """
154         (type, host, path, user, pswd, parm) = bb.decodeurl(data.expand(url, d))
155         return type in ['http','https','ftp']
156     supports = staticmethod(supports)
157
158     def localpath(url, d):
159 #       strip off parameters
160         (type, host, path, user, pswd, parm) = bb.decodeurl(data.expand(url, d))
161         if "localpath" in parm:
162 #           if user overrides local path, use it.
163             return parm["localpath"]
164         url = bb.encodeurl([type, host, path, user, pswd, {}])
165
166         return os.path.join(data.getVar("DL_DIR", d), os.path.basename(url))
167     localpath = staticmethod(localpath)
168
169     def go(self, d, urls = []):
170         """Fetch urls"""
171         def fetch_uri(uri, basename, dl, md5, d):
172             if os.path.exists(dl):
173 #               file exists, but we didnt complete it.. trying again..
174                 fetchcmd = data.getVar("RESUMECOMMAND", d, 1)
175             else:
176                 fetchcmd = data.getVar("FETCHCOMMAND", d, 1)
177
178             bb.note("fetch " + uri)
179             fetchcmd = fetchcmd.replace("${URI}", uri)
180             fetchcmd = fetchcmd.replace("${FILE}", basename)
181             bb.debug(2, "executing " + fetchcmd)
182             ret = os.system(fetchcmd)
183             if ret != 0:
184                 return False
185
186 #           supposedly complete.. write out md5sum
187             if bb.which(data.getVar('PATH', d), 'md5sum'):
188                 try:
189                     md5pipe = os.popen('md5sum ' + dl)
190                     md5data = (md5pipe.readline().split() or [ "" ])[0]
191                     md5pipe.close()
192                 except OSError:
193                     md5data = ""
194                 md5out = file(md5, 'w')
195                 md5out.write(md5data)
196                 md5out.close()
197             else:
198                 md5out = file(md5, 'w')
199                 md5out.write("")
200                 md5out.close()
201             return True
202
203         if not urls:
204             urls = self.urls
205
206         localdata = data.createCopy(d)
207         data.setVar('OVERRIDES', "wget:" + data.getVar('OVERRIDES', localdata), localdata)
208         data.update_data(localdata)
209
210         for uri in urls:
211             completed = 0
212             (type, host, path, user, pswd, parm) = bb.decodeurl(data.expand(uri, localdata))
213             basename = os.path.basename(path)
214             dl = self.localpath(uri, d)
215             dl = data.expand(dl, localdata)
216             md5 = dl + '.md5'
217
218             if os.path.exists(md5):
219 #               complete, nothing to see here..
220                 continue
221
222             premirrors = [ i.split() for i in (data.getVar('PREMIRRORS', localdata, 1) or "").split('\n') if i ]
223             for (find, replace) in premirrors:
224                 newuri = uri_replace(uri, find, replace, d)
225                 if newuri != uri:
226                     if fetch_uri(newuri, basename, dl, md5, localdata):
227                         completed = 1
228                         break
229
230             if completed:
231                 continue
232
233             if fetch_uri(uri, basename, dl, md5, localdata):
234                 continue
235
236 #           try mirrors
237             mirrors = [ i.split() for i in (data.getVar('MIRRORS', localdata, 1) or "").split('\n') if i ]
238             for (find, replace) in mirrors:
239                 newuri = uri_replace(uri, find, replace, d)
240                 if newuri != uri:
241                     if fetch_uri(newuri, basename, dl, md5, localdata):
242                         completed = 1
243                         break
244
245             if not completed:
246                 raise FetchError(uri)
247
248         del localdata
249
250
251 methods.append(Wget())
252
253 class Cvs(Fetch):
254     """Class to fetch a module or modules from cvs repositories"""
255     def supports(url, d):
256         """Check to see if a given url can be fetched with cvs.
257            Expects supplied url in list form, as outputted by bb.decodeurl().
258         """
259         (type, host, path, user, pswd, parm) = bb.decodeurl(data.expand(url, d))
260         return type in ['cvs', 'pserver']
261     supports = staticmethod(supports)
262
263     def localpath(url, d):
264         (type, host, path, user, pswd, parm) = bb.decodeurl(data.expand(url, d))
265         if "localpath" in parm:
266 #           if user overrides local path, use it.
267             return parm["localpath"]
268
269         if not "module" in parm:
270             raise MissingParameterError("cvs method needs a 'module' parameter")
271         else:
272             module = parm["module"]
273         if 'tag' in parm:
274             tag = parm['tag']
275         else:
276             tag = ""
277         if 'date' in parm:
278             date = parm['date']
279         else:
280             if not tag:
281                 date = data.getVar("CVSDATE", d, 1) or data.getVar("DATE", d, 1)
282             else:
283                 date = ""
284
285         return os.path.join(data.getVar("DL_DIR", d, 1),data.expand('%s_%s_%s_%s.tar.gz' % ( module.replace('/', '.'), host, tag, date), d))
286     localpath = staticmethod(localpath)
287
288     def go(self, d, urls = []):
289         """Fetch urls"""
290         if not urls:
291             urls = self.urls
292
293         localdata = data.createCopy(d)
294         data.setVar('OVERRIDES', "cvs:%s" % data.getVar('OVERRIDES', localdata), localdata)
295         data.update_data(localdata)
296
297         for loc in urls:
298             (type, host, path, user, pswd, parm) = bb.decodeurl(data.expand(loc, localdata))
299             if not "module" in parm:
300                 raise MissingParameterError("cvs method needs a 'module' parameter")
301             else:
302                 module = parm["module"]
303
304             dlfile = self.localpath(loc, localdata)
305             dldir = data.getVar('DL_DIR', localdata, 1)
306 #           if local path contains the cvs
307 #           module, consider the dir above it to be the
308 #           download directory
309 #           pos = dlfile.find(module)
310 #           if pos:
311 #               dldir = dlfile[:pos]
312 #           else:
313 #               dldir = os.path.dirname(dlfile)
314
315 #           setup cvs options
316             options = []
317             if 'tag' in parm:
318                 tag = parm['tag']
319             else:
320                 tag = ""
321
322             if 'date' in parm:
323                 date = parm['date']
324             else:
325                 if not tag:
326                     date = data.getVar("CVSDATE", d, 1) or data.getVar("DATE", d, 1)
327                 else:
328                     date = ""
329
330             if "method" in parm:
331                 method = parm["method"]
332             else:
333                 method = "pserver"
334
335             if "localdir" in parm:
336                 localdir = parm["localdir"]
337             else:
338                 localdir = module
339
340             cvs_rsh = None
341             if method == "ext":
342                 if "rsh" in parm:
343                     cvs_rsh = parm["rsh"]
344
345             tarfn = data.expand('%s_%s_%s_%s.tar.gz' % (module.replace('/', '.'), host, tag, date), localdata)
346             data.setVar('TARFILES', dlfile, localdata)
347             data.setVar('TARFN', tarfn, localdata)
348
349             dl = os.path.join(dldir, tarfn)
350             if os.access(dl, os.R_OK):
351                 bb.debug(1, "%s already exists, skipping cvs checkout." % tarfn)
352                 continue
353
354             pn = data.getVar('PN', d, 1)
355             cvs_tarball_stash = None
356             if pn:
357                 cvs_tarball_stash = data.getVar('CVS_TARBALL_STASH_%s' % pn, d, 1)
358             if cvs_tarball_stash == None:
359                 cvs_tarball_stash = data.getVar('CVS_TARBALL_STASH', d, 1)
360             if cvs_tarball_stash:
361                 fetchcmd = data.getVar("FETCHCOMMAND_wget", d, 1)
362                 uri = cvs_tarball_stash + tarfn
363                 bb.note("fetch " + uri)
364                 fetchcmd = fetchcmd.replace("${URI}", uri)
365                 ret = os.system(fetchcmd)
366                 if ret == 0:
367                     bb.note("Fetched %s from tarball stash, skipping checkout" % tarfn)
368                     continue
369
370             if date:
371                 options.append("-D %s" % date)
372             if tag:
373                 options.append("-r %s" % tag)
374
375             olddir = os.path.abspath(os.getcwd())
376             os.chdir(data.expand(dldir, localdata))
377
378 #           setup cvsroot
379             if method == "dir":
380                 cvsroot = path
381             else:
382                 cvsroot = ":" + method + ":" + user
383                 if pswd:
384                     cvsroot += ":" + pswd
385                 cvsroot += "@" + host + ":" + path
386
387             data.setVar('CVSROOT', cvsroot, localdata)
388             data.setVar('CVSCOOPTS', " ".join(options), localdata)
389             data.setVar('CVSMODULE', module, localdata)
390             cvscmd = data.getVar('FETCHCOMMAND', localdata, 1)
391             cvsupdatecmd = data.getVar('UPDATECOMMAND', localdata, 1)
392
393             if cvs_rsh:
394                 cvscmd = "CVS_RSH=\"%s\" %s" % (cvs_rsh, cvscmd)
395                 cvsupdatecmd = "CVS_RSH=\"%s\" %s" % (cvs_rsh, cvsupdatecmd)
396
397 #           create module directory
398             bb.debug(2, "Fetch: checking for module directory")
399             pkg=data.expand('${PN}', d)
400             pkgdir=os.path.join(data.expand('${CVSDIR}', localdata), pkg)
401             moddir=os.path.join(pkgdir,localdir)
402             if os.access(os.path.join(moddir,'CVS'), os.R_OK):
403                 bb.note("Update " + loc)
404 #               update sources there
405                 os.chdir(moddir)
406                 myret = os.system(cvsupdatecmd)
407             else:
408                 bb.note("Fetch " + loc)
409 #               check out sources there
410                 bb.mkdirhier(pkgdir)
411                 os.chdir(pkgdir)
412                 bb.debug(1, "Running %s" % cvscmd)
413                 myret = os.system(cvscmd)
414
415             if myret != 0:
416                 try:
417                     os.rmdir(moddir)
418                 except OSError:
419                     pass
420                 raise FetchError(module)
421
422             os.chdir(moddir)
423             os.chdir('..')
424 #           tar them up to a defined filename
425             myret = os.system("tar -czf %s %s" % (os.path.join(dldir,tarfn), os.path.basename(moddir)))
426             if myret != 0:
427                 try:
428                     os.unlink(tarfn)
429                 except OSError:
430                     pass
431             os.chdir(olddir)
432         del localdata
433
434 methods.append(Cvs())
435
436 class Bk(Fetch):
437     def supports(url, d):
438         """Check to see if a given url can be fetched via bitkeeper.
439            Expects supplied url in list form, as outputted by bb.decodeurl().
440         """
441         (type, host, path, user, pswd, parm) = bb.decodeurl(data.expand(url, d))
442         return type in ['bk']
443     supports = staticmethod(supports)
444
445 methods.append(Bk())
446
447 class Local(Fetch):
448     def supports(url, d):
449         """Check to see if a given url can be fetched in the local filesystem.
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 ['file','patch']
454     supports = staticmethod(supports)
455
456     def localpath(url, d):
457         """Return the local filename of a given url assuming a successful fetch.
458         """
459         path = url.split("://")[1]
460         newpath = path
461         if path[0] != "/":
462             filespath = data.getVar('FILESPATH', d, 1)
463             if filespath:
464                 newpath = bb.which(filespath, path)
465             if not newpath:
466                 filesdir = data.getVar('FILESDIR', d, 1)
467                 if filesdir:
468                     newpath = os.path.join(filesdir, path)
469         return newpath
470     localpath = staticmethod(localpath)
471
472     def go(self, urls = []):
473         """Fetch urls (no-op for Local method)"""
474 #       no need to fetch local files, we'll deal with them in place.
475         return 1
476
477 methods.append(Local())
478
479 class Svn(Fetch):
480     """Class to fetch a module or modules from svn repositories"""
481     def supports(url, d):
482         """Check to see if a given url can be fetched with svn.
483            Expects supplied url in list form, as outputted by bb.decodeurl().
484         """
485         (type, host, path, user, pswd, parm) = bb.decodeurl(data.expand(url, d))
486         return type in ['svn']
487     supports = staticmethod(supports)
488
489     def localpath(url, d):
490         (type, host, path, user, pswd, parm) = bb.decodeurl(data.expand(url, d))
491         if "localpath" in parm:
492 #           if user overrides local path, use it.
493             return parm["localpath"]
494
495         if not "module" in parm:
496             raise MissingParameterError("svn method needs a 'module' parameter")
497         else:
498             module = parm["module"]
499         if 'rev' in parm:
500             revision = parm['rev']
501         else:
502             revision = ""
503
504         date = data.getVar("CVSDATE", d, 1) or data.getVar("DATE", d, 1)
505
506         return os.path.join(data.getVar("DL_DIR", d, 1),data.expand('%s_%s_%s_%s.tar.gz' % ( module.replace('/', '.'), host, revision, date), d))
507     localpath = staticmethod(localpath)
508
509     def go(self, d, urls = []):
510         """Fetch urls"""
511         if not urls:
512             urls = self.urls
513
514         localdata = data.createCopy(d)
515         data.setVar('OVERRIDES', "svn:%s" % data.getVar('OVERRIDES', localdata), localdata)
516         data.update_data(localdata)
517
518         for loc in urls:
519             (type, host, path, user, pswd, parm) = bb.decodeurl(data.expand(loc, localdata))
520             if not "module" in parm:
521                 raise MissingParameterError("svn method needs a 'module' parameter")
522             else:
523                 module = parm["module"]
524
525             dlfile = self.localpath(loc, localdata)
526             dldir = data.getVar('DL_DIR', localdata, 1)
527 #           if local path contains the svn
528 #           module, consider the dir above it to be the
529 #           download directory
530 #           pos = dlfile.find(module)
531 #           if pos:
532 #               dldir = dlfile[:pos]
533 #           else:
534 #               dldir = os.path.dirname(dlfile)
535
536 #           setup svn options
537             options = []
538             if 'rev' in parm:
539                 revision = parm['rev']
540             else:
541                 revision = ""
542
543             date = data.getVar("CVSDATE", d, 1) or data.getVar("DATE", d, 1)
544
545             if "method" in parm:
546                 method = parm["method"]
547             else:
548                 method = "pserver"
549
550             if "proto" in parm:
551                 proto = parm["proto"]
552             else:
553                 proto = "svn"
554
555             svn_rsh = None
556             if method == "ext":
557                 if "rsh" in parm:
558                     svn_rsh = parm["rsh"]
559
560             tarfn = data.expand('%s_%s_%s_%s.tar.gz' % (module.replace('/', '.'), host, revision, date), localdata)
561             data.setVar('TARFILES', dlfile, localdata)
562             data.setVar('TARFN', tarfn, localdata)
563
564             dl = os.path.join(dldir, tarfn)
565             if os.access(dl, os.R_OK):
566                 bb.debug(1, "%s already exists, skipping svn checkout." % tarfn)
567                 continue
568
569             svn_tarball_stash = data.getVar('CVS_TARBALL_STASH', d, 1)
570             if svn_tarball_stash:
571                 fetchcmd = data.getVar("FETCHCOMMAND_wget", d, 1)
572                 uri = svn_tarball_stash + tarfn
573                 bb.note("fetch " + uri)
574                 fetchcmd = fetchcmd.replace("${URI}", uri)
575                 ret = os.system(fetchcmd)
576                 if ret == 0:
577                     bb.note("Fetched %s from tarball stash, skipping checkout" % tarfn)
578                     continue
579
580             olddir = os.path.abspath(os.getcwd())
581             os.chdir(data.expand(dldir, localdata))
582
583 #           setup svnroot
584 #            svnroot = ":" + method + ":" + user
585 #            if pswd:
586 #                svnroot += ":" + pswd
587             svnroot = host + path
588
589             data.setVar('SVNROOT', svnroot, localdata)
590             data.setVar('SVNCOOPTS', " ".join(options), localdata)
591             data.setVar('SVNMODULE', module, localdata)
592             svncmd = data.getVar('FETCHCOMMAND', localdata, 1)
593             svncmd = "svn co %s://%s/%s" % (proto, svnroot, module)
594
595             if revision:
596                 svncmd = "svn co -r %s %s://%s/%s" % (revision, proto, svnroot, module)
597             if svn_rsh:
598                 svncmd = "svn_RSH=\"%s\" %s" % (svn_rsh, svncmd)
599
600 #           create temp directory
601             bb.debug(2, "Fetch: creating temporary directory")
602             bb.mkdirhier(data.expand('${WORKDIR}', localdata))
603             data.setVar('TMPBASE', data.expand('${WORKDIR}/oesvn.XXXXXX', localdata), localdata)
604             tmppipe = os.popen(data.getVar('MKTEMPDIRCMD', localdata, 1) or "false")
605             tmpfile = tmppipe.readline().strip()
606             if not tmpfile:
607                 bb.error("Fetch: unable to create temporary directory.. make sure 'mktemp' is in the PATH.")
608                 raise FetchError(module)
609
610 #           check out sources there
611             os.chdir(tmpfile)
612             bb.note("Fetch " + loc)
613             bb.debug(1, "Running %s" % svncmd)
614             myret = os.system(svncmd)
615             if myret != 0:
616                 try:
617                     os.rmdir(tmpfile)
618                 except OSError:
619                     pass
620                 raise FetchError(module)
621
622             os.chdir(os.path.join(tmpfile, os.path.dirname(module)))
623 #           tar them up to a defined filename
624             myret = os.system("tar -czf %s %s" % (os.path.join(dldir,tarfn), os.path.basename(module)))
625             if myret != 0:
626                 try:
627                     os.unlink(tarfn)
628                 except OSError:
629                     pass
630 #           cleanup
631             os.system('rm -rf %s' % tmpfile)
632             os.chdir(olddir)
633         del localdata
634
635 methods.append(Svn())