Add a test for the keyword expansion fix in r5379.
[cvs2svn.git] / cvs2svn_lib / artifact_manager.py
blob310baf2fcdf14dc43958c5ccc3cf3c3bb9cef91c
1 # (Be in -*- python -*- mode.)
3 # ====================================================================
4 # Copyright (c) 2000-2008 CollabNet. All rights reserved.
6 # This software is licensed as described in the file COPYING, which
7 # you should have received as part of this distribution. The terms
8 # are also available at http://subversion.tigris.org/license-1.html.
9 # If newer versions of this license are posted there, you may use a
10 # newer version instead, at your option.
12 # This software consists of voluntary contributions made by many
13 # individuals. For exact contribution history, see the revision
14 # history and logs, available at http://cvs2svn.tigris.org/.
15 # ====================================================================
17 """This module manages the artifacts produced by conversion passes."""
20 from cvs2svn_lib.log import logger
21 from cvs2svn_lib.artifact import TempFile
24 class ArtifactNotActiveError(Exception):
25 """An artifact was requested when no passes that have registered
26 that they need it are active."""
28 def __init__(self, artifact_name):
29 Exception.__init__(
30 self, 'Artifact %s is not currently active' % artifact_name)
33 class ArtifactManager:
34 """Manage artifacts that are created by one pass but needed by others.
36 This class is responsible for cleaning up artifacts once they are no
37 longer needed. The trick is that cvs2svn can be run pass by pass,
38 so not all passes might be executed during a specific program run.
40 To use this class:
42 - Call artifact_manager.set_artifact(name, artifact) once for each
43 known artifact.
45 - Call artifact_manager.creates(which_pass, artifact) to indicate
46 that WHICH_PASS is the pass that creates ARTIFACT.
48 - Call artifact_manager.uses(which_pass, artifact) to indicate that
49 WHICH_PASS needs to use ARTIFACT.
51 There are also helper methods register_temp_file(),
52 register_artifact_needed(), and register_temp_file_needed() which
53 combine some useful operations.
55 Then, in pass order:
57 - Call pass_skipped() for any passes that were already executed
58 during a previous cvs2svn run.
60 - Call pass_started() when a pass is about to start execution.
62 - If a pass that has been started will be continued during the next
63 program run, then call pass_continued().
65 - If a pass that has been started finishes execution, call
66 pass_done(), to allow any artifacts that won't be needed anymore
67 to be cleaned up.
69 - Call pass_deferred() for any passes that have been deferred to a
70 future cvs2svn run.
72 Finally:
74 - Call check_clean() to verify that all artifacts have been
75 accounted for."""
77 def __init__(self):
78 # A map { artifact_name : artifact } of known artifacts.
79 self._artifacts = { }
81 # A map { pass : set_of_artifacts }, where set_of_artifacts is a
82 # set of artifacts needed by the pass.
83 self._pass_needs = { }
85 # A set of passes that are currently being executed.
86 self._active_passes = set()
88 def set_artifact(self, name, artifact):
89 """Add ARTIFACT to the list of artifacts that we manage.
91 Store it under NAME."""
93 assert name not in self._artifacts
94 self._artifacts[name] = artifact
96 def get_artifact(self, name):
97 """Return the artifact with the specified name.
99 If the artifact does not currently exist, raise a KeyError. If it
100 is not registered as being needed by one of the active passes,
101 raise an ArtifactNotActiveError."""
103 artifact = self._artifacts[name]
104 for active_pass in self._active_passes:
105 if artifact in self._pass_needs[active_pass]:
106 # OK
107 return artifact
108 else:
109 raise ArtifactNotActiveError(name)
111 def creates(self, which_pass, artifact):
112 """Register that WHICH_PASS creates ARTIFACT.
114 ARTIFACT must already have been registered."""
116 # An artifact is automatically "needed" in the pass in which it is
117 # created:
118 self.uses(which_pass, artifact)
120 def uses(self, which_pass, artifact):
121 """Register that WHICH_PASS uses ARTIFACT.
123 ARTIFACT must already have been registered."""
125 artifact._passes_needed.add(which_pass)
126 if which_pass in self._pass_needs:
127 self._pass_needs[which_pass].add(artifact)
128 else:
129 self._pass_needs[which_pass] = set([artifact])
131 def register_temp_file(self, basename, which_pass):
132 """Register a temporary file with base name BASENAME as an artifact.
134 Return the filename of the temporary file."""
136 artifact = TempFile(basename)
137 self.set_artifact(basename, artifact)
138 self.creates(which_pass, artifact)
140 def get_temp_file(self, basename):
141 """Return the filename of the temporary file with the specified BASENAME.
143 If the temporary file is not an existing, registered TempFile,
144 raise a KeyError."""
146 return self.get_artifact(basename).filename
148 def register_artifact_needed(self, artifact_name, which_pass):
149 """Register that WHICH_PASS uses the artifact named ARTIFACT_NAME.
151 An artifact with this name must already have been registered."""
153 artifact = self._artifacts[artifact_name]
154 artifact._passes_needed.add(which_pass)
155 if which_pass in self._pass_needs:
156 self._pass_needs[which_pass].add(artifact)
157 else:
158 self._pass_needs[which_pass] = set([artifact,])
160 def register_temp_file_needed(self, basename, which_pass):
161 """Register that a temporary file is needed by WHICH_PASS.
163 Register that the temporary file with base name BASENAME is needed
164 by WHICH_PASS."""
166 self.register_artifact_needed(basename, which_pass)
168 def _unregister_artifacts(self, which_pass):
169 """Unregister any artifacts that were needed for WHICH_PASS.
171 Return a list of artifacts that are no longer needed at all."""
173 try:
174 artifacts = list(self._pass_needs[which_pass])
175 except KeyError:
176 # No artifacts were needed for that pass:
177 return []
179 del self._pass_needs[which_pass]
181 unneeded_artifacts = []
182 for artifact in artifacts:
183 artifact._passes_needed.remove(which_pass)
184 if not artifact._passes_needed:
185 unneeded_artifacts.append(artifact)
187 return unneeded_artifacts
189 def pass_skipped(self, which_pass):
190 """WHICH_PASS was executed during a previous cvs2svn run.
192 Its artifacts were created then, and any artifacts that would
193 normally be cleaned up after this pass have already been cleaned
194 up."""
196 self._unregister_artifacts(which_pass)
198 def pass_started(self, which_pass):
199 """WHICH_PASS is starting."""
201 self._active_passes.add(which_pass)
203 def pass_continued(self, which_pass):
204 """WHICH_PASS will be continued during the next program run.
206 WHICH_PASS, which has already been started, will be continued
207 during the next program run. Unregister any artifacts that would
208 be cleaned up at the end of WHICH_PASS without actually cleaning
209 them up."""
211 self._active_passes.remove(which_pass)
212 self._unregister_artifacts(which_pass)
214 def pass_done(self, which_pass, skip_cleanup):
215 """WHICH_PASS is done.
217 Clean up all artifacts that are no longer needed. If SKIP_CLEANUP
218 is True, then just do the bookkeeping without actually calling
219 artifact.cleanup()."""
221 self._active_passes.remove(which_pass)
222 artifacts = self._unregister_artifacts(which_pass)
223 if not skip_cleanup:
224 for artifact in artifacts:
225 artifact.cleanup()
227 def pass_deferred(self, which_pass):
228 """WHICH_PASS is being deferred until a future cvs2svn run.
230 Unregister any artifacts that would be cleaned up during
231 WHICH_PASS."""
233 self._unregister_artifacts(which_pass)
235 def check_clean(self):
236 """All passes have been processed.
238 Output a warning messages if all artifacts have not been accounted
239 for. (This is mainly a consistency check, that no artifacts were
240 registered under nonexistent passes.)"""
242 unclean_artifacts = [
243 str(artifact)
244 for artifact in self._artifacts.values()
245 if artifact._passes_needed]
247 if unclean_artifacts:
248 logger.warn(
249 'INTERNAL: The following artifacts were not cleaned up:\n %s\n'
250 % ('\n '.join(unclean_artifacts)))
253 # The default ArtifactManager instance:
254 artifact_manager = ArtifactManager()