Add the location of the .bb file to BBPATH in the parser's handle() function rather...
[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 import bb.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 = bb.data.init()):
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 = bb.data.init()):
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 = bb.data.init()):
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 = bb.data.init()):
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 = bb.data.init()):
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(bb.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(bb.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         return os.path.join(bb.data.getVar("DL_DIR", d), os.path.basename(url))
166     localpath = staticmethod(localpath)
167
168     def go(self, d = bb.data.init(), urls = []):
169         """Fetch urls"""
170         def fetch_uri(uri, basename, dl, md5, d):
171             if os.path.exists(dl):
172 #               file exists, but we didnt complete it.. trying again..
173                 fetchcmd = bb.data.getVar("RESUMECOMMAND", d, 1)
174             else:
175                 fetchcmd = bb.data.getVar("FETCHCOMMAND", d, 1)
176
177             bb.note("fetch " + uri)
178             fetchcmd = fetchcmd.replace("${URI}", uri)
179             fetchcmd = fetchcmd.replace("${FILE}", basename)
180             bb.debug(2, "executing " + fetchcmd)
181             ret = os.system(fetchcmd)
182             if ret != 0:
183                 return False
184
185 #           supposedly complete.. write out md5sum
186             if bb.which(bb.data.getVar('PATH', d), 'md5sum'):
187                 try:
188                     md5pipe = os.popen('md5sum ' + dl)
189                     md5data = (md5pipe.readline().split() or [ "" ])[0]
190                     md5pipe.close()
191                 except OSError:
192                     md5data = ""
193                 md5out = file(md5, 'w')
194                 md5out.write(md5data)
195                 md5out.close()
196             else:
197                 md5out = file(md5, 'w')
198                 md5out.write("")
199                 md5out.close()
200             return True
201
202         if not urls:
203             urls = self.urls
204
205         from copy import deepcopy
206         localdata = deepcopy(d)
207         bb.data.setVar('OVERRIDES', "wget:" + bb.data.getVar('OVERRIDES', localdata), localdata)
208         bb.data.update_data(localdata)
209
210         for uri in urls:
211             completed = 0
212             (type, host, path, user, pswd, parm) = bb.decodeurl(bb.data.expand(uri, localdata))
213             basename = os.path.basename(path)
214             dl = self.localpath(uri, d)
215             dl = bb.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 (bb.data.getVar('PREMIRRORS', localdata, 1) or "").split('\n') if i ]
223             for (find, replace) in premirrors:
224                 newuri = uri_replace(uri, find, replace)
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 (bb.data.getVar('MIRRORS', localdata, 1) or "").split('\n') if i ]
238             for (find, replace) in mirrors:
239                 newuri = uri_replace(uri, find, replace)
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(bb.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(bb.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 = bb.data.getVar("CVSDATE", d, 1) or bb.data.getVar("DATE", d, 1)
282             else:
283                 date = ""
284
285         return os.path.join(bb.data.getVar("DL_DIR", d, 1),bb.data.expand('%s_%s_%s_%s.tar.gz' % ( module.replace('/', '.'), host, tag, date), d))
286     localpath = staticmethod(localpath)
287
288     def go(self, d = bb.data.init(), urls = []):
289         """Fetch urls"""
290         if not urls:
291             urls = self.urls
292
293         from copy import deepcopy
294         localdata = deepcopy(d)
295         bb.data.setVar('OVERRIDES', "cvs:%s" % bb.data.getVar('OVERRIDES', localdata), localdata)
296         bb.data.update_data(localdata)
297
298         for loc in urls:
299             (type, host, path, user, pswd, parm) = bb.decodeurl(bb.data.expand(loc, localdata))
300             if not "module" in parm:
301                 raise MissingParameterError("cvs method needs a 'module' parameter")
302             else:
303                 module = parm["module"]
304
305             dlfile = self.localpath(loc, localdata)
306             dldir = bb.data.getVar('DL_DIR', localdata, 1)
307 #           if local path contains the cvs
308 #           module, consider the dir above it to be the
309 #           download directory
310 #           pos = dlfile.find(module)
311 #           if pos:
312 #               dldir = dlfile[:pos]
313 #           else:
314 #               dldir = os.path.dirname(dlfile)
315
316 #           setup cvs options
317             options = []
318             if 'tag' in parm:
319                 tag = parm['tag']
320             else:
321                 tag = ""
322
323             if 'date' in parm:
324                 date = parm['date']
325             else:
326                 if not tag:
327                     date = bb.data.getVar("CVSDATE", d, 1) or bb.data.getVar("DATE", d, 1)
328                 else:
329                     date = ""
330
331             if "method" in parm:
332                 method = parm["method"]
333             else:
334                 method = "pserver"
335
336             if "localdir" in parm:
337                 localdir = parm["localdir"]
338             else:
339                 localdir = module
340
341             cvs_rsh = None
342             if method == "ext":
343                 if "rsh" in parm:
344                     cvs_rsh = parm["rsh"]
345
346             tarfn = bb.data.expand('%s_%s_%s_%s.tar.gz' % (module.replace('/', '.'), host, tag, date), localdata)
347             bb.data.setVar('TARFILES', dlfile, localdata)
348             bb.data.setVar('TARFN', tarfn, localdata)
349
350             dl = os.path.join(dldir, tarfn)
351             if os.access(dl, os.R_OK):
352                 bb.debug(1, "%s already exists, skipping cvs checkout." % tarfn)
353                 continue
354
355             pn = bb.data.getVar('PN', d, 1)
356             cvs_tarball_stash = None
357             if pn:
358                 cvs_tarball_stash = bb.data.getVar('CVS_TARBALL_STASH_%s' % pn, d, 1)
359             if cvs_tarball_stash == None:
360                 cvs_tarball_stash = bb.data.getVar('CVS_TARBALL_STASH', d, 1)
361             if cvs_tarball_stash:
362                 fetchcmd = bb.data.getVar("FETCHCOMMAND_wget", d, 1)
363                 uri = cvs_tarball_stash + tarfn
364                 bb.note("fetch " + uri)
365                 fetchcmd = fetchcmd.replace("${URI}", uri)
366                 ret = os.system(fetchcmd)
367                 if ret == 0:
368                     bb.note("Fetched %s from tarball stash, skipping checkout" % tarfn)
369                     continue
370
371             if date:
372                 options.append("-D %s" % date)
373             if tag:
374                 options.append("-r %s" % tag)
375
376             olddir = os.path.abspath(os.getcwd())
377             os.chdir(bb.data.expand(dldir, localdata))
378
379 #           setup cvsroot
380             if method == "dir":
381                 cvsroot = path
382             else:
383                 cvsroot = ":" + method + ":" + user
384                 if pswd:
385                     cvsroot += ":" + pswd
386                 cvsroot += "@" + host + ":" + path
387
388             bb.data.setVar('CVSROOT', cvsroot, localdata)
389             bb.data.setVar('CVSCOOPTS', " ".join(options), localdata)
390             bb.data.setVar('CVSMODULE', module, localdata)
391             cvscmd = bb.data.getVar('FETCHCOMMAND', localdata, 1)
392             cvsupdatecmd = bb.data.getVar('UPDATECOMMAND', localdata, 1)
393
394             if cvs_rsh:
395                 cvscmd = "CVS_RSH=\"%s\" %s" % (cvs_rsh, cvscmd)
396                 cvsupdatecmd = "CVS_RSH=\"%s\" %s" % (cvs_rsh, cvsupdatecmd)
397
398 #           create module directory
399             bb.debug(2, "Fetch: checking for module directory")
400             pkg=bb.data.expand('${PN}', d)
401             pkgdir=os.path.join(bb.data.expand('${CVSDIR}', localdata), pkg)
402             moddir=os.path.join(pkgdir,localdir)
403             if os.access(os.path.join(moddir,'CVS'), os.R_OK):
404                 bb.note("Update " + loc)
405 #               update sources there
406                 os.chdir(moddir)
407                 myret = os.system(cvsupdatecmd)
408             else:
409                 bb.note("Fetch " + loc)
410 #               check out sources there
411                 bb.mkdirhier(pkgdir)
412                 os.chdir(pkgdir)
413                 bb.debug(1, "Running %s" % cvscmd)
414                 myret = os.system(cvscmd)
415
416             if myret != 0:
417                 try:
418                     os.rmdir(moddir)
419                 except OSError:
420                     pass
421                 raise FetchError(module)
422
423             os.chdir(moddir)
424             os.chdir('..')
425 #           tar them up to a defined filename
426             myret = os.system("tar -czf %s %s" % (os.path.join(dldir,tarfn), os.path.basename(moddir)))
427             if myret != 0:
428                 try:
429                     os.unlink(tarfn)
430                 except OSError:
431                     pass
432             os.chdir(olddir)
433         del localdata
434
435 methods.append(Cvs())
436
437 class Bk(Fetch):
438     def supports(url, d):
439         """Check to see if a given url can be fetched via bitkeeper.
440            Expects supplied url in list form, as outputted by bb.decodeurl().
441         """
442         (type, host, path, user, pswd, parm) = bb.decodeurl(bb.data.expand(url, d))
443         return type in ['bk']
444     supports = staticmethod(supports)
445
446 methods.append(Bk())
447
448 class Local(Fetch):
449     def supports(url, d):
450         """Check to see if a given url can be fetched in the local filesystem.
451            Expects supplied url in list form, as outputted by bb.decodeurl().
452         """
453         (type, host, path, user, pswd, parm) = bb.decodeurl(bb.data.expand(url, d))
454         return type in ['file','patch']
455     supports = staticmethod(supports)
456
457     def localpath(url, d):
458         """Return the local filename of a given url assuming a successful fetch.
459         """
460         path = url.split("://")[1]
461         newpath = path
462         if path[0] != "/":
463             filespath = bb.data.getVar('FILESPATH', d, 1)
464             if filespath:
465                 newpath = bb.which(filespath, path)
466             if not newpath:
467                 filesdir = bb.data.getVar('FILESDIR', d, 1)
468                 if filesdir:
469                     newpath = os.path.join(filesdir, path)
470         return newpath
471     localpath = staticmethod(localpath)
472
473     def go(self, urls = []):
474         """Fetch urls (no-op for Local method)"""
475 #       no need to fetch local files, we'll deal with them in place.
476         return 1
477
478 methods.append(Local())
479
480 class Svn(Fetch):
481     """Class to fetch a module or modules from svn repositories"""
482     def supports(url, d):
483         """Check to see if a given url can be fetched with svn.
484            Expects supplied url in list form, as outputted by bb.decodeurl().
485         """
486         (type, host, path, user, pswd, parm) = bb.decodeurl(bb.data.expand(url, d))
487         return type in ['svn']
488     supports = staticmethod(supports)
489
490     def localpath(url, d):
491         (type, host, path, user, pswd, parm) = bb.decodeurl(bb.data.expand(url, d))
492         if "localpath" in parm:
493 #           if user overrides local path, use it.
494             return parm["localpath"]
495
496         if not "module" in parm:
497             raise MissingParameterError("svn method needs a 'module' parameter")
498         else:
499             module = parm["module"]
500         if 'rev' in parm:
501             revision = parm['rev']
502         else:
503             revision = ""
504
505         date = bb.data.getVar("CVSDATE", d, 1) or bb.data.getVar("DATE", d, 1)
506
507         return os.path.join(bb.data.getVar("DL_DIR", d, 1),bb.data.expand('%s_%s_%s_%s.tar.gz' % ( module.replace('/', '.'), host, revision, date), d))
508     localpath = staticmethod(localpath)
509
510     def go(self, d = bb.data.init(), urls = []):
511         """Fetch urls"""
512         if not urls:
513             urls = self.urls
514
515         from copy import deepcopy
516         localdata = deepcopy(d)
517         bb.data.setVar('OVERRIDES', "svn:%s" % bb.data.getVar('OVERRIDES', localdata), localdata)
518         bb.data.update_data(localdata)
519
520         for loc in urls:
521             (type, host, path, user, pswd, parm) = bb.decodeurl(bb.data.expand(loc, localdata))
522             if not "module" in parm:
523                 raise MissingParameterError("svn method needs a 'module' parameter")
524             else:
525                 module = parm["module"]
526
527             dlfile = self.localpath(loc, localdata)
528             dldir = bb.data.getVar('DL_DIR', localdata, 1)
529 #           if local path contains the svn
530 #           module, consider the dir above it to be the
531 #           download directory
532 #           pos = dlfile.find(module)
533 #           if pos:
534 #               dldir = dlfile[:pos]
535 #           else:
536 #               dldir = os.path.dirname(dlfile)
537
538 #           setup svn options
539             options = []
540             if 'rev' in parm:
541                 revision = parm['rev']
542             else:
543                 revision = ""
544
545             date = bb.data.getVar("CVSDATE", d, 1) or bb.data.getVar("DATE", d, 1)
546
547             if "method" in parm:
548                 method = parm["method"]
549             else:
550                 method = "pserver"
551
552             if "proto" in parm:
553                 proto = parm["proto"]
554             else:
555                 proto = "svn"
556
557             svn_rsh = None
558             if method == "ext":
559                 if "rsh" in parm:
560                     svn_rsh = parm["rsh"]
561
562             tarfn = bb.data.expand('%s_%s_%s_%s.tar.gz' % (module.replace('/', '.'), host, revision, date), localdata)
563             bb.data.setVar('TARFILES', dlfile, localdata)
564             bb.data.setVar('TARFN', tarfn, localdata)
565
566             dl = os.path.join(dldir, tarfn)
567             if os.access(dl, os.R_OK):
568                 bb.debug(1, "%s already exists, skipping svn checkout." % tarfn)
569                 continue
570
571             svn_tarball_stash = bb.data.getVar('CVS_TARBALL_STASH', d, 1)
572             if svn_tarball_stash:
573                 fetchcmd = bb.data.getVar("FETCHCOMMAND_wget", d, 1)
574                 uri = svn_tarball_stash + tarfn
575                 bb.note("fetch " + uri)
576                 fetchcmd = fetchcmd.replace("${URI}", uri)
577                 ret = os.system(fetchcmd)
578                 if ret == 0:
579                     bb.note("Fetched %s from tarball stash, skipping checkout" % tarfn)
580                     continue
581
582             olddir = os.path.abspath(os.getcwd())
583             os.chdir(bb.data.expand(dldir, localdata))
584
585 #           setup svnroot
586 #            svnroot = ":" + method + ":" + user
587 #            if pswd:
588 #                svnroot += ":" + pswd
589             svnroot = host + path
590
591             bb.data.setVar('SVNROOT', svnroot, localdata)
592             bb.data.setVar('SVNCOOPTS', " ".join(options), localdata)
593             bb.data.setVar('SVNMODULE', module, localdata)
594             svncmd = bb.data.getVar('FETCHCOMMAND', localdata, 1)
595             svncmd = "svn co %s://%s/%s" % (proto, svnroot, module)
596
597             if revision:
598                 svncmd = "svn co -r %s %s://%s/%s" % (proto, revision, svnroot, module)
599             if svn_rsh:
600                 svncmd = "svn_RSH=\"%s\" %s" % (svn_rsh, svncmd)
601
602 #           create temp directory
603             bb.debug(2, "Fetch: creating temporary directory")
604             bb.mkdirhier(bb.data.expand('${WORKDIR}', localdata))
605             bb.data.setVar('TMPBASE', bb.data.expand('${WORKDIR}/oesvn.XXXXXX', localdata), localdata)
606             tmppipe = os.popen(bb.data.getVar('MKTEMPDIRCMD', localdata, 1) or "false")
607             tmpfile = tmppipe.readline().strip()
608             if not tmpfile:
609                 bb.error("Fetch: unable to create temporary directory.. make sure 'mktemp' is in the PATH.")
610                 raise FetchError(module)
611
612 #           check out sources there
613             os.chdir(tmpfile)
614             bb.note("Fetch " + loc)
615             bb.debug(1, "Running %s" % svncmd)
616             myret = os.system(svncmd)
617             if myret != 0:
618                 try:
619                     os.rmdir(tmpfile)
620                 except OSError:
621                     pass
622                 raise FetchError(module)
623
624             os.chdir(os.path.join(tmpfile, os.path.dirname(module)))
625 #           tar them up to a defined filename
626             myret = os.system("tar -czf %s %s" % (os.path.join(dldir,tarfn), os.path.basename(module)))
627             if myret != 0:
628                 try:
629                     os.unlink(tarfn)
630                 except OSError:
631                     pass
632 #           cleanup
633             os.system('rm -rf %s' % tmpfile)
634             os.chdir(olddir)
635         del localdata
636
637 methods.append(Svn())