7 """ This tool fixes visual studio 2010 projects to have the proper project references.
9 Project files need to refer to other project files that they are dependent on if visual
10 studio is to build them properly. This is a painful task when done manually, but luckily
11 this script automates the process for you.
12 It requires an environment variable called BUILD_TOP that points at the top of all files
13 included in a code base. This is used to find the dependent projects.
16 hmmm: this tool is NOT finished.
22 """ Initializes the class with a set of arguments to work with.
24 The arguments need to be in the form described by print_instructions().
30 src_dir = os.getenv(
"BUILD_TOP")
39 """ Shows the instructions for using this class. """
41 This script will replace all occurrences of a phrase you specify in a set of files. The
42 replacement process will be careful about C and C++ syntax and will not replace occurrences
43 within comments or which are not "complete" phrases (due to other alpha-numeric characters
44 that abut the phrase). The arguments to the script are:
46 {0}: PhraseToReplace ReplacementPhrase File1 [File2 ...]
48 For example, if the phrase to replace is Goop, it will be replaced in these contexts:
52 but it will not be found in these contexts:
60 """ Performs command line argument handling. """
78 """ loads the file into our memory buffer for processing. """
80 our_file =
open(filename,
"rb")
84 print(
"There was an error reading the file {0}".format(filename))
89 print(
"There was an error opening the file {0}".format(filename))
95 """ takes the processed buffer and sends it back out to the filename. """
97 output_filename = filename
99 our_file =
open(output_filename,
"wb")
103 print(
"There was an error writing the file {0}".format(output_filename))
108 print(
"There was an error opening the file {0}".format(output_filename))
116 """ given a character, this returns true if it's between a-z, A-Z or 0-9. """
117 if (check_char[0] ==
"_"):
119 if ( (check_char[0] <=
"z")
and (check_char[0] >=
"a")):
121 if ( (check_char[0] <=
"Z")
and (check_char[0] >=
"A")):
123 if ( (check_char[0] <=
"9")
and (check_char[0] >=
"0")):
129 """ given a string to fix, this replaces all appropriate locations of the phrase. """
132 while (indy < len(fix_string)):
142 char_before = fix_string[indy-1]
157 """ iterates through the stored version of the file and replaces the phrase. """
158 self.
statestate = self.EATING_NORMAL_TEXT;
165 while (len(contents) > 0):
167 next_line = contents[0]
169 contents = contents[1:]
173 if ((len(next_line) > 0)
and (self.
statestate == self.EATING_NORMAL_TEXT)
and (
'/' in next_line)):
175 while (indy < len(next_line)):
177 indy = next_line.find(
'/', indy)
180 if ((len(next_line) > indy + 1)
and (next_line[indy + 1] ==
'/')):
183 next_line = next_line[indy:]
184 self.
statestate = self.EATING_ONELINE_COMMENT
186 self.emit_normal_accumulator()
188 if ((len(next_line) > indy + 1)
and (next_line[indy + 1] ==
'*')):
191 next_line = next_line[indy:]
192 self.
statestate = self.EATING_MULTILINE_COMMENT
194 self.emit_normal_accumulator()
199 if (self.
statestate == self.EATING_NORMAL_TEXT):
203 elif (self.
statestate == self.EATING_ONELINE_COMMENT):
207 self.emit_comment_accumulator()
208 self.
statestate = self.EATING_NORMAL_TEXT
209 elif (self.
statestate == self.EATING_MULTILINE_COMMENT):
214 if (
"*/" in next_line):
216 self.emit_comment_accumulator()
217 self.
statestate = self.EATING_NORMAL_TEXT
219 if (self.
statestate == self.EATING_MULTILINE_COMMENT):
220 print(
"file seems to have unclosed multi-line comment.")
222 self.emit_normal_accumulator()
228 """ Orchestrates the process of replacing the phrases. """
231 if (try_command_line !=
True):
232 print(
"failed to process the command line...\n")
236 for i
in range(0, len(self.
filesfiles)):
237 print(
"file {0} is \'{1}\'".format(i, self.
filesfiles[i]))
239 if (worked
is False):
240 print(
"skipping since file read failed on: {0}".format(self.
filesfiles[i]))
244 if (worked
is False):
245 print(
"skipping, since processing failed on: {0}".format(self.
filesfiles[i]))
248 if (worked
is False):
249 print(
"writing file back failed on: {0}".format(self.
filesfiles[i]))
250 print(
"finished processing all files.")
255 """ the main entry point to the project fixing process.
257 Operates on one project file at a time by:
258 1) finding all libraries (lib files) used by the project A,
259 2) locating the external project that creates each lib file,
260 3) adding a reference to the external projects to our project A.
262 We rely on some important assumptions to get this done:
263 1) project names and project file names are unique across the codebase,
264 2) the structure of the source code hierarchies uses a compatible architecture,
268 print(
"repair is unimplemented")
273 """ locates an XML tag with "tag_name" and returns the contents of the tag.
275 this currently assumes that the start tag, contents, and end tag are all on the same
276 line of text (which is not a very good assumption in general).
278 if (
'<' + tag_name
in file_line):
280 gt_pos = file_line.find(
'>', 0)
281 if (gt_pos < 0):
return ""
282 tag_close_pos = file_line.find(
'</' + tag_name, gt_pos + 1);
283 if (tag_close_pos < 0):
return ""
284 return file_line[gt_pos + 1 : tag_close_pos]
288 """ locates an XML tag with "tag_name" and returns the value of the "attribute_name".
291 if (
'<' + tag_name
not in file_line):
return ""
292 if (attribute_name
not in file_line):
return ""
293 attrib_pos = file_line.find(attribute_name, 0)
295 quote_pos = file_line.find(
'"', attrib_pos)
296 if (quote_pos < 0):
return ""
297 quote_close_pos = file_line.find(
'"', quote_pos + 1)
298 if (quote_close_pos < 0):
return ""
299 return file_line[quote_pos + 1 : quote_close_pos]
304 """ reads in a file and extracts the contents of a particular XML tag.
306 may not want a file read here. better to have a nice established way for
307 dealing with the existing buffer.
311 while (len(contents) > 0):
313 next_line = contents[0]
315 contents = contents[1:]
318 if (found !=
""):
return found
324 """ reads in a visual studio project file and figures out that project's GUID.
326 note that this will fail horribly if the project has been messed with and is no
327 longer in microsoft's official format.
332 """ given a ProjectReference line, this pulls out just the filename involved.
337 """ reads in a visual studio project file and locates all references.
342 while (len(contents) > 0):
344 next_line = contents[0]
346 contents = contents[1:]
350 refs_list.append(ref)
354 """ given an AdditionalDependencies line, this finds the libs listed.
356 just_libs = self.
extract_xml_tagextract_xml_tag(line,
"AdditionalDependencies")
357 if (just_libs ==
""):
return []
358 lib_list = just_libs.split(
';')
360 for i
in range(1, len(lib_list)):
361 if (lib_list[i] ==
'%(AdditionalDependencies)'):
362 lib_list = lib_list[0 : i] + lib_list[i + 1 : len(lib_list)]
368 """ reads in a visual studio project file and locates all dependencies.
370 This will produce a list of the lib files used by c++ projects. These
371 are what we need to link up to their providing projects, if they're
372 actually things that we build.
376 while (len(contents) > 0):
378 next_line = contents[0]
380 contents = contents[1:]
381 if (
'AdditionalDependencies' in next_line):
390 """ determines the asset created by a visual studio project file.
392 This probably only works right on c++ projects. It will figure out the
393 item being created by the project using the breadcrumbs provided.
402 while (len(contents) > 0):
404 next_line = contents[0]
406 contents = contents[1:]
410 if project_name ==
"":
419 if config_type ==
"":
420 temp = self.
extract_xml_tagextract_xml_tag(next_line,
"ConfigurationType")
431 if (config_type !=
"")
and (project_name !=
""):
432 asset_name = project_name
433 if (config_type ==
"DynamicLibrary"): asset_name +=
".dll"
434 elif (config_type ==
"Library"): asset_name +=
".dll"
435 elif (config_type ==
"StaticLibrary"): asset_name +=
".lib"
436 elif (config_type ==
"Application"): asset_name +=
".exe"
437 elif (config_type ==
"WinExe"): asset_name +=
".exe"
438 elif (config_type ==
"Exe"): asset_name +=
".exe"
439 elif (config_type ==
"Utility"):
return ""
441 print(
"unknown configuration type: " + config_type +
"\nin proj file: " + filename)
451 """ traverses the directory in "dir" and finds all the project files.
453 the project files are returned as a massive list.
462 for root, dirs, files
in os.walk(dir):
471 for curr_file
in files:
472 indy = len(curr_file) - 4
474 if curr_file[indy:].lower() ==
'proj':
475 full_path = os.path.join(root, curr_file)
478 to_return.append(full_path)
482 """ calculates path between directory at "our_path" to the location of "project_file".
484 this assumes that the locations are actually rooted at the same place; otherwise there is
485 no path between the locations. the location at "our_path" is considered to be the source,
486 or where we start out. the location for "project_file" is the target location.
490 sourcel = our_path.replace(
'\\',
'/')
491 targee = project_file.replace(
'\\',
'/')
493 sourcel = str.split(sourcel,
'/')
494 targee = str.split(targee,
'/')
498 while (len(sourcel)
and len(targee)
and (sourcel[0] == targee[0])):
506 for i
in range(0, len(sourcel)):
508 print(
"calculated a prefix of: " + prefix)
510 return prefix +
"/".join(targee)
515 """ locates every project file in our list and determines the asset created by it.
517 this returns a dictionary of {asset=project} items. we index by asset way more frequently
518 than by project, so the asset name is used as our key.
524 if (asset_found ==
""):
527 to_return[asset_found] = proj
534 """ finds all the libraries needed by the "project" file and returns their project files.
543 if current
in self.
assetsassets:
545 proj_needed = self.
assetsassets[current]
547 to_return.append(proj_needed)
552 if current[len(current) - 4:] ==
".lib":
554 current = current[0:-4] +
".dll"
556 if current
in self.
assetsassets:
557 proj_needed = self.
assetsassets[current]
559 to_return.append(proj_needed)
566 """ cleans out any references in "project" to assets that we intend to update.
568 this actually modifies the file. it had better be right.
579 """ a sort-of unit test for the functions in this script.
581 currently geared for manual inspection of the test results.
583 print(
"testing some of the methods...")
587 if test_file ==
"": test_file = os.getenv(
"FEISTY_MEOW_APEX") +
"/nucleus/applications/nechung/nechung.vcxproj"
588 print(
"test file is: " + test_file)
591 print(
"from proj, got a guid of " + guid)
594 print(
"refs list is: " +
" ".join(refs))
600 print(
"our created asset is: " + asset)
606 if (len(fixit.projects) > 0):
607 rando = random.randint(0, len(fixit.projects) - 1)
608 print(
"index chosen to examine: {0}".format(rando))
610 relpath = self.
find_relative_pathfind_relative_path(os.path.dirname(test_file), fixit.projects[rando])
611 print(
"found relative path from source:\n " + test_file)
612 print(
"to target:\n " + fixit.projects[rando])
613 print(
"is this =>\n " + relpath)
616 print(
"refs found are:\n" +
" ".join(full_refs))
619 print(
"we just munged your file! go check it! no references should remain that are in our new list.")
631 if __name__ ==
"__main__":
638 print(
"we're bailing before doing anything real...")
641 fixit.repair_project_references()
def is_alphanumeric(self, check_char)
def walk_directory_for_projects(self, dir)
def locate_all_assets(self)
def print_instructions(self)
def repair_project_references(self)
def replace_within_string(self, fix_string)
def extract_filename_from_project_reference(self, file_line)
def locate_referenced_projects(self, project)
def write_file_data(self, filename)
def replace_all_occurrences(self)
def find_all_project_references(self, filename)
def extract_xml_attribute(self, file_line, tag_name, attribute_name)
def remove_redundant_references(self, project)
def extract_xml_tag(self, file_line, tag_name)
def find_asset_created(self, filename)
def extract_xml_tag_from_file(self, filename, tag_name)
def find_relative_path(self, our_path, project_file)
def validate_and_consume_command_line(self)
def process_file_data(self)
def parse_dependency_line(self, line)
def extract_dependencies(self, filename)
def read_file_data(self, filename)
def extract_guid_from_project_file(self, filename)