|
1 # Copyright (c) 2007-2008 Infrae. All rights reserved. |
|
2 # $Id: Common.py 33243 2009-01-29 10:59:47Z sylvain $ |
|
3 |
|
4 from sets import Set # For python 2.3 compatibility |
|
5 import os.path |
|
6 import os |
|
7 import re |
|
8 |
|
9 |
|
10 import zc.buildout |
|
11 |
|
12 |
|
13 def ignoredFile(file): |
|
14 """Return true if the file should be ignored while checking for |
|
15 added/changed/modified files. |
|
16 """ |
|
17 for suffix in ['.pyc', '.pyo', '.egg-info']: |
|
18 if file.endswith(suffix): |
|
19 return True |
|
20 return False |
|
21 |
|
22 |
|
23 def reportInvalidFiles(path, name, badfiles): |
|
24 """Report invalid files. |
|
25 """ |
|
26 badfiles = [file for file in badfiles if not ignoredFile(file)] |
|
27 if not badfiles: |
|
28 return |
|
29 raise ValueError("""\ |
|
30 In '%s': |
|
31 local modifications detected while uninstalling %r: Uninstall aborted! |
|
32 |
|
33 Please check for local modifications and make sure these are checked |
|
34 in. |
|
35 |
|
36 If you sure that these modifications can be ignored, remove the |
|
37 checkout manually: |
|
38 |
|
39 rm -rf %s |
|
40 |
|
41 Or if applicable, add the file to the 'svn:ignore' property of the |
|
42 file's container directory. Alternatively, add an ignore glob pattern |
|
43 to your subversion client's 'global-ignores' configuration variable. |
|
44 """ % (path, name, """ |
|
45 rm -rf """.join(badfiles))) |
|
46 |
|
47 |
|
48 def checkExistPath(path, warning=True): |
|
49 """Check that a path exist. |
|
50 """ |
|
51 status = os.path.exists(path) |
|
52 if not status and warning: |
|
53 print "-------- WARNING --------" |
|
54 print "Directory %s have been removed." % os.path.abspath(path) |
|
55 print "Changes might be lost." |
|
56 print "-------- WARNING --------" |
|
57 return status |
|
58 |
|
59 |
|
60 def checkAddedPaths(location, urls): |
|
61 """Check that no path have been added to that location. |
|
62 """ |
|
63 current_paths = Set([os.path.join(location, s) for s in |
|
64 os.listdir(location)]) |
|
65 recipe_paths = Set(urls.keys()) |
|
66 added_paths = list(current_paths - recipe_paths) |
|
67 for path in added_paths[:]: |
|
68 if path.endswith('.svn'): |
|
69 added_paths.remove(path) |
|
70 if added_paths: |
|
71 msg = "New path have been added to the location: %s." |
|
72 raise ValueError(msg % ', '.join(added_paths)) |
|
73 |
|
74 |
|
75 def prepareURLs(location, urls): |
|
76 """Given a list of urls/path, and a location, prepare a list of |
|
77 tuple with url, full path. |
|
78 """ |
|
79 |
|
80 def prepareEntry(line): |
|
81 link, path = line.split() |
|
82 return os.path.join(location, path), link |
|
83 |
|
84 return dict([prepareEntry(l) for l in urls.splitlines() if l.strip()]) |
|
85 |
|
86 |
|
87 def extractNames(urls): |
|
88 """Return just the target names of the urls (used for egg names)""" |
|
89 |
|
90 def extractName(line): |
|
91 link, name = line.split() |
|
92 return name |
|
93 |
|
94 return [extractName(line) for line in urls.splitlines() if line.strip()] |
|
95 |
|
96 |
|
97 class BaseRecipe(object): |
|
98 """infrae.subversion recipe. Base class. |
|
99 """ |
|
100 |
|
101 def __init__(self, buildout, name, options): |
|
102 self.buildout = buildout |
|
103 self.name = name |
|
104 self.options = options |
|
105 # location is overridable if desired. |
|
106 location = options.get('location', None) |
|
107 if location: |
|
108 self.location = os.path.abspath(os.path.join( |
|
109 buildout['buildout']['directory'], location)) |
|
110 else: |
|
111 self.location = os.path.join( |
|
112 buildout['buildout']['parts-directory'], self.name) |
|
113 options['location'] = self.location |
|
114 self.revisions = {} # Store revision information for each link |
|
115 self.updated = [] # Store updated links |
|
116 self.urls = prepareURLs(self.location, options['urls']) |
|
117 self.export = options.get('export') |
|
118 self.offline = buildout['buildout'].get('offline', 'false') == 'true' |
|
119 self.eggify = options.get('as_eggs', False) |
|
120 self.eggs = self.eggify and extractNames(options['urls']) or [] |
|
121 self.newest = ( |
|
122 not self.offline and |
|
123 buildout['buildout'].get('newest', 'true') == 'true' |
|
124 ) |
|
125 self.verbose = buildout['buildout'].get('verbosity', 0) |
|
126 self.warning = not (options.get('no_warnings', 'false') == 'true') |
|
127 |
|
128 def _exportInformationToOptions(self): |
|
129 """Export revision and changed information to options. |
|
130 |
|
131 Options can only contains strings. |
|
132 """ |
|
133 if self.options.get('export_info', False): |
|
134 self.options['updated'] = str('\n'.join(self.updated)) |
|
135 str_revisions = ['%s %s' % r for r in self.revisions.items() |
|
136 if r[1]] |
|
137 self.options['revisions'] = str('\n'.join(sorted(str_revisions))) |
|
138 # Always export egg list |
|
139 self.options['eggs'] = '\n'.join(sorted(self.eggs)) |
|
140 |
|
141 def _updateAllRevisionInformation(self): |
|
142 """Update all revision information for defined urls. |
|
143 """ |
|
144 for path, link in self.urls.items(): |
|
145 if os.path.exists(path): |
|
146 self._updateRevisionInformation(link, path) |
|
147 |
|
148 def _updateRevisionInformation(self, link, revision): |
|
149 """Update revision information on a path. |
|
150 """ |
|
151 old_revision = self.revisions.get(link, None) |
|
152 self.revisions[link] = revision |
|
153 if not (old_revision is None): |
|
154 self.updated.append(link) |
|
155 |
|
156 def _updatePath(self, link, path): |
|
157 """Update a single path. |
|
158 """ |
|
159 raise NotImplementedError |
|
160 |
|
161 def _updateAllPaths(self): |
|
162 """Update the checkouts. |
|
163 """ |
|
164 ignore = self.options.get('ignore_updates', False) or self.export |
|
165 |
|
166 num_release = re.compile('.*@[0-9]+$') |
|
167 for path, link in self.urls.items(): |
|
168 if not checkExistPath(path, warning=self.warning): |
|
169 if self.verbose: |
|
170 print "Entry %s missing, checkout a new version ..." % link |
|
171 self._installPath(link, path) |
|
172 continue |
|
173 |
|
174 if ignore: |
|
175 continue |
|
176 |
|
177 if num_release.match(link): |
|
178 if self.verbose: |
|
179 print "Given num release for %s, skipping." % link |
|
180 continue |
|
181 |
|
182 if self.verbose: |
|
183 print "Updating %s" % path |
|
184 self._updatePath(link, path) |
|
185 |
|
186 def update(self): |
|
187 """Update the recipe. |
|
188 |
|
189 Does not update SVN path if the buildout is in offline mode, |
|
190 but still eggify and export information. |
|
191 """ |
|
192 if self.newest: |
|
193 self._updateAllPaths() |
|
194 |
|
195 if self.eggify: |
|
196 self._eggify() |
|
197 self._exportInformationToOptions() |
|
198 return self.location |
|
199 |
|
200 def _installPath(self, link, path): |
|
201 """Checkout a single entry. |
|
202 """ |
|
203 raise NotImplementedError |
|
204 |
|
205 def _installPathVerbose(self, link, path): |
|
206 """Checkout a single entry with verbose. |
|
207 """ |
|
208 if self.verbose: |
|
209 print "%s %s to %s" % (self.export and 'Export' or 'Fetch', |
|
210 link, path) |
|
211 self._installPath(link, path) |
|
212 |
|
213 def _eggify(self): |
|
214 """Install everything as development eggs if eggs=true""" |
|
215 if self.eggify: |
|
216 target = self.buildout['buildout']['develop-eggs-directory'] |
|
217 for path in self.urls.keys(): |
|
218 # If we update the recipe, and we don't have newest, |
|
219 # and that some path have been deleted, all of them |
|
220 # might not be there. |
|
221 if checkExistPath(path, warning=self.warning): |
|
222 zc.buildout.easy_install.develop(path, target) |
|
223 |
|
224 def install(self): |
|
225 """Checkout the checkouts. |
|
226 |
|
227 Fails if buildout is running in offline mode. |
|
228 """ |
|
229 |
|
230 for path, link in self.urls.items(): |
|
231 self._installPathVerbose(link, path) |
|
232 installed = [self.location] |
|
233 |
|
234 if self.eggify: |
|
235 self._eggify() |
|
236 # And also return the develop-eggs/*.egg-link files that are |
|
237 # ours so that an uninstall automatically zaps them. |
|
238 dev_dir = self.buildout['buildout']['develop-eggs-directory'] |
|
239 egg_links = ['%s.egg-link' % egg for egg in self.eggs] |
|
240 egg_links = [os.path.join(dev_dir, link) for link in egg_links] |
|
241 installed += egg_links |
|
242 self._exportInformationToOptions() |
|
243 |
|
244 return installed |