diff options
| author | Paul Eggleton <paul.eggleton@linux.intel.com> | 2015-05-18 16:08:36 +0100 |
|---|---|---|
| committer | Richard Purdie <richard.purdie@linuxfoundation.org> | 2015-05-19 11:58:45 +0100 |
| commit | aefc80c02ed6d4de5768723d86ce80520387e1d3 (patch) | |
| tree | 9606d0ddd49051ed0f83193c78ce5005225c89c4 /bitbake/lib/bb/utils.py | |
| parent | ba0546bfaf23aa5ba1033e348a0a1addf0623abb (diff) | |
| download | poky-aefc80c02ed6d4de5768723d86ce80520387e1d3.tar.gz | |
bitbake: lib/bb/utils: fix and extend edit_metadata_file()
Fix several bugs and add some useful enhancements to make this into a
more generic metadata editing function:
* Support modifying function values (name must be specified ending with
"()")
* Support dropping values by returning None as the new value
* Split out edit_metadata() function to provide same functionality
on a list/iterable
* Pass operation to callback and allow function to return them
* Pass current output lines to callback so they can be modified
* Fix handling of single-quoted values
* Handle :=, =+, .=, and =. operators
* Support arbitrary indent string
* Support indenting by length of assignment (by specifying -1)
* Fix typo in variablename - intentspc -> indentspc
* Expand function docstring to cover arguments / usage
* Add a parameter to enable matching names with overrides applied
* Add some bitbake-selftest tests
Note that this does change the expected signature of the callback
function. The only known caller is in lib/bb/utils.py itself; I doubt
anyone else has made extensive use of this function yet.
(Bitbake rev: 20059e4d5ab9bf0f32c781ccb208da3c95818018)
Signed-off-by: Paul Eggleton <paul.eggleton@linux.intel.com>
Signed-off-by: Richard Purdie <richard.purdie@linuxfoundation.org>
Diffstat (limited to 'bitbake/lib/bb/utils.py')
| -rw-r--r-- | bitbake/lib/bb/utils.py | 238 |
1 files changed, 180 insertions, 58 deletions
diff --git a/bitbake/lib/bb/utils.py b/bitbake/lib/bb/utils.py index 0db7e56651..988b845a4a 100644 --- a/bitbake/lib/bb/utils.py +++ b/bitbake/lib/bb/utils.py | |||
| @@ -963,14 +963,62 @@ def exec_flat_python_func(func, *args, **kwargs): | |||
| 963 | bb.utils.better_exec(comp, context, code, '<string>') | 963 | bb.utils.better_exec(comp, context, code, '<string>') |
| 964 | return context['retval'] | 964 | return context['retval'] |
| 965 | 965 | ||
| 966 | def edit_metadata_file(meta_file, variables, func): | 966 | def edit_metadata(meta_lines, variables, varfunc, match_overrides=False): |
| 967 | """Edit a recipe or config file and modify one or more specified | 967 | """Edit lines from a recipe or config file and modify one or more |
| 968 | variable values set in the file using a specified callback function. | 968 | specified variable values set in the file using a specified callback |
| 969 | The file is only written to if the value(s) actually change. | 969 | function. Lines are expected to have trailing newlines. |
| 970 | Parameters: | ||
| 971 | meta_lines: lines from the file; can be a list or an iterable | ||
| 972 | (e.g. file pointer) | ||
| 973 | variables: a list of variable names to look for. Functions | ||
| 974 | may also be specified, but must be specified with '()' at | ||
| 975 | the end of the name. Note that the function doesn't have | ||
| 976 | any intrinsic understanding of _append, _prepend, _remove, | ||
| 977 | or overrides, so these are considered as part of the name. | ||
| 978 | These values go into a regular expression, so regular | ||
| 979 | expression syntax is allowed. | ||
| 980 | varfunc: callback function called for every variable matching | ||
| 981 | one of the entries in the variables parameter. The function | ||
| 982 | should take four arguments: | ||
| 983 | varname: name of variable matched | ||
| 984 | origvalue: current value in file | ||
| 985 | op: the operator (e.g. '+=') | ||
| 986 | newlines: list of lines up to this point. You can use | ||
| 987 | this to prepend lines before this variable setting | ||
| 988 | if you wish. | ||
| 989 | and should return a three-element tuple: | ||
| 990 | newvalue: new value to substitute in, or None to drop | ||
| 991 | the variable setting entirely. (If the removal | ||
| 992 | results in two consecutive blank lines, one of the | ||
| 993 | blank lines will also be dropped). | ||
| 994 | newop: the operator to use - if you specify None here, | ||
| 995 | the original operation will be used. | ||
| 996 | indent: number of spaces to indent multi-line entries, | ||
| 997 | or -1 to indent up to the level of the assignment | ||
| 998 | and opening quote, or a string to use as the indent. | ||
| 999 | minbreak: True to allow the first element of a | ||
| 1000 | multi-line value to continue on the same line as | ||
| 1001 | the assignment, False to indent before the first | ||
| 1002 | element. | ||
| 1003 | match_overrides: True to match items with _overrides on the end, | ||
| 1004 | False otherwise | ||
| 1005 | Returns a tuple: | ||
| 1006 | updated: | ||
| 1007 | True if changes were made, False otherwise. | ||
| 1008 | newlines: | ||
| 1009 | Lines after processing | ||
| 970 | """ | 1010 | """ |
| 1011 | |||
| 971 | var_res = {} | 1012 | var_res = {} |
| 1013 | if match_overrides: | ||
| 1014 | override_re = '(_[a-zA-Z0-9-_$(){}]+)?' | ||
| 1015 | else: | ||
| 1016 | override_re = '' | ||
| 972 | for var in variables: | 1017 | for var in variables: |
| 973 | var_res[var] = re.compile(r'^%s[ \t]*[?+]*=' % var) | 1018 | if var.endswith('()'): |
| 1019 | var_res[var] = re.compile('^(%s%s)[ \\t]*\([ \\t]*\)[ \\t]*{' % (var[:-2].rstrip(), override_re)) | ||
| 1020 | else: | ||
| 1021 | var_res[var] = re.compile('^(%s%s)[ \\t]*[?+:.]*=[+.]*[ \\t]*(["\'])' % (var, override_re)) | ||
| 974 | 1022 | ||
| 975 | updated = False | 1023 | updated = False |
| 976 | varset_start = '' | 1024 | varset_start = '' |
| @@ -978,70 +1026,144 @@ def edit_metadata_file(meta_file, variables, func): | |||
| 978 | newlines = [] | 1026 | newlines = [] |
| 979 | in_var = None | 1027 | in_var = None |
| 980 | full_value = '' | 1028 | full_value = '' |
| 1029 | var_end = '' | ||
| 981 | 1030 | ||
| 982 | def handle_var_end(): | 1031 | def handle_var_end(): |
| 983 | (newvalue, indent, minbreak) = func(in_var, full_value) | 1032 | prerun_newlines = newlines[:] |
| 984 | if newvalue != full_value: | 1033 | op = varset_start[len(in_var):].strip() |
| 985 | if isinstance(newvalue, list): | 1034 | (newvalue, newop, indent, minbreak) = varfunc(in_var, full_value, op, newlines) |
| 986 | intentspc = ' ' * indent | 1035 | changed = (prerun_newlines != newlines) |
| 987 | if minbreak: | 1036 | |
| 988 | # First item on first line | 1037 | if newvalue is None: |
| 989 | if len(newvalue) == 1: | 1038 | # Drop the value |
| 990 | newlines.append('%s "%s"\n' % (varset_start, newvalue[0])) | 1039 | return True |
| 1040 | elif newvalue != full_value or (newop not in [None, op]): | ||
| 1041 | if newop not in [None, op]: | ||
| 1042 | # Callback changed the operator | ||
| 1043 | varset_new = "%s %s" % (in_var, newop) | ||
| 1044 | else: | ||
| 1045 | varset_new = varset_start | ||
| 1046 | |||
| 1047 | if isinstance(indent, (int, long)): | ||
| 1048 | if indent == -1: | ||
| 1049 | indentspc = ' ' * (len(varset_new) + 2) | ||
| 1050 | else: | ||
| 1051 | indentspc = ' ' * indent | ||
| 1052 | else: | ||
| 1053 | indentspc = indent | ||
| 1054 | if in_var.endswith('()'): | ||
| 1055 | # A function definition | ||
| 1056 | if isinstance(newvalue, list): | ||
| 1057 | newlines.append('%s {\n%s%s\n}\n' % (varset_new, indentspc, ('\n%s' % indentspc).join(newvalue))) | ||
| 1058 | else: | ||
| 1059 | if not newvalue.startswith('\n'): | ||
| 1060 | newvalue = '\n' + newvalue | ||
| 1061 | if not newvalue.endswith('\n'): | ||
| 1062 | newvalue = newvalue + '\n' | ||
| 1063 | newlines.append('%s {%s}\n' % (varset_new, newvalue)) | ||
| 1064 | else: | ||
| 1065 | # Normal variable | ||
| 1066 | if isinstance(newvalue, list): | ||
| 1067 | if not newvalue: | ||
| 1068 | # Empty list -> empty string | ||
| 1069 | newlines.append('%s ""\n' % varset_new) | ||
| 1070 | elif minbreak: | ||
| 1071 | # First item on first line | ||
| 1072 | if len(newvalue) == 1: | ||
| 1073 | newlines.append('%s "%s"\n' % (varset_new, newvalue[0])) | ||
| 1074 | else: | ||
| 1075 | newlines.append('%s "%s \\\n' % (varset_new, newvalue[0])) | ||
| 1076 | for item in newvalue[1:]: | ||
| 1077 | newlines.append('%s%s \\\n' % (indentspc, item)) | ||
| 1078 | newlines.append('%s"\n' % indentspc) | ||
| 991 | else: | 1079 | else: |
| 992 | newlines.append('%s "%s\\\n' % (varset_start, newvalue[0])) | 1080 | # No item on first line |
| 993 | for item in newvalue[1:]: | 1081 | newlines.append('%s " \\\n' % varset_new) |
| 994 | newlines.append('%s%s \\\n' % (intentspc, item)) | 1082 | for item in newvalue: |
| 1083 | newlines.append('%s%s \\\n' % (indentspc, item)) | ||
| 995 | newlines.append('%s"\n' % indentspc) | 1084 | newlines.append('%s"\n' % indentspc) |
| 996 | else: | 1085 | else: |
| 997 | # No item on first line | 1086 | newlines.append('%s "%s"\n' % (varset_new, newvalue)) |
| 998 | newlines.append('%s " \\\n' % varset_start) | ||
| 999 | for item in newvalue: | ||
| 1000 | newlines.append('%s%s \\\n' % (intentspc, item)) | ||
| 1001 | newlines.append('%s"\n' % intentspc) | ||
| 1002 | else: | ||
| 1003 | newlines.append('%s "%s"\n' % (varset_start, newvalue)) | ||
| 1004 | return True | 1087 | return True |
| 1005 | else: | 1088 | else: |
| 1006 | # Put the old lines back where they were | 1089 | # Put the old lines back where they were |
| 1007 | newlines.extend(varlines) | 1090 | newlines.extend(varlines) |
| 1008 | return False | 1091 | # If newlines was touched by the function, we'll need to return True |
| 1092 | return changed | ||
| 1009 | 1093 | ||
| 1010 | with open(meta_file, 'r') as f: | 1094 | checkspc = False |
| 1011 | for line in f: | 1095 | |
| 1012 | if in_var: | 1096 | for line in meta_lines: |
| 1013 | value = line.rstrip() | 1097 | if in_var: |
| 1014 | varlines.append(line) | 1098 | value = line.rstrip() |
| 1015 | full_value += value[:-1] | 1099 | varlines.append(line) |
| 1016 | if value.endswith('"') or value.endswith("'"): | 1100 | if in_var.endswith('()'): |
| 1017 | full_value = full_value[:-1] | 1101 | full_value += '\n' + value |
| 1018 | if handle_var_end(): | ||
| 1019 | updated = True | ||
| 1020 | in_var = None | ||
| 1021 | else: | 1102 | else: |
| 1022 | matched = False | 1103 | full_value += value[:-1] |
| 1023 | for (varname, var_re) in var_res.iteritems(): | 1104 | if value.endswith(var_end): |
| 1024 | if var_re.match(line): | 1105 | if in_var.endswith('()'): |
| 1025 | splitvalue = line.split('"', 1) | 1106 | if full_value.count('{') - full_value.count('}') >= 0: |
| 1026 | varset_start = splitvalue[0].rstrip() | 1107 | continue |
| 1027 | value = splitvalue[1].rstrip() | 1108 | full_value = full_value[:-1] |
| 1028 | if value.endswith('\\'): | 1109 | if handle_var_end(): |
| 1029 | value = value[:-1] | 1110 | updated = True |
| 1030 | full_value = value | 1111 | checkspc = True |
| 1031 | varlines = [line] | 1112 | in_var = None |
| 1032 | in_var = varname | 1113 | else: |
| 1033 | if value.endswith('"') or value.endswith("'"): | 1114 | skip = False |
| 1034 | full_value = full_value[:-1] | 1115 | for (varname, var_re) in var_res.iteritems(): |
| 1035 | if handle_var_end(): | 1116 | res = var_re.match(line) |
| 1036 | updated = True | 1117 | if res: |
| 1037 | in_var = None | 1118 | isfunc = varname.endswith('()') |
| 1038 | matched = True | 1119 | if isfunc: |
| 1039 | break | 1120 | splitvalue = line.split('{', 1) |
| 1040 | if not matched: | 1121 | var_end = '}' |
| 1041 | newlines.append(line) | 1122 | else: |
| 1123 | var_end = res.groups()[-1] | ||
| 1124 | splitvalue = line.split(var_end, 1) | ||
| 1125 | varset_start = splitvalue[0].rstrip() | ||
| 1126 | value = splitvalue[1].rstrip() | ||
| 1127 | if not isfunc and value.endswith('\\'): | ||
| 1128 | value = value[:-1] | ||
| 1129 | full_value = value | ||
| 1130 | varlines = [line] | ||
| 1131 | in_var = res.group(1) | ||
| 1132 | if isfunc: | ||
| 1133 | in_var += '()' | ||
| 1134 | if value.endswith(var_end): | ||
| 1135 | full_value = full_value[:-1] | ||
| 1136 | if handle_var_end(): | ||
| 1137 | updated = True | ||
| 1138 | checkspc = True | ||
| 1139 | in_var = None | ||
| 1140 | skip = True | ||
| 1141 | break | ||
| 1142 | if not skip: | ||
| 1143 | if checkspc: | ||
| 1144 | checkspc = False | ||
| 1145 | if newlines[-1] == '\n' and line == '\n': | ||
| 1146 | # Squash blank line if there are two consecutive blanks after a removal | ||
| 1147 | continue | ||
| 1148 | newlines.append(line) | ||
| 1149 | return (updated, newlines) | ||
| 1150 | |||
| 1151 | |||
| 1152 | def edit_metadata_file(meta_file, variables, varfunc): | ||
| 1153 | """Edit a recipe or config file and modify one or more specified | ||
| 1154 | variable values set in the file using a specified callback function. | ||
| 1155 | The file is only written to if the value(s) actually change. | ||
| 1156 | This is basically the file version of edit_metadata(), see that | ||
| 1157 | function's description for parameter/usage information. | ||
| 1158 | Returns True if the file was written to, False otherwise. | ||
| 1159 | """ | ||
| 1160 | with open(meta_file, 'r') as f: | ||
| 1161 | (updated, newlines) = edit_metadata(f, variables, varfunc) | ||
| 1042 | if updated: | 1162 | if updated: |
| 1043 | with open(meta_file, 'w') as f: | 1163 | with open(meta_file, 'w') as f: |
| 1044 | f.writelines(newlines) | 1164 | f.writelines(newlines) |
| 1165 | return updated | ||
| 1166 | |||
| 1045 | 1167 | ||
| 1046 | def edit_bblayers_conf(bblayers_conf, add, remove): | 1168 | def edit_bblayers_conf(bblayers_conf, add, remove): |
| 1047 | """Edit bblayers.conf, adding and/or removing layers""" | 1169 | """Edit bblayers.conf, adding and/or removing layers""" |
| @@ -1070,7 +1192,7 @@ def edit_bblayers_conf(bblayers_conf, add, remove): | |||
| 1070 | # Need to use a list here because we can't set non-local variables from a callback in python 2.x | 1192 | # Need to use a list here because we can't set non-local variables from a callback in python 2.x |
| 1071 | bblayercalls = [] | 1193 | bblayercalls = [] |
| 1072 | 1194 | ||
| 1073 | def handle_bblayers(varname, origvalue): | 1195 | def handle_bblayers(varname, origvalue, op, newlines): |
| 1074 | bblayercalls.append(varname) | 1196 | bblayercalls.append(varname) |
| 1075 | updated = False | 1197 | updated = False |
| 1076 | bblayers = [remove_trailing_sep(x) for x in origvalue.split()] | 1198 | bblayers = [remove_trailing_sep(x) for x in origvalue.split()] |
| @@ -1094,9 +1216,9 @@ def edit_bblayers_conf(bblayers_conf, add, remove): | |||
| 1094 | notadded.append(addlayer) | 1216 | notadded.append(addlayer) |
| 1095 | 1217 | ||
| 1096 | if updated: | 1218 | if updated: |
| 1097 | return (bblayers, 2, False) | 1219 | return (bblayers, None, 2, False) |
| 1098 | else: | 1220 | else: |
| 1099 | return (origvalue, 2, False) | 1221 | return (origvalue, None, 2, False) |
| 1100 | 1222 | ||
| 1101 | edit_metadata_file(bblayers_conf, ['BBLAYERS'], handle_bblayers) | 1223 | edit_metadata_file(bblayers_conf, ['BBLAYERS'], handle_bblayers) |
| 1102 | 1224 | ||
