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 | ||