def test_tree_is_unchanged(self): for path, contents in yield_examples(): expected = cmp.parse(contents, path) actual = cmp.parse(str(cmp.parse(contents, path))) msg = 'Failed on %s.\nExpected\n%s\n\nGot\n%s' % (path, expected, actual) self.assertEqual(expected, actual, msg)
def test_line_numbers_in_exceptions(self): input = '''\ FIND_PACKAGE(ITK) INCLUDE( ''' try: parse(input) self.fail('Expected an exception, but none was raised.') except Exception as e: self.assertTrue('line 2' in str(e))
def main(): # Parse arguments parser = argparse.ArgumentParser( description='Pretty-print CMakeLists files.') parser.add_argument('files', type=str, nargs='*', help='files to pretty print (default is stdin)') parser.add_argument('-t', '--tree', action='store_true', help='print out the syntax trees') args = parser.parse_args() # Gather files filenames = args.files files = [('<stdin>', sys.stdin)] if filenames: files = [(name, open(name)) for name in filenames] # Process files for (name, file) in files: with file: input = file.read() tree = cmp.parse(input, path=name) if args.tree: # Print out AST print(repr(tree)) else: # Pretty print print(str(tree), end='')
def test_parse_nonempty2(self): input = '''\ # Top level comment FIND_PACKAGE(ITK REQUIRED) INCLUDE(${ITK_USE_FILE}) ADD_EXECUTABLE(CastImageFilter CastImageFilter.cxx) TARGET_LINK_LIBRARIES(CastImageFilter # inline comment 1 vtkHybrid #inline comment 2 ITKIO ITKBasicFilters ITKCommon ) ''' output = parse(input) expected = File([ Comment('# Top level comment'), Command('FIND_PACKAGE', [Arg('ITK'), Arg('REQUIRED')]), Command('INCLUDE', [Arg('${ITK_USE_FILE}')]), BlankLine(), Command('ADD_EXECUTABLE', [Arg('CastImageFilter'), Arg('CastImageFilter.cxx')]), Command('TARGET_LINK_LIBRARIES', [ Arg('CastImageFilter', comments=['# inline comment 1']), Arg('vtkHybrid', comments=['#inline comment 2']), Arg('ITKIO'), Arg('ITKBasicFilters'), Arg('ITKCommon'), ]) ]) msg = '\nexpected\n%s\ngot\n%s' % (expected, output) self.assertEqual(expected, output, msg)
def test_parse_nonempty1(self): input = 'FIND_PACKAGE(ITK REQUIRED)' output = parse(input) expected = File( [Command('FIND_PACKAGE', [Arg('ITK'), Arg('REQUIRED')])]) msg = '\nexpected\n%s\ngot\n%s' % (repr(expected), repr(output)) self.assertEqual(expected, output, msg)
def test_idempotency_of_parsing_and_unparsing(self): input = '''\ # Top level comment FIND_PACKAGE(ITK REQUIRED) INCLUDE(${ITK_USE_FILE}) ''' round_trip = lambda s: str(parse(s)) self.assertEqual(round_trip(input), round_trip(round_trip(input)))
def test_arg_comments_preserved(self): input = ''' some_Command ( x # inline comment about x ) ''' output = str(parse(input)) assert output == input
def test_multiline_string(self): s = ''' string containing newlines ''' input = '''\ set (MY_STRING "%s") ''' % s tree = parse(input) expected = File([Command('set', [Arg('MY_STRING'), Arg('"' + s + '"')])]) self.assertEqual(expected, tree)
def test_multiline_string(self): s = ''' string containing newlines ''' input = '''\ set (MY_STRING "%s") ''' % s tree = parse(input) expected = File( [Command('set', [Arg('MY_STRING'), Arg('"' + s + '"')])]) self.assertEqual(expected, tree)
def test_ifs_indented(self): input = ''' if(a) if(b) set(X 1) endif() else(a) if(c) set(Y 2) endif(c) endif(a) ''' self.assertMultiLineEqual(input, str(parse(input)))
def test_comments_preserved(self): input = '''\ # file comment # more about the file # comment above Command1 Command1(VERSION 2.6) # inline comment for Command1 Command2(x # inline comment about x "y" # inline comment about a quoted string "y" ) # inline comment for Command2 ''' output = str(parse(input)) self.assertMultiLineEqual(input, output)
def parseCmakeFile(file): with open(file) as f: contents = f.read() parsed = cmp.parse(contents) packages = [] for x in parsed: if x.__class__.__name__ == 'Command' and x.name == 'find_package': package = [] for arg in x.body: if arg.contents in IGNORE_ARGS or arg.contents.startswith('$') or arg.contents[0].isdigit(): continue package.append(arg.contents) packages.append(package) return packages
def port(content, extra_rules=[], extra_warnings=[]): """ Arguments: content - A string with contents of CMakeLists.txt extra_rules - a list of functions (rules) to apply to the content extra_warnings - a list of functions to check for warnings in the content Returns: A new string with the updated CMakeLists """ cmake = cmkp.parse(content) #Pulls out all methods in this class with name starting with "rule" rules = get_functions_with( criteria=lambda name: name.startswith("rule"), from_class=CMakeListsPorter) for rule in rules + extra_rules: rule(cmake) #Pulls out all methods in this class with name starting with "warn" warnings = get_functions_with( criteria=lambda name: name.startswith("warn"), from_class=CMakeListsPorter) for warning in warnings + extra_warnings: warning(cmake) # This command must be at the bottom of the package cmake.append(cmkp.BlankLine()) ament_package_cmd = cmkp.Command("ament_package", []) cmake.append(ament_package_cmd) cmake_contents = '\n'.join( cmkp.compose_lines(cmake, cmkp.FormattingOptions())) + '\n' #Post-process text #replace variable names: for var, replacement in CatkinToAmentMigration.CMAKE_LISTS_RENAMED_VARIABLES.items( ): cmake_contents = cmake_contents.replace(var, replacement) return cmake_contents
def test_parse_empty_raises_exception(self): self.assertEqual(File([]), parse(''))
def parse_cmake_dependencies(): url = "https://raw.githubusercontent.com/AppImage/AppImageKit/" \ "appimagetool/master/cmake/dependencies.cmake" response = requests.get(url) response.raise_for_status() script = cmp.parse(response.text) for statement in script: if hasattr(statement, "name"): if statement.name.lower() == "externalproject_add": args = statement.body skip_args = 0 for i, arg in enumerate(args): while skip_args > 0: skip_args -= 1 continue if not hasattr(arg, "contents"): continue lc = arg.contents.lower() if lc == "url": # next "arg"'s contents are the URL skip_args = 1 # TODO: support for URL hash yield TarballURL(args[i + 1].contents, None) elif lc == "git_repository": # next "arg"'s contents are the repository URL skip_args = 1 repo_url = args[i+1].contents tag = None # look for tag in following arguments for j, potential_tag in enumerate(args[i+2:]): if not hasattr(arg, "contents"): continue if potential_tag.contents.lower() == "git_tag": tag = args[(i+2)+(j+1)].contents break yield GitRepository(repo_url, tag) # search for patches to download elif lc in ("patch_command"): skip_args = 1 # check if command contains URL for i in map(lambda x: getattr(x, "contents", None), args[i+1:]): if i is None: continue if i in EXTERNALPROJECT_OPTIONS: break i = i.replace("$<SEMICOLON>", ";").strip("\"") # check if i is a URL parsed = urlparse(i) if not (parsed.scheme and parsed.netloc): continue yield PatchURL(i)
def test_parse_nonempty1(self): input = 'FIND_PACKAGE(ITK REQUIRED)' output = parse(input) expected = File([Command('FIND_PACKAGE', [Arg('ITK'), Arg('REQUIRED')])]) msg = '\nexpected\n%s\ngot\n%s' % (repr(expected), repr(output)) self.assertEqual(expected, output, msg)
def test_tree_is_unchanged(self): for path, contents in yield_examples(): expected = cmp.parse(contents, path) actual = cmp.parse(str(cmp.parse(contents, path))) msg = 'Failed on %s.\nExpected\n%s\n\nGot\n%s' % (path, expected, actual) self.assertMultiLineEqual(str(expected), str(actual), msg)
def test_arg_with_a_slash(self): tree = parse('include_directories (${HELLO_SOURCE_DIR}/Hello)') expected = File([ Command('include_directories', [Arg('${HELLO_SOURCE_DIR}/Hello')]) ]) self.assertEqual(expected, tree)
def test_idempotency_of_parse_unparse(self): round_trip = lambda s, path='<string>': str(cmp.parse(s, path)) for path, contents in yield_examples(): self.assertEqual(round_trip(contents, path), round_trip(round_trip(contents, path)), 'Failed on %s' % path)
# General information about the project. project = u'OpenMW' copyright = u'2016, OpenMW Team' # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the # built documents. # # The short X.Y version. # The full version, including alpha/beta/rc tags. from parse_cmake import parsing cmake_raw = open(project_root+'/CMakeLists.txt', 'r').read() cmake_data = parsing.parse(cmake_raw) release = version = int(cmake_data[24][1][1].contents), int(cmake_data[25][1][1].contents), int(cmake_data[26][1][1].contents) # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. #language = cpp # There are two options for replacing |today|: either, you set today to some # non-false value, then it is used: #today = '' # Else, today_fmt is used as the format for a strftime call. #today_fmt = '%B %d, %Y' # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. exclude_patterns = []
def test_command_with_no_args(self): tree = parse('cmd()') expected = File([Command('cmd', [])]) self.assertEqual(expected, tree)
def parse_cmake_dependencies(): url = "https://raw.githubusercontent.com/AppImage/AppImageKit/" \ "appimagetool/master/cmake/dependencies.cmake" response = requests.get(url) response.raise_for_status() script = cmp.parse(response.text) for statement in script: if hasattr(statement, "name"): if statement.name.lower() == "externalproject_add": args = statement.body skip_args = 0 for i, arg in enumerate(args): while skip_args > 0: skip_args -= 1 continue if not hasattr(arg, "contents"): continue lc = arg.contents.lower() if lc == "url": # next "arg"'s contents are the URL skip_args = 1 # TODO: support for URL hash yield TarballURL(args[i + 1].contents, None) elif lc == "git_repository": # next "arg"'s contents are the repository URL skip_args = 1 repo_url = args[i + 1].contents tag = None # look for tag in following arguments for j, potential_tag in enumerate(args[i + 2:]): if not hasattr(arg, "contents"): continue if potential_tag.contents.lower() == "git_tag": tag = args[(i + 2) + (j + 1)].contents break yield GitRepository(repo_url, tag) # search for patches to download elif lc in ("patch_command"): skip_args = 1 # check if command contains URL for i in map(lambda x: getattr(x, "contents", None), args[i + 1:]): if i is None: continue if i in EXTERNALPROJECT_OPTIONS: break i = i.replace("$<SEMICOLON>", ";").strip("\"") # check if i is a URL parsed = urlparse(i) if not (parsed.scheme and parsed.netloc): continue yield PatchURL(i)
def port(cls, dryrun=False): # Make sure the file exists (even though this is already checked) if not exists(CMAKELISTS): print("ERROR: Unable to locate CMakeLists.txt") return False # Read the file content fid = open(CMAKELISTS, "r") content = fid.read() fid.close() # Parse the cmake content cmake = cmkp.parse(content) # Item indices to remove from the file removeIndices = [] # List of catkin packages this package depends on catkinDepends = [] # Libraries created by this package packageLibs = [] # Message and service files to generate msgsAndSrvs = [] projectDeclIndex = -1 hasCpp11 = False for index, item in enumerate(cmake): # Skip non commmand items if type(item) != type(cmkp.Command("a", "b")): continue # Grab names of all arguments to this command args = [b.contents for b in item.body] if item.name == "project": projectDeclIndex = index elif item.name == "add_definitions": # Handle C++11 support added through definitions if "-std=c++11" in args: hasCpp11 = True elif item.name == "set": # Handle C++11 support specifically set to CXX flags if "CMAKE_CXX_FLAGS" in args: for arg in args: if "-std=c++11" in arg: hasCpp11 = True break elif item.name == "find_package": if len(args) > 0 and "catkin" == args[0]: removeIndices.append(index) # An example of command is: # find_package(catkin REQUIRED COMPONENTS pkg1 pkg2) if "COMPONENTS" in args: componentsStart = args.index("COMPONENTS") catkinDepends = args[componentsStart + 1:] generatesMessages = ("message_generation" in args) # Remove packages that no longer exist in ROS 2 catkinDepends = list( filter(lambda p: p not in UNKNOWN_ROS_PACKAGES, catkinDepends)) # Handle packages that have been renamed in ROS 2 catkinDepends = list( map(lambda p: RENAMED_ROS_PACKAGES.get(p, p), catkinDepends)) # Add additional packages needed for message generation if generatesMessages: catkinDepends.extend([ "rosidl_default_generators", "rosidl_default_runtime", ]) elif item.name == "catkin_package": # Remove the catkin_packge element removeIndices.append(index) elif item.name == "include_directories": # Remove the include directories, which will be added later removeIndices.append(index) elif item.name == "link_directories": # Remove the link directories, which will be added later removeIndices.append(index) elif item.name == "catkin_destinations": # Remove this command as it no longer exists removeIndices.append(index) elif item.name == "catkin_metapackage": # Remove this command as it no longer exists removeIndices.append(index) elif item.name == "catkin_python_setup": # Remove this command as it no longer exists removeIndices.append(index) elif item.name == "add_dependencies": # The catkin exported targets variable no longer exists, # so remove this if "${catkin_EXPORTED_TARGETS}" in args: removeIndices.append(index) elif item.name == "target_link_libraries": # Replace the reference to catkin libraries (which no longer # exists) with a reference to libraries variable if "${catkin_LIBRARIES}" in args: catkinIndex = args.index("${catkin_LIBRARIES}") item.body[catkinIndex] = cmkp.Arg("${LIBS}") elif item.name == "add_library": # Keep track of the names of all libraries created # by this package if len(args) > 0: packageLibs.append(args[0]) elif item.name == "add_message_files": if len(args) > 1: msgFiles = list(map(lambda s: "msg/%s" % s, args[1:])) msgsAndSrvs.extend(msgFiles) # Remove this command as it has been replaced removeIndices.append(index) elif item.name == "add_service_files": if len(args) > 1: serviceFiles = list(map(lambda s: "srv/%s" % s, args[1:])) msgsAndSrvs.extend(serviceFiles) # Remove this command as it has been replaced removeIndices.append(index) elif item.name == "generate_messages": # Remove this command as it has been replaced removeIndices.append(index) # Should never happen, but just in case... if projectDeclIndex == -1: print("ERROR: Failed to locate project declaration!") return False # Remove all indices in reverse sorted order to prevent the # indices from changing values for index in sorted(removeIndices, reverse=True): del cmake[index] # Make sure C++11 support is added if not hasCpp11: comment = cmkp.Comment("# Add support for C++11") openIf = cmkp.Command("if", [cmkp.Arg("NOT"), cmkp.Arg("WIN32")]) addDef = cmkp.Command("add_definitions", [cmkp.Arg(contents="-std=c++11")]) closeIf = cmkp.Command("endif", []) # Add all the items items = [ cmkp.BlankLine(), comment, openIf, addDef, closeIf, cmkp.BlankLine(), ] for item in items: projectDeclIndex += 1 cmake.insert(projectDeclIndex, item) ## Add all find_package calls # Must find the ament_cmake package catkinDepends.insert(0, "ament_cmake") # Add calls to find all other dependency packages for pkg in catkinDepends: findPkg = cls.__findPackage(pkg) projectDeclIndex += 1 cmake.insert(projectDeclIndex, findPkg) # Add a blank line projectDeclIndex = cls.__addBlankLine(cmake, projectDeclIndex) ## Handle message generation if len(msgsAndSrvs) > 0: # rosidl_generate_interfaces(urg_node_msgs # "msg/Status.msg" # DEPENDENCIES builtin_interfaces std_msgs # ) cmdArgs = [cmkp.Arg("${PROJECT_NAME}")] for filename in msgsAndSrvs: cmdArgs.append(cmkp.Arg('"%s"' % filename)) # Add dependencies on message packages cmdArgs.extend([ cmkp.Arg("DEPENDENCIES"), cmkp.Arg("builtin_interfaces"), ]) for pkg in catkinDepends: if pkg.endswith("_msgs") or pkg.endswith("srvs"): cmdArgs.append(cmkp.Arg(pkg)) genIfaceCmd = cmkp.Command("rosidl_generate_interfaces", cmdArgs) projectDeclIndex += 1 cmake.insert(projectDeclIndex, genIfaceCmd) # Add a blank line projectDeclIndex = cls.__addBlankLine(cmake, projectDeclIndex) ## Define variables for all include dirs and libraries includeArgs = [cmkp.Arg("INCLUDE_DIRS")] libArgs = [cmkp.Arg("LIBS")] libDirArgs = [cmkp.Arg("LIBRARY_DIRS")] for pkg in catkinDepends: includeArgs.append(cmkp.Arg("${%s_INCLUDE_DIRS}" % pkg)) libArgs.append(cmkp.Arg("${%s_LIBRARIES}" % pkg)) libDirArgs.append(cmkp.Arg("${%s_LIBRARY_DIRS}" % pkg)) # If an include directory exists for this package, add it to # the include dirs if exists("include"): includeArgs.insert(1, cmkp.Arg("include")) # Add command to set include dirs setIncludeDirs = cmkp.Command("set", includeArgs) projectDeclIndex += 1 cmake.insert(projectDeclIndex, setIncludeDirs) ## Add the include_directories command includeDirs = cmkp.Command("include_directories", [cmkp.Arg("${INCLUDE_DIRS}")]) projectDeclIndex += 1 cmake.insert(projectDeclIndex, includeDirs) projectDeclIndex = cls.__addBlankLine(cmake, projectDeclIndex) # Add command to set lib dirs setLibDirs = cmkp.Command("set", libDirArgs) projectDeclIndex += 1 cmake.insert(projectDeclIndex, setLibDirs) projectDeclIndex = cls.__addBlankLine(cmake, projectDeclIndex) ## Add the link_directories command linkDirs = cmkp.Command("link_directories", [cmkp.Arg("${LIBRARY_DIRS}")]) projectDeclIndex += 1 cmake.insert(projectDeclIndex, linkDirs) projectDeclIndex = cls.__addBlankLine(cmake, projectDeclIndex) # Add command to set libs setLibs = cmkp.Command("set", libArgs) projectDeclIndex += 1 cmake.insert(projectDeclIndex, setLibs) projectDeclIndex = cls.__addBlankLine(cmake, projectDeclIndex) ## Export all ament dependencies at the bottom of the file cmake.append(cmkp.BlankLine()) for pkg in catkinDepends: export = cmkp.Command("ament_export_dependencies", [cmkp.Arg(pkg)]) cmake.append(export) ## Export include directories exportIncludes = cmkp.Command("ament_export_include_directories", [cmkp.Arg("${INCLUDE_DIRS}")]) cmake.append(exportIncludes) ## Export all known libraries if len(packageLibs) > 0: exportLibs = cmkp.Command("ament_export_libraries", [cmkp.Arg(lib) for lib in packageLibs]) cmake.append(exportLibs) # Add the final call to initialize the ament package # (this must be at the bottom of the file!) projectDeclIndex = cmake.append(cmkp.BlankLine()) amentPackageCmd = cmkp.Command("ament_package", []) cmake.append(amentPackageCmd) # Remove any double blank lines index = 0 while index < (len(cmake) - 1): isBlank = lambda i: (type(i) == type(cmkp.BlankLine())) item = cmake[index] nextItem = cmake[index + 1] if isBlank(item) and isBlank(nextItem): del cmake[index] continue # Repeat this index index += 1 # Convert the CMakeLists content into a nicely formatted string cmakeData = '\n'.join( cmkp.compose_lines(cmake, cmkp.FormattingOptions())) + '\n' # Replace all instances of variables that no longer exist renamedVariables = { "${CATKIN_DEVEL_PREFIX}/": "", "${CATKIN_GLOBAL_BIN_DESTINATION}": "bin", "${CATKIN_GLOBAL_INCLUDE_DESTINATION}": "include", "${CATKIN_GLOBAL_LIB_DESTINATION}": "lib", "${CATKIN_GLOBAL_LIBEXEC_DESTINATION}": "lib", "${CATKIN_GLOBAL_SHARE_DESTINATION}": "share", "${CATKIN_PACKAGE_BIN_DESTINATION}": "lib/${PROJECT_NAME}", "${CATKIN_PACKAGE_INCLUDE_DESTINATION}": "include/${PROJECT_NAME}", "${CATKIN_PACKAGE_LIB_DESTINATION}": "lib", "${CATKIN_PACKAGE_SHARE_DESTINATION}": "share/${PROJECT_NAME}", } for var, replacement in renamedVariables.items(): cmakeData = cmakeData.replace(var, replacement) if not dryrun: fid = open(CMAKELISTS, "w") fid.write("%s\n" % cmakeData.strip()) fid.close() else: print(cmakeData) return True # Success
master_doc = 'index' # General information about the project. project = u'OpenMW' copyright = u'2016, OpenMW Team' # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the # built documents. # # The short X.Y version. # The full version, including alpha/beta/rc tags. from parse_cmake import parsing cmake_raw = open(project_root + '/CMakeLists.txt', 'r').read() cmake_data = parsing.parse(cmake_raw) release = version = int(cmake_data[24][1][1].contents), int( cmake_data[25][1][1].contents), int(cmake_data[26][1][1].contents) # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. #language = cpp # There are two options for replacing |today|: either, you set today to some # non-false value, then it is used: #today = '' # Else, today_fmt is used as the format for a strftime call. #today_fmt = '%B %d, %Y' # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files.
def parse_file(filename: Path) -> cmp.File: return cmp.parse(filename.read_text())