Add perforce fetcher from Jordan Crouse, suitably modified for recent bitbake changes
[bitbake.git] / lib / bb / fetch / __init__.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 class ParameterError(Exception):
42     """Exception raised when a url cannot be proccessed due to invalid parameters."""
43
44 class MD5SumError(Exception):
45     """Exception raised when a MD5SUM of a file does not match the expected one"""
46
47 def uri_replace(uri, uri_find, uri_replace, d):
48 #   bb.msg.note(1, bb.msg.domain.Fetcher, "uri_replace: operating on %s" % uri)
49     if not uri or not uri_find or not uri_replace:
50         bb.msg.debug(1, bb.msg.domain.Fetcher, "uri_replace: passed an undefined value, not replacing")
51     uri_decoded = list(bb.decodeurl(uri))
52     uri_find_decoded = list(bb.decodeurl(uri_find))
53     uri_replace_decoded = list(bb.decodeurl(uri_replace))
54     result_decoded = ['','','','','',{}]
55     for i in uri_find_decoded:
56         loc = uri_find_decoded.index(i)
57         result_decoded[loc] = uri_decoded[loc]
58         import types
59         if type(i) == types.StringType:
60             import re
61             if (re.match(i, uri_decoded[loc])):
62                 result_decoded[loc] = re.sub(i, uri_replace_decoded[loc], uri_decoded[loc])
63                 if uri_find_decoded.index(i) == 2:
64                     if d:
65                         localfn = bb.fetch.localpath(uri, d)
66                         if localfn:
67                             result_decoded[loc] = os.path.dirname(result_decoded[loc]) + "/" + os.path.basename(bb.fetch.localpath(uri, d))
68 #                       bb.msg.note(1, bb.msg.domain.Fetcher, "uri_replace: matching %s against %s and replacing with %s" % (i, uri_decoded[loc], uri_replace_decoded[loc]))
69             else:
70 #               bb.msg.note(1, bb.msg.domain.Fetcher, "uri_replace: no match")
71                 return uri
72 #           else:
73 #               for j in i.keys():
74 #                   FIXME: apply replacements against options
75     return bb.encodeurl(result_decoded)
76
77 methods = []
78 urldata = {}
79
80 def init(urls = [], d = None):
81     if d == None:
82         bb.msg.debug(2, bb.msg.domain.Fetcher, "BUG init called with None as data object!!!")
83         return
84
85     for m in methods:
86         m.urls = []
87
88     for u in urls:
89         ud = initdata(u, d)
90         if ud.method:
91             ud.method.urls.append(u)
92
93 def initdata(url, d):
94     if url not in urldata:
95         ud = FetchData()
96         (ud.type, ud.host, ud.path, ud.user, ud.pswd, ud.parm) = bb.decodeurl(data.expand(url, d))
97         ud.date = Fetch.getSRCDate(d)
98         for m in methods:
99             if m.supports(url, ud, d):
100                 ud.localpath = m.localpath(url, ud, d)
101                 ud.md5 = ud.localpath + '.md5'
102                 # if user sets localpath for file, use it instead.
103                 if "localpath" in ud.parm:
104                     ud.localpath = ud.parm["localpath"]
105                 ud.method = m
106                 break
107         urldata[url] = ud
108     return urldata[url]
109
110 def go(d):
111     """Fetch all urls"""
112     for m in methods:
113         for u in m.urls:
114             ud = urldata[u]
115             if ud.localfile and not m.forcefetch(u, ud, d) and os.path.exists(urldata[u].md5):
116                 # File already present along with md5 stamp file
117                 # Touch md5 file to show activity
118                 os.utime(ud.md5, None)
119                 continue
120             # RP - is olddir needed?
121             # olddir = os.path.abspath(os.getcwd())
122             m.go(u, ud  , d)
123             # os.chdir(olddir)
124             if ud.localfile and not m.forcefetch(u, ud, d):
125                 Fetch.write_md5sum(u, ud, d)
126
127 def localpaths(d):
128     """Return a list of the local filenames, assuming successful fetch"""
129     local = []
130     for m in methods:
131         for u in m.urls:
132             local.append(urldata[u].localpath)
133     return local
134
135 def localpath(url, d):
136     ud = initdata(url, d)
137     if ud.method:
138         return ud.localpath
139     return url
140
141 class FetchData(object):
142     """Class for fetcher variable store"""
143     def __init__(self):
144         self.localfile = ""
145
146
147 class Fetch(object):
148     """Base class for 'fetch'ing data"""
149
150     def __init__(self, urls = []):
151         self.urls = []
152
153     def supports(self, url, urldata, d):
154         """
155         Check to see if this fetch class supports a given url.
156         """
157         return 0
158
159     def localpath(self, url, urldata, d):
160         """
161         Return the local filename of a given url assuming a successful fetch.
162         Can also setup variables in urldata for use in go (saving code duplication 
163         and duplicate code execution)
164         """
165         return url
166
167     def setUrls(self, urls):
168         self.__urls = urls
169
170     def getUrls(self):
171         return self.__urls
172
173     urls = property(getUrls, setUrls, None, "Urls property")
174
175     def forcefetch(self, url, urldata, d):
176         """
177         Force a fetch, even if localpath exists?
178         """
179         return False
180
181     def go(self, url, urldata, d):
182         """
183         Fetch urls
184         Assumes localpath was called first
185         """
186         raise NoMethodError("Missing implementation for url")
187
188     def getSRCDate(d):
189         """
190         Return the SRC Date for the component
191
192         d the bb.data module
193         """
194         pn = data.getVar("PN", d, 1)
195
196         if pn:
197             return data.getVar("SRCDATE_%s" % pn, d, 1) or data.getVar("CVSDATE_%s" % pn, d, 1) or data.getVar("DATE", d, 1)
198
199         return data.getVar("SRCDATE", d, 1) or data.getVar("CVSDATE", d, 1) or data.getVar("DATE", d, 1)
200     getSRCDate = staticmethod(getSRCDate)
201
202     def try_mirror(d, tarfn):
203         """
204         Try to use a mirrored version of the sources. We do this
205         to avoid massive loads on foreign cvs and svn servers.
206         This method will be used by the different fetcher
207         implementations.
208
209         d Is a bb.data instance
210         tarfn is the name of the tarball
211         """
212         tarpath = os.path.join(data.getVar("DL_DIR", d, 1), tarfn)
213         if os.access(tarpath, os.R_OK):
214             bb.msg.debug(1, bb.msg.domain.Fetcher, "%s already exists, skipping checkout." % tarfn)
215             return True
216
217         pn = data.getVar('PN', d, True)
218         src_tarball_stash = None
219         if pn:
220             src_tarball_stash = (data.getVar('SRC_TARBALL_STASH_%s' % pn, d, True) or data.getVar('CVS_TARBALL_STASH_%s' % pn, d, True) or data.getVar('SRC_TARBALL_STASH', d, True) or data.getVar('CVS_TARBALL_STASH', d, True) or "").split()
221
222         for stash in src_tarball_stash:
223             fetchcmd = data.getVar("FETCHCOMMAND_mirror", d, True) or data.getVar("FETCHCOMMAND_wget", d, True)
224             uri = stash + tarfn
225             bb.msg.note(1, bb.msg.domain.Fetcher, "fetch " + uri)
226             fetchcmd = fetchcmd.replace("${URI}", uri)
227             ret = os.system(fetchcmd)
228             if ret == 0:
229                 bb.msg.note(1, bb.msg.domain.Fetcher, "Fetched %s from tarball stash, skipping checkout" % tarfn)
230                 return True
231         return False
232     try_mirror = staticmethod(try_mirror)
233
234     def verify_md5sum(ud, got_sum):
235         """
236         Verify the md5sum we wanted with the one we got
237         """
238         wanted_sum = None
239         if 'md5sum' in ud.parm:
240             wanted_sum = ud.parm['md5sum']
241         if not wanted_sum:
242             return True
243
244         return wanted_sum == got_sum
245     verify_md5sum = staticmethod(verify_md5sum)
246
247     def write_md5sum(url, ud, d):
248         if bb.which(data.getVar('PATH', d), 'md5sum'):
249             try:
250                 md5pipe = os.popen('md5sum ' + ud.localpath)
251                 md5data = (md5pipe.readline().split() or [ "" ])[0]
252                 md5pipe.close()
253             except OSError:
254                 md5data = ""
255
256         # verify the md5sum
257         if not Fetch.verify_md5sum(ud, md5data):
258             raise MD5SumError(url)
259
260         md5out = file(ud.md5, 'w')
261         md5out.write(md5data)
262         md5out.close()
263     write_md5sum = staticmethod(write_md5sum)
264
265 import cvs
266 import git
267 import local
268 import svn
269 import wget
270 import svk
271 import ssh
272 import perforce
273
274 methods.append(cvs.Cvs())
275 methods.append(git.Git())
276 methods.append(local.Local())
277 methods.append(svn.Svn())
278 methods.append(wget.Wget())
279 methods.append(svk.Svk())
280 methods.append(ssh.SSH())
281 methods.append(perforce.Perforce())