1# 2# Copyright 2013, 2018 Free Software Foundation, Inc. 3# 4# This file is part of GNU Radio 5# 6# GNU Radio is free software; you can redistribute it and/or modify 7# it under the terms of the GNU General Public License as published by 8# the Free Software Foundation; either version 3, or (at your option) 9# any later version. 10# 11# GNU Radio is distributed in the hope that it will be useful, 12# but WITHOUT ANY WARRANTY; without even the implied warranty of 13# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14# GNU General Public License for more details. 15# 16# You should have received a copy of the GNU General Public License 17# along with GNU Radio; see the file COPYING. If not, write to 18# the Free Software Foundation, Inc., 51 Franklin Street, 19# Boston, MA 02110-1301, USA. 20# 21""" Edit CMakeLists.txt files """ 22 23from __future__ import print_function 24from __future__ import absolute_import 25from __future__ import unicode_literals 26 27import re 28import logging 29 30logger = logging.getLogger(__name__) 31 32 33class CMakeFileEditor(object): 34 """A tool for editing CMakeLists.txt files. """ 35 def __init__(self, filename, separator='\n ', indent=' '): 36 self.filename = filename 37 with open(filename, 'r') as f: 38 self.cfile = f.read() 39 self.separator = separator 40 self.indent = indent 41 42 def append_value(self, entry, value, to_ignore_start='', to_ignore_end=''): 43 """ Add a value to an entry. """ 44 regexp = re.compile(r'({}\({}[^()]*?)\s*?(\s?{})\)'.format(entry, to_ignore_start, to_ignore_end), 45 re.MULTILINE) 46 substi = r'\1' + self.separator + value + r'\2)' 47 (self.cfile, nsubs) = regexp.subn(substi, self.cfile, count=1) 48 return nsubs 49 50 def remove_value(self, entry, value, to_ignore_start='', to_ignore_end=''): 51 """ 52 Remove a value from an entry. 53 Example: You want to remove file.cc from this list() entry: 54 list(SOURCES 55 file.cc 56 other_file.cc 57 ) 58 59 Then run: 60 >>> C.remove_value('list', 'file.cc', 'SOURCES') 61 62 Returns the number of occurrences of entry in the current file 63 that were removed. 64 """ 65 # In the case of the example above, these are cases we need to catch: 66 # - list(file.cc ... 67 # entry is right after the value parentheses, no whitespace. Can only happen 68 # when to_ignore_start is empty. 69 # - list(... file.cc) 70 # Other entries come first, then entry is preceded by whitespace. 71 # - list(SOURCES ... file.cc) # whitespace! 72 # When to_ignore_start is not empty, entry must always be preceded by whitespace. 73 if len(to_ignore_start) == 0: 74 regexp = r'^\s*({entry}\((?:[^()]*?\s+|)){value}\s*([^()]*{to_ignore_end}\s*\)){to_ignore_start}' 75 else: 76 regexp = r'^\s*({entry}\(\s*{to_ignore_start}[^()]*?\s+){value}\s*([^()]*{to_ignore_end}\s*\))' 77 regexp = regexp.format( 78 entry=entry, 79 to_ignore_start=to_ignore_start, 80 value=value, 81 to_ignore_end=to_ignore_end, 82 ) 83 regexp = re.compile(regexp, re.MULTILINE) 84 (self.cfile, nsubs) = re.subn(regexp, r'\1\2', self.cfile, count=1) 85 return nsubs 86 87 def delete_entry(self, entry, value_pattern=''): 88 """Remove an entry from the current buffer.""" 89 regexp = r'{}\s*\([^()]*{}[^()]*\)[^\n]*\n'.format(entry, value_pattern) 90 regexp = re.compile(regexp, re.MULTILINE) 91 (self.cfile, nsubs) = re.subn(regexp, '', self.cfile, count=1) 92 return nsubs 93 94 def write(self): 95 """ Write the changes back to the file. """ 96 with open(self.filename, 'w') as f: 97 f.write(self.cfile) 98 99 def remove_double_newlines(self): 100 """Simply clear double newlines from the file buffer.""" 101 self.cfile = re.compile('\n\n\n+', re.MULTILINE).sub('\n\n', self.cfile) 102 103 def find_filenames_match(self, regex): 104 """ Find the filenames that match a certain regex 105 on lines that aren't comments """ 106 filenames = [] 107 reg = re.compile(regex) 108 fname_re = re.compile(r'[a-zA-Z]\w+\.\w{1,5}$') 109 for line in self.cfile.splitlines(): 110 if len(line.strip()) == 0 or line.strip()[0] == '#': 111 continue 112 for word in re.split('[ /)(\t\n\r\f\v]', line): 113 if fname_re.match(word) and reg.search(word): 114 filenames.append(word) 115 return filenames 116 117 def disable_file(self, fname): 118 """ Comment out a file. 119 Example: 120 add_library( 121 file1.cc 122 ) 123 124 Here, file1.cc becomes #file1.cc with disable_file('file1.cc'). 125 """ 126 starts_line = False 127 for line in self.cfile.splitlines(): 128 if len(line.strip()) == 0 or line.strip()[0] == '#': 129 continue 130 if re.search(r'\b'+fname+r'\b', line): 131 if re.match(fname, line.lstrip()): 132 starts_line = True 133 break 134 comment_out_re = r'#\1' + '\n' + self.indent 135 if not starts_line: 136 comment_out_re = r'\n' + self.indent + comment_out_re 137 (self.cfile, nsubs) = re.subn(r'(\b'+fname+r'\b)\s*', comment_out_re, self.cfile) 138 if nsubs == 0: 139 logger.warning("Warning: A replacement failed when commenting out {}. Check the CMakeFile.txt manually.".format(fname)) 140 elif nsubs > 1: 141 logger.warning("Warning: Replaced {} {} times (instead of once). Check the CMakeFile.txt manually.".format(fname, nsubs)) 142 143 def comment_out_lines(self, pattern, comment_str='#'): 144 """ Comments out all lines that match with pattern """ 145 for line in self.cfile.splitlines(): 146 if re.search(pattern, line): 147 self.cfile = self.cfile.replace(line, comment_str+line) 148 149 def check_for_glob(self, globstr): 150 """ Returns true if a glob as in globstr is found in the cmake file """ 151 glob_re = r'GLOB\s[a-z_]+\s"{}"'.format(globstr.replace('*', r'\*')) 152 return re.search(glob_re, self.cfile, flags=re.MULTILINE|re.IGNORECASE) is not None 153