10637.3.1
by Guilherme Salgado
Use the default python version instead of a hard-coded version |
1 |
#!/usr/bin/python
|
9492.1.1
by Karl Fogel
Add utilities/formatdoctest.py and utilities/migrater/, both brought |
2 |
#
|
3 |
# Copyright 2009 Canonical Ltd. This software is licensed under the
|
|
4 |
# GNU Affero General Public License version 3 (see the file LICENSE).
|
|
5 |
||
6 |
"""Migrate modules from the old LP directory structure to the new using
|
|
7 |
a control file and the exising mover script that Francis wrote.
|
|
8 |
"""
|
|
9 |
||
10 |
import errno |
|
11 |
import os |
|
12 |
import re |
|
13 |
||
14 |
from find import find_files, find_matches |
|
15 |
from optparse import OptionParser |
|
16 |
from rename_module import ( |
|
17 |
bzr_add, bzr_move_file, bzr_remove_file, rename_module, update_references) |
|
18 |
from rename_zcml import handle_zcml |
|
19 |
from utils import log, run, spew |
|
20 |
||
21 |
||
22 |
MOVER = os.path.join(os.path.dirname(__file__), 'rename_module.py') |
|
23 |
||
24 |
TLA_MAP = dict( |
|
25 |
ans='answers', |
|
26 |
app='app', |
|
27 |
blu='blueprints', |
|
28 |
bug='bugs', |
|
29 |
cod='code', |
|
30 |
reg='registry', |
|
31 |
sha='shared', |
|
32 |
soy='soyuz', |
|
33 |
svc='services', |
|
34 |
tes='testing', |
|
35 |
tra='translations', |
|
9668.6.2
by Curtis Hovey
Fixed errors in migrater. |
36 |
pkg='registry', |
10234.3.7
by Curtis Hovey
updated code per review. |
37 |
hdb='hardwaredb', |
9492.1.1
by Karl Fogel
Add utilities/formatdoctest.py and utilities/migrater/, both brought |
38 |
)
|
39 |
||
40 |
RENAME_MAP = dict( |
|
41 |
components='adapters', |
|
42 |
database='model', |
|
43 |
ftests='tests', |
|
44 |
pagetests='stories', |
|
45 |
)
|
|
46 |
||
47 |
OLD_TOP = 'lib/canonical/launchpad' |
|
48 |
NEW_TOP = 'lib/lp' |
|
49 |
||
50 |
APP_DIRECTORIES = [ |
|
51 |
'adapters', |
|
52 |
'browser', |
|
53 |
'doc', |
|
54 |
'emailtemplates', |
|
55 |
'event', |
|
56 |
'feed', |
|
57 |
'interfaces', |
|
58 |
'model', |
|
59 |
'notifications', |
|
60 |
'scripts', |
|
61 |
'stories', |
|
62 |
'subscribers', |
|
63 |
'templates', |
|
64 |
'tests', |
|
65 |
'browser/tests', |
|
66 |
]
|
|
67 |
||
68 |
TEST_PATHS = set(('doc', 'tests', 'ftests', 'pagetests')) |
|
69 |
# Ripped straight from GNU touch(1)
|
|
70 |
FLAGS = os.O_WRONLY | os.O_CREAT | os.O_NONBLOCK | os.O_NOCTTY |
|
71 |
||
72 |
||
73 |
def parse_args(): |
|
74 |
"""Return a tuple of parser, option, and arguments."""
|
|
75 |
usage = """\ |
|
76 |
%prog [options] controlfile app_codes+
|
|
77 |
||
78 |
controlfile is the file containing the list of files to be moved. Each file
|
|
79 |
is prefixed with a TLA identifying the apps.
|
|
80 |
||
81 |
app_codes is a list of TLAs identifying the apps to migrate.
|
|
82 |
"""
|
|
83 |
parser = OptionParser(usage) |
|
84 |
parser.add_option( |
|
85 |
'--dryrun', |
|
86 |
action='store_true', default=False, dest='dry_run', |
|
87 |
help=("If this option is used actions will be printed " |
|
88 |
"but not executed.")) |
|
89 |
parser.add_option( |
|
90 |
'--no-move', |
|
91 |
action='store_false', default=True, dest='move', |
|
92 |
help="Don't actually move any files, just set up the app's tree.") |
|
93 |
||
94 |
options, arguments = parser.parse_args() |
|
95 |
return parser, options, arguments |
|
96 |
||
97 |
||
98 |
def convert_ctl_data(data): |
|
99 |
"""Return a dict of files, each keyed to an app."""
|
|
100 |
app_data = {} |
|
101 |
for line in data: |
|
102 |
try: |
|
103 |
tla, fn = line.split() |
|
104 |
except ValueError: |
|
105 |
continue
|
|
106 |
if not tla in app_data: |
|
107 |
app_data[tla] = [] |
|
108 |
app_data[tla].append(fn[2:]) |
|
109 |
return app_data |
|
110 |
||
111 |
COLLIDED = [] |
|
112 |
||
9668.6.2
by Curtis Hovey
Fixed errors in migrater. |
113 |
|
9492.1.1
by Karl Fogel
Add utilities/formatdoctest.py and utilities/migrater/, both brought |
114 |
def move_it(old_path, new_path): |
115 |
"""Move a versioned file without colliding with another file."""
|
|
116 |
# Move the file and fix the imports. LBYL.
|
|
117 |
if os.path.exists(new_path): |
|
118 |
if os.path.getsize(new_path) == 0: |
|
119 |
# We must remove the file since bzr refuses to clobber existing
|
|
120 |
# files.
|
|
121 |
bzr_remove_file(new_path) |
|
122 |
else: |
|
123 |
log('COLLISION! target already exists: %s', new_path) |
|
124 |
COLLIDED.append(new_path) |
|
125 |
# Try to find an alternative. I seriously doubt we'll ever have
|
|
126 |
# more than two collisions.
|
|
127 |
for counter in range(10): |
|
128 |
fn, ext = os.path.splitext(new_path) |
|
129 |
new_target = fn + '_%d' % counter + ext |
|
130 |
log(' new target: %s', new_target) |
|
131 |
if not os.path.exists(new_target): |
|
132 |
new_path = new_target |
|
133 |
break
|
|
134 |
else: |
|
135 |
raise AssertionError('Too many collisions: ' + new_path) |
|
136 |
rename_module(old_path, new_path) |
|
137 |
||
138 |
||
139 |
def make_tree(app): |
|
140 |
"""Make the official tree structure."""
|
|
141 |
if not os.path.exists(NEW_TOP): |
|
142 |
os.mkdir(NEW_TOP) |
|
143 |
tld = os.path.join(NEW_TOP, TLA_MAP[app]) |
|
144 |
||
145 |
for directory in [''] + APP_DIRECTORIES: |
|
146 |
d = os.path.join(tld, directory) |
|
147 |
try: |
|
148 |
os.mkdir(d) |
|
149 |
bzr_add(d) |
|
150 |
print "created", d |
|
151 |
except OSError, e: |
|
152 |
if e.errno == errno.EEXIST: |
|
153 |
# The directory already exists, so assume that the __init__.py
|
|
154 |
# file also exists.
|
|
155 |
continue
|
|
156 |
else: |
|
157 |
raise
|
|
158 |
else: |
|
159 |
# Touch an empty __init__.py to make the thing a package.
|
|
160 |
init_file = os.path.join(d, '__init__.py') |
|
11666.3.1
by Curtis Hovey
Merged apocalypse-0 into this branch to fix translations and codehosting. |
161 |
fd = os.open(init_file, FLAGS, 0666) |
162 |
os.close(fd) |
|
163 |
bzr_add(init_file) |
|
9492.1.1
by Karl Fogel
Add utilities/formatdoctest.py and utilities/migrater/, both brought |
164 |
# Add the whole directory.
|
165 |
bzr_add(tld) |
|
166 |
||
167 |
||
168 |
def file2module(module_file): |
|
169 |
"""From a filename, return the python module name."""
|
|
170 |
start_path = 'lib' + os.path.sep |
|
171 |
module_file, dummy = os.path.splitext(module_file) |
|
172 |
module = module_file[len(start_path):].replace(os.path.sep, '.') |
|
173 |
return module |
|
174 |
||
175 |
||
176 |
def handle_script(old_path, new_path): |
|
177 |
"""Move a script or directory and update references in cronscripts."""
|
|
178 |
parts = old_path.split(os.path.sep) |
|
179 |
if (len(parts) - parts.index('scripts')) > 2: |
|
180 |
# The script is a directory not a single-file module.
|
|
181 |
# Just get the directory portion and move everything at once.
|
|
182 |
old_path = os.path.join(*parts[:-1]) |
|
183 |
new_full_path = new_path |
|
184 |
else: |
|
185 |
# The script is a single-file module. Add the script name to the end
|
|
186 |
# of new_path.
|
|
187 |
new_full_path = os.path.join(new_path, parts[-1]) |
|
188 |
||
189 |
# Move the file or directory
|
|
190 |
bzr_move_file(old_path, new_path) |
|
191 |
# Update references, but only in the cronscripts directory.
|
|
192 |
source_module = file2module(old_path) |
|
193 |
target_module = file2module(new_full_path) |
|
194 |
update_references(source_module, target_module) |
|
195 |
update_helper_imports(old_path, new_full_path) |
|
196 |
||
197 |
||
198 |
def map_filename(path): |
|
199 |
"""Return the renamed file name."""
|
|
200 |
fn, dummy = os.path.splitext(path) |
|
201 |
if fn.endswith('-pages'): |
|
202 |
# Don't remap -pages doctests here.
|
|
203 |
return path |
|
204 |
else: |
|
205 |
return os.sep.join(RENAME_MAP.get(path_part, path_part) |
|
206 |
for path_part in path.split(os.sep)) |
|
207 |
||
208 |
||
209 |
def handle_test(old_path, new_path): |
|
210 |
"""Migrate tests."""
|
|
211 |
spew('handle_test(%s, %s)', old_path, new_path) |
|
212 |
unsupported_dirs = [ |
|
213 |
'components', |
|
214 |
'daemons', |
|
215 |
'model', |
|
216 |
'interfaces', |
|
217 |
'mail', |
|
218 |
'mailout', |
|
219 |
'translationformat', |
|
220 |
'utilities', |
|
221 |
'validators', |
|
222 |
'vocabularies', |
|
223 |
'webapp', |
|
224 |
'xmlrpc', |
|
225 |
]
|
|
226 |
new_path = map_filename(new_path) |
|
227 |
# Do target -pages.txt doctest remapping.
|
|
228 |
file_name, ext = os.path.splitext(new_path) |
|
229 |
if file_name.endswith('-pages'): |
|
230 |
new_path = file_name[:-6] + '-views' + ext |
|
231 |
parts = new_path.split(os.sep) |
|
232 |
index = parts.index('doc') |
|
233 |
parts[index:index + 1] = ['browser', 'tests'] |
|
234 |
new_path = os.sep.join(parts) |
|
235 |
if '/tests/' in new_path and '/browser/tests/' not in new_path: |
|
236 |
# All unit tests except to browser unit tests move to the app
|
|
237 |
# tests dir.
|
|
238 |
new_path = os.sep.join( |
|
9668.6.2
by Curtis Hovey
Fixed errors in migrater. |
239 |
path_part for path_part in new_path.split(os.sep) |
240 |
if path_part not in unsupported_dirs) |
|
9492.1.1
by Karl Fogel
Add utilities/formatdoctest.py and utilities/migrater/, both brought |
241 |
# Create new_path's directory if it doesn't exist yet.
|
242 |
try: |
|
243 |
test_dir, dummy = os.path.split(new_path) |
|
244 |
os.makedirs(test_dir) |
|
245 |
spew('created: %s', test_dir) |
|
246 |
except OSError, error: |
|
247 |
if error.errno != errno.EEXIST: |
|
248 |
raise
|
|
249 |
else: |
|
250 |
# Add the whole directory.
|
|
251 |
run('bzr', 'add', test_dir) |
|
252 |
move_it(old_path, new_path) |
|
253 |
dir_path, file_name = os.path.split(old_path) |
|
254 |
if file_name.endswith('py') and not file_name.startswith('test_'): |
|
255 |
update_helper_imports(old_path, new_path) |
|
256 |
||
257 |
||
258 |
def update_helper_imports(old_path, new_path): |
|
259 |
"""Fix the references to the test helper."""
|
|
260 |
old_dir_path, file_name = os.path.split(old_path) |
|
261 |
old_module_path = file2module(old_dir_path).replace('.', '\\.') |
|
262 |
module_name, dummy = os.path.splitext(file_name) |
|
263 |
new_module_path = file2module(os.path.dirname(new_path)) |
|
264 |
source = r'\b%s(\.| import )%s\b' % (old_module_path, module_name) |
|
265 |
target = r'%s\1%s' % (new_module_path, module_name) |
|
266 |
root_dirs = ['cronscripts', 'lib/canonical', 'lib/lp'] |
|
267 |
file_pattern = '\.(py|txt|zcml)$' |
|
268 |
print source, target |
|
269 |
print " Updating references:" |
|
270 |
for root_dir in root_dirs: |
|
271 |
for summary in find_matches( |
|
272 |
root_dir, file_pattern, source, substitution=target): |
|
273 |
print " * %(file_path)s" % summary |
|
274 |
||
275 |
||
276 |
def setup_test_harnesses(app_name): |
|
277 |
"""Create the doctest harnesses."""
|
|
278 |
app_path = os.path.join(NEW_TOP, app_name) |
|
279 |
doctest_path = os.path.join(app_path, 'doc') |
|
280 |
doctests = [file_name |
|
281 |
for file_name in os.listdir(doctest_path) |
|
282 |
if file_name.endswith('.txt')] |
|
283 |
print 'Installing doctest harnesses' |
|
284 |
install_doctest_suite( |
|
285 |
'test_doc.py', os.path.join(app_path, 'tests'), doctests=doctests) |
|
286 |
install_doctest_suite( |
|
287 |
'test_views.py', os.path.join(app_path, 'browser', 'tests')) |
|
288 |
||
289 |
||
290 |
def install_doctest_suite(file_name, dir_path, doctests=None): |
|
291 |
"""Copy the simple doctest builder."""
|
|
292 |
test_doc_path = os.path.join( |
|
293 |
os.path.dirname(__file__), file_name) |
|
294 |
test_doc_file = open(test_doc_path, 'r') |
|
295 |
try: |
|
296 |
test_doc = test_doc_file.read() |
|
297 |
finally: |
|
298 |
test_doc_file.close() |
|
299 |
if doctests is not None: |
|
300 |
test_doc = test_doc.replace('special = {}', get_special(doctests)) |
|
301 |
test_doc_path = os.path.join(dir_path, file_name) |
|
302 |
if os.path.isfile(test_doc_path): |
|
303 |
# This harness was made in a previous run.
|
|
304 |
print " Skipping %s, it was made in a previous run" % test_doc_path |
|
305 |
return
|
|
306 |
test_doc_file = open(test_doc_path, 'w') |
|
307 |
try: |
|
308 |
test_doc_file.write(test_doc) |
|
309 |
finally: |
|
310 |
test_doc_file.close() |
|
311 |
bzr_add([test_doc_path]) |
|
312 |
||
313 |
||
314 |
def get_special(doctests): |
|
315 |
"""extract the special setups from test_system_documentation."""
|
|
316 |
system_doc_lines = [] |
|
317 |
special_lines = [] |
|
318 |
doctest_pattern = re.compile(r"^ '(%s)[^']*':" % '|'.join(doctests)) |
|
319 |
system_doc_path = os.path.join( |
|
320 |
OLD_TOP, 'ftests', 'test_system_documentation.py') |
|
321 |
system_doc = open(system_doc_path) |
|
322 |
try: |
|
323 |
in_special = False |
|
324 |
for line in system_doc: |
|
325 |
match = doctest_pattern.match(line) |
|
326 |
if match is not None: |
|
327 |
in_special = True |
|
328 |
print ' * Extracting special test for %s' % match.group(1) |
|
329 |
if in_special: |
|
330 |
special_lines.append(line.replace(' ', ' ')) |
|
331 |
else: |
|
332 |
system_doc_lines.append(line) |
|
333 |
if in_special and '),' in line: |
|
334 |
in_special = False |
|
335 |
finally: |
|
336 |
system_doc.close() |
|
337 |
if len(special_lines) == 0: |
|
338 |
# There was nothing to extract.
|
|
339 |
return 'special = {}' |
|
340 |
# Get the setup and teardown functions.
|
|
341 |
special_lines.insert(0, 'special = {\n') |
|
342 |
special_lines.append(' }') |
|
343 |
code = ''.join(special_lines) |
|
344 |
helper_pattern = re.compile(r'\b(setUp|tearDown)=(\w*)\b') |
|
345 |
helpers = set(match.group(2) for match in helper_pattern.finditer(code)) |
|
10234.3.7
by Curtis Hovey
updated code per review. |
346 |
if 'setUp' in helpers: |
10234.3.3
by Curtis Hovey
Migrated hardward database to lp. Updated test_doc to run the hwddb test. |
347 |
helpers.remove('setUp') |
10234.3.7
by Curtis Hovey
updated code per review. |
348 |
if 'tearDown' in helpers: |
10234.3.3
by Curtis Hovey
Migrated hardward database to lp. Updated test_doc to run the hwddb test. |
349 |
helpers.remove('tearDown') |
9492.1.1
by Karl Fogel
Add utilities/formatdoctest.py and utilities/migrater/, both brought |
350 |
# Extract the setup and teardown functions.
|
351 |
lines = list(system_doc_lines) |
|
352 |
system_doc_lines = [] |
|
353 |
helper_lines = [] |
|
354 |
helper_pattern = re.compile(r'^def (%s)\b' % '|'.join(helpers)) |
|
355 |
in_helper = False |
|
356 |
for line in lines: |
|
357 |
if in_helper and len(line) > 1 and line[0] != ' ': |
|
358 |
in_helper = False |
|
359 |
match = helper_pattern.match(line) |
|
360 |
if match is not None: |
|
361 |
in_helper = True |
|
362 |
print ' * Extracting special function for %s' % match.group(1) |
|
363 |
if in_helper: |
|
364 |
helper_lines.append(line) |
|
365 |
else: |
|
366 |
system_doc_lines.append(line) |
|
367 |
if len(helper_lines) > 0: |
|
368 |
code = ''.join(helper_lines) + code |
|
369 |
# Write the smaller test_system_documentation.py.
|
|
370 |
system_doc = open(system_doc_path, 'w') |
|
371 |
try: |
|
372 |
system_doc.write(''.join(system_doc_lines)) |
|
373 |
finally: |
|
374 |
system_doc.close() |
|
375 |
# Return the local app's specials code.
|
|
376 |
special_lines.insert(0, 'special = {\n') |
|
377 |
special_lines.append(' }') |
|
378 |
return code |
|
379 |
||
380 |
||
381 |
def handle_py_file(old_path, new_path, subdir): |
|
382 |
"""Migrate python files."""
|
|
383 |
if subdir in APP_DIRECTORIES: |
|
384 |
# We need the full path, including file name.
|
|
385 |
move_it(old_path, new_path) |
|
386 |
return True |
|
387 |
else: |
|
388 |
return False |
|
389 |
||
390 |
||
391 |
def get_all_module_members(app_name): |
|
392 |
"""Return a dict of dicts of lists; package, module, members."""
|
|
393 |
all_members = {} |
|
394 |
package_names = ['interfaces', 'model', 'browser', 'components'] |
|
395 |
member_pattern = r'^(?:class|def) (?P<name>[\w]*)' |
|
396 |
for package_name in package_names: |
|
397 |
root_dir = os.path.join(NEW_TOP, app_name, package_name) |
|
398 |
module_names = {} |
|
399 |
for summary in find_matches(root_dir, 'py$', member_pattern): |
|
400 |
members = [] |
|
401 |
for line in summary['lines']: |
|
402 |
members.append(line['match'].group('name')) |
|
403 |
module_name, dummy = os.path.splitext( |
|
404 |
os.path.basename(summary['file_path'])) |
|
405 |
# Reverse sorting avoids false-positive matches in REs.
|
|
406 |
module_names[module_name] = sorted(members, reverse=True) |
|
407 |
all_members[package_name] = module_names |
|
408 |
return all_members |
|
409 |
||
410 |
||
411 |
def one_true_import(app_name, all_members): |
|
412 |
"""Replace glob inports from interfaces to avoid circular imports."""
|
|
413 |
app_path = os.path.join(NEW_TOP, app_name) |
|
414 |
print "Replace glob inports from interfaces to avoid circular imports." |
|
415 |
all_interfaces = get_all_interfaces() |
|
416 |
for file_path in find_files(app_path, file_pattern='py$'): |
|
417 |
fix_file_true_import(file_path, all_interfaces) |
|
418 |
||
419 |
||
420 |
def fix_file_true_import(file_path, all_interfaces): |
|
421 |
"""Fix the interface imports in a file."""
|
|
422 |
from textwrap import fill |
|
423 |
bad_pattern = 'from canonical.launchpad.interfaces import' |
|
424 |
delimiters_pattern = re.compile(r'[,()]+') |
|
425 |
import_lines = [] |
|
426 |
content = [] |
|
427 |
in_import = False |
|
428 |
changed = False |
|
429 |
file_ = open(file_path, 'r') |
|
430 |
try: |
|
431 |
for line in file_: |
|
432 |
if in_import and len(line) > 1 and line[0] != ' ': |
|
433 |
in_import = False |
|
434 |
# Build a dict of interfaces used.
|
|
435 |
bad_import = delimiters_pattern.sub( |
|
436 |
' ', ''.join(import_lines)) |
|
437 |
identifiers = bad_import.split()[3:] |
|
438 |
modules = {} |
|
439 |
for identifier in identifiers: |
|
440 |
if identifier not in all_interfaces: |
|
441 |
print ' * missing %s' % identifier |
|
442 |
continue
|
|
443 |
modules.setdefault( |
|
444 |
all_interfaces[identifier], []).append(identifier) |
|
445 |
good_imports = [] |
|
446 |
# Build the import code from the dict.
|
|
447 |
for module_path in sorted(modules): |
|
448 |
symbols = ', '.join(sorted(modules[module_path])) |
|
449 |
if len(symbols) > 78 - len(bad_pattern): |
|
450 |
symbols = '(\n%s)' % fill( |
|
451 |
symbols, width=78, |
|
452 |
initial_indent=' ', subsequent_indent=' ') |
|
453 |
good_imports.append( |
|
454 |
'from %s import %s\n' % (module_path, symbols)) |
|
455 |
# Insert the good imports into the module.
|
|
456 |
content.extend(good_imports) |
|
457 |
if line.startswith(bad_pattern): |
|
458 |
in_import = True |
|
459 |
changed = True |
|
460 |
import_lines = [] |
|
461 |
print ' Fixing interface imports in %s' % file_path |
|
462 |
if in_import: |
|
463 |
import_lines.append(line) |
|
464 |
else: |
|
465 |
content.append(line) |
|
466 |
finally: |
|
467 |
file_.close() |
|
468 |
if changed: |
|
469 |
file_ = open(file_path, 'w') |
|
470 |
try: |
|
471 |
file_.write(''.join(content)) |
|
472 |
finally: |
|
473 |
file_.close() |
|
474 |
||
475 |
||
476 |
def get_all_interfaces(): |
|
477 |
"""return a dict of interface member and module path."""
|
|
478 |
# {'IPersonSet', 'lp.registrty.interfaces.person'}
|
|
479 |
all_interfaces = {} |
|
480 |
member_pattern = r'^(?:class |def )*(?P<name>[\w]*)' |
|
481 |
for summary in find_matches( |
|
482 |
'.', '(canonical|lp)/.*/interfaces.*\.py$', member_pattern): |
|
483 |
module_path, dummy = os.path.splitext(summary['file_path']) |
|
484 |
module_path = module_path.replace('./lib/', '') |
|
485 |
assert module_path.startswith('lp') or module_path.startswith('ca'), ( |
|
486 |
'!! Bad module path.') |
|
487 |
module_path = module_path.replace('/', '.') |
|
488 |
for line in summary['lines']: |
|
489 |
all_interfaces[line['match'].group('name')] = module_path |
|
490 |
return all_interfaces |
|
491 |
||
492 |
||
493 |
def handle_templates(app): |
|
494 |
"""Migrate the page templates referenced in the zcml."""
|
|
495 |
new_browser_path = os.path.join(NEW_TOP, TLA_MAP[app], 'browser') |
|
496 |
new_template_path = os.path.join(NEW_TOP, TLA_MAP[app], 'templates') |
|
497 |
templates = set() |
|
498 |
missing_templates = [] |
|
499 |
shared_templates = [] |
|
500 |
for summary in find_matches( |
|
501 |
new_browser_path, '\.zcml$', r'template="\.\./([^"]+)"'): |
|
502 |
for line in summary['lines']: |
|
503 |
file_name = line['match'].group(1) |
|
504 |
templates.add(os.path.join(OLD_TOP, file_name)) |
|
505 |
# Some views have the template file in the code.
|
|
506 |
for summary in find_matches( |
|
507 |
new_browser_path, '\.py$', r"""\.\./(templates/[^"']+)"""): |
|
508 |
for line in summary['lines']: |
|
509 |
file_name = line['match'].group(1) |
|
510 |
if 'xrds' in file_name: |
|
511 |
# xrds files belong to OpenID and account. Fix the single
|
|
512 |
# reference in the registry tree.
|
|
513 |
old_template = ( |
|
514 |
"../../../canonical/launchpad/templates/person-xrds.pt") |
|
515 |
for dummy in find_matches( |
|
516 |
new_browser_path, 'person.py', |
|
517 |
'../templates/person-xrds.pt', substitution=old_template): |
|
518 |
pass
|
|
519 |
continue
|
|
520 |
templates.add(os.path.join(OLD_TOP, file_name)) |
|
521 |
print "Processing templates" |
|
522 |
for template_path in templates: |
|
523 |
if not os.path.isfile(template_path): |
|
524 |
missing_templates.append(template_path) |
|
525 |
continue
|
|
526 |
if is_shared_template(template_path): |
|
527 |
shared_templates.append(template_path) |
|
528 |
continue
|
|
529 |
bzr_move_file(template_path, new_template_path) |
|
530 |
if len(missing_templates) > 0: |
|
531 |
print "zcml references unknown templates:" |
|
532 |
for file_path in missing_templates: |
|
533 |
print ' %s' % file_path |
|
534 |
if len(shared_templates) > 0: |
|
535 |
print "Warning: many apps reference these templates (fix by hand):" |
|
536 |
for template_path in shared_templates: |
|
537 |
file_name = os.path.basename(template_path) |
|
538 |
print ' %s' % file_name |
|
539 |
# Update the template reference in the browser/*zcml.
|
|
540 |
pattern = r'(template=")\.\./(templates/%s)"' % file_name |
|
541 |
substitution = r'\1../../../canonical/launchpad/\2"' |
|
542 |
for summary in find_matches( |
|
543 |
new_browser_path, '\.zcml$', pattern, |
|
544 |
substitution=substitution): |
|
545 |
pass
|
|
546 |
||
547 |
||
548 |
def is_shared_template(template_path): |
|
549 |
"""Return true if the template is referenced in the old zcml."""
|
|
550 |
old_zcml_path = os.path.join(OLD_TOP, 'zcml') |
|
551 |
file_name = os.path.basename(template_path) |
|
552 |
for dummy in find_matches(old_zcml_path, '\.zcml$', file_name): |
|
553 |
return True |
|
554 |
return False |
|
555 |
||
556 |
||
557 |
def main(ctl_data, apps, opts): |
|
558 |
"""Migrate applications."""
|
|
559 |
# Get a dict keyed by app TLA with all files for that app.
|
|
560 |
app_to_files = convert_ctl_data(ctl_data) |
|
561 |
||
562 |
if len(apps) == 1 and apps[0] == 'all': |
|
563 |
apps = app_to_files.keys() |
|
564 |
||
565 |
not_moved = [] |
|
566 |
for app in apps: |
|
567 |
if app not in app_to_files: |
|
568 |
print "No files tagged for app", app |
|
569 |
continue
|
|
570 |
if app not in TLA_MAP: |
|
571 |
print 'Unknown file owner:', app |
|
572 |
continue
|
|
573 |
||
574 |
app_name = TLA_MAP[app] |
|
575 |
make_tree(app) |
|
576 |
if not opts.move: |
|
577 |
continue
|
|
578 |
||
579 |
app_files = app_to_files[app] |
|
580 |
for fpath in app_files: |
|
581 |
if fpath.endswith('.zcml'): |
|
582 |
# ZCML is processed after modules are moved.
|
|
583 |
not_moved.append(fpath) |
|
584 |
continue
|
|
585 |
print "Processing:", fpath |
|
586 |
full_path = os.path.join(OLD_TOP, fpath) |
|
587 |
if not os.path.exists(full_path): |
|
588 |
# The module has already been moved, ignore.
|
|
589 |
continue
|
|
590 |
to_path = map_filename(fpath) |
|
591 |
path, file_name = os.path.split(to_path) |
|
592 |
spew(" to_path = %s", to_path) |
|
593 |
new_path_to_dir = os.path.join(NEW_TOP, app_name, path) |
|
9668.6.2
by Curtis Hovey
Fixed errors in migrater. |
594 |
new_path_to_fn = os.path.join(NEW_TOP, app_name, to_path) |
9492.1.1
by Karl Fogel
Add utilities/formatdoctest.py and utilities/migrater/, both brought |
595 |
# Special cases.
|
596 |
if set(fpath.split(os.sep)) & TEST_PATHS: |
|
597 |
handle_test(full_path, new_path_to_fn) |
|
598 |
continue
|
|
599 |
subdir = to_path.split(os.sep)[0] |
|
600 |
if subdir in ['scripts']: |
|
601 |
handle_script(full_path, new_path_to_dir) |
|
602 |
continue
|
|
603 |
# Only process python modules.
|
|
604 |
if file_name.endswith('.py'): |
|
605 |
if handle_py_file(full_path, new_path_to_fn, subdir): |
|
606 |
continue
|
|
607 |
||
608 |
not_moved.append(fpath) |
|
609 |
||
610 |
# Create the test harnesses for the moved tests.
|
|
611 |
setup_test_harnesses(app_name) |
|
612 |
# Replace glob imports from interfaces to avoid circular nonsense.
|
|
613 |
app_members = get_all_module_members(app_name) |
|
614 |
one_true_import(app_name, app_members) |
|
615 |
# Migrate the zcml.
|
|
616 |
handle_zcml( |
|
617 |
app_name, OLD_TOP, NEW_TOP, app_files, app_members, not_moved) |
|
618 |
# Move templates referenced by the moved zcml and view classes.
|
|
619 |
handle_templates(app) |
|
620 |
||
621 |
# Warn about the files that weren't moved.
|
|
622 |
if len(not_moved) > 0: |
|
623 |
print ("Warning: the following did not have a rule to " |
|
624 |
"move them. They are left unchanged.") |
|
625 |
for nm in not_moved: |
|
626 |
print " ", nm |
|
627 |
# Warn about collisions.
|
|
628 |
if len(COLLIDED) > 0: |
|
629 |
print ("Warning: the following files collided and need to be " |
|
630 |
"manually fixed.") |
|
631 |
for pow in COLLIDED: |
|
632 |
print " ", pow |
|
633 |
||
634 |
||
635 |
if __name__ == '__main__': |
|
636 |
parser, opts, args = parse_args() |
|
637 |
if len(args) < 2: |
|
638 |
parser.error('Control file and at least one app is required') |
|
639 |
||
640 |
ctl_fn = args[0] |
|
641 |
apps = args[1:] |
|
642 |
||
643 |
ctl_data = open(ctl_fn, 'r').readlines() |
|
644 |
main(ctl_data, apps, opts) |