@@ -46,6 +46,9 @@ def main():
4646 if args .zephyr_base :
4747 os .environ ['ZEPHYR_BASE' ] = args .zephyr_base
4848
49+ strict_scope = StrictScope (args .strict_scope_dir , args .strict_scope_file ) \
50+ if args .strict_flip_checks else None
51+
4952 print ("Parsing " + args .kconfig_file )
5053 kconf = Kconfig (args .kconfig_file , warn_to_stderr = False ,
5154 suppress_traceback = True )
@@ -88,8 +91,8 @@ def main():
8891 # Print warnings for symbols that didn't get the assigned value. Only
8992 # do this for handwritten input too, to avoid likely unhelpful warnings
9093 # when using an old configuration and updating Kconfig files.
91- check_assigned_sym_values (kconf )
92- check_assigned_choice_values (kconf )
94+ check_assigned_sym_values (kconf , strict_scope )
95+ check_assigned_choice_values (kconf , strict_scope )
9396
9497 if kconf .syms .get ('WARN_DEPRECATED' , kconf .y ).tri_value == 2 :
9598 check_deprecated (kconf )
@@ -132,6 +135,9 @@ def main():
132135 if error_out :
133136 err ("Aborting due to Kconfig warnings" )
134137
138+ if strict_scope :
139+ strict_scope .report ()
140+
135141 # Write the merged configuration and the C header
136142 print (kconf .write_config (args .config_out ))
137143 print (kconf .write_autoconf (args .header_out ))
@@ -158,7 +164,68 @@ def check_no_promptless_assign(kconf):
158164symbols. """ + SYM_INFO_HINT .format (sym ))
159165
160166
161- def check_assigned_sym_values (kconf ):
167+ class StrictScope :
168+ # Decides which configuration fragments an ineffective assignment is an
169+ # error for, rather than just a warning.
170+ #
171+ # A fragment is in scope when it sits directly in one of 'dirs' (files in
172+ # subdirectories such as boards/ or socs/ are not in scope), or when it is
173+ # listed in 'files'. The application owns those fragments and knows the
174+ # target they are built for, so an assignment that Kconfig discards there
175+ # is a bug. Everything else - board and SoC defconfigs, shield, snippet and
176+ # module fragments, fragments belonging to another image - is shared
177+ # between targets and cannot be expected to "take" everywhere, so those
178+ # keep warning only.
179+
180+ def __init__ (self , dirs , files ):
181+ self .dirs = {os .path .realpath (d ) for d in dirs }
182+ self .files = {os .path .realpath (f ) for f in files }
183+ self .errors = []
184+
185+ def covers (self , loc ):
186+ if loc is None :
187+ return False
188+
189+ path = os .path .realpath (loc [0 ])
190+ return path in self .files or os .path .dirname (path ) in self .dirs
191+
192+ def add (self , loc , msg ):
193+ # Records 'msg' as an error and returns True if 'loc' is in scope.
194+ # Returns False otherwise, leaving it to the caller to warn instead.
195+
196+ if not self .covers (loc ):
197+ return False
198+
199+ self .errors .append (f"{ loc [0 ]} :{ loc [1 ]} : { msg } " )
200+ return True
201+
202+ def report (self ):
203+ # Prints every recorded error and aborts if there were any. All of them
204+ # are reported at once so that a single build lists everything that
205+ # needs fixing.
206+
207+ if not self .errors :
208+ return
209+
210+ for msg in self .errors :
211+ print ("\n " + textwrap .fill ("error: " + msg , 100 ), file = sys .stderr )
212+
213+ err (f"aborting due to { len (self .errors )} ineffective Kconfig "
214+ "assignment(s) in application-owned configuration. Assign a value "
215+ "that Kconfig can honor, satisfy the missing dependencies, or drop "
216+ "the assignment. Building with -DKCONFIG_STRICT=n downgrades this "
217+ "to a warning." )
218+
219+
220+ def report_ineffective (strict_scope , loc , msg ):
221+ # Turns an ineffective assignment into an error when it comes from a
222+ # fragment in the strict scope, and into a warning otherwise.
223+
224+ if strict_scope is None or not strict_scope .add (loc , msg ):
225+ warn (msg )
226+
227+
228+ def check_assigned_sym_values (kconf , strict_scope = None ):
162229 # Verifies that the values assigned to symbols "took" (matches the value
163230 # the symbols actually got), printing warnings otherwise. Choice symbols
164231 # are checked separately, in check_assigned_choice_values().
@@ -197,7 +264,8 @@ def check_assigned_sym_values(kconf):
197264 msg += "Check these unsatisfied dependencies: " + \
198265 ", " .join (expr_strs ) + ". "
199266
200- warn (msg + SYM_INFO_HINT .format (sym ))
267+ report_ineffective (strict_scope , sym .user_loc ,
268+ msg + SYM_INFO_HINT .format (sym ))
201269
202270
203271def missing_deps (sym ):
@@ -225,7 +293,7 @@ def missing_deps(sym):
225293 return [dep for dep in deps if expr_value (dep ) == 0 ]
226294
227295
228- def check_assigned_choice_values (kconf ):
296+ def check_assigned_choice_values (kconf , strict_scope = None ):
229297 # Verifies that any choice symbols that were selected (by setting them to
230298 # y) ended up as the selection, printing warnings otherwise.
231299 #
@@ -242,7 +310,7 @@ def check_assigned_choice_values(kconf):
242310 if choice .user_selection and \
243311 choice .user_selection is not choice .selection :
244312
245- warn ( f"""\
313+ report_ineffective ( strict_scope , choice . user_selection . user_loc , f"""\
246314 The choice symbol { choice .user_selection .name_and_loc } was selected (set =y),
247315but { choice .selection .name_and_loc if choice .selection else "no symbol" } ended
248316up as the choice selection. """ + SYM_INFO_HINT .format (choice .user_selection ))
@@ -364,6 +432,25 @@ def parse_args():
364432 "set specific configuration settings to a "
365433 "pre-defined value and thereby remove any user "
366434 " adjustments." )
435+ parser .add_argument ("--strict-flip-checks" ,
436+ action = "store_true" ,
437+ help = "Treat an assignment that Kconfig does not honor "
438+ "as an error instead of a warning, for the "
439+ "fragments selected by --strict-scope-dir and "
440+ "--strict-scope-file" )
441+ parser .add_argument ("--strict-scope-dir" ,
442+ action = "append" ,
443+ default = [],
444+ help = "Directory whose configuration fragments the "
445+ "strict checks apply to. Only files directly in "
446+ "the directory are covered, not files in its "
447+ "subdirectories. May be given multiple times." )
448+ parser .add_argument ("--strict-scope-file" ,
449+ action = "append" ,
450+ default = [],
451+ help = "Configuration fragment the strict checks apply "
452+ "to, regardless of its location. May be given "
453+ "multiple times." )
367454 parser .add_argument ("--zephyr-base" ,
368455 help = "Path to current Zephyr installation" )
369456 parser .add_argument ("kconfig_file" ,
0 commit comments