diff --git a/share/analysis-scripts/build.py b/share/analysis-scripts/build.py
index f6da389562c9dbc1290fc1829ebc3e7f5bdf0453..784967783c3f45131220b58aecc4b3aeb2f4b399 100755
--- a/share/analysis-scripts/build.py
+++ b/share/analysis-scripts/build.py
@@ -107,7 +107,6 @@ if not blug:
         sys.exit("error: path to 'blug' binary must be in PATH or variable BLUG")
 blug = Path(blug)
 blug_dir = blug.resolve().parent
-blug_print = blug_dir / "blug-print"
 # to import blug_jbdb
 sys.path.insert(0, blug_dir.as_posix())
 import blug_jbdb
@@ -199,7 +198,8 @@ def find_definitions(funcname, filename):
     file_content = source_filter.open_and_filter(filename, not under_test)
     file_lines = file_content.splitlines(keepends=True)
     newlines = function_finder.compute_newline_offsets(file_lines)
-    defs = function_finder.find_definitions_and_declarations(True, False, filename, file_content, file_lines, newlines, funcname)
+    defs = function_finder.find_definitions_and_declarations(
+        True, False, filename, file_content, file_lines, newlines, funcname)
     res = []
     for d in defs:
         defining_line = file_lines[d[2]-1]
@@ -213,6 +213,22 @@ def find_definitions(funcname, filename):
         res.append((d[2], has_args))
     return res
 
+def list_partition(f, l):
+    """Equivalent to OCaml's List.partition: returns 2 lists with the elements of l,
+    partitioned according to predicate f."""
+    l1 = []
+    l2 = []
+    for e in l:
+        if f(l):
+            l1.append(e)
+        else:
+            l2.append(e)
+    return l1, l2
+
+def pp_list(l):
+    """Applies prettify to a list of sources/targets and sorts the result."""
+    return sorted([prettify(e) for e in l])
+
 # End of auxiliary functions ##################################################
 
 sources_map = dict()
@@ -220,34 +236,47 @@ if sources:
     if not targets:
         sys.exit("error: option --targets is mandatory when --sources is specified")
     if len(targets) > 1:
-        sys.exit("error: option --targets can only have a single target when --sources is specified")
-    sources_map[targets[0]] = [s for s in sources if blug_jbdb.filter_source(s)]
+        sys.exit("error: option --targets can only have a single target \
+        when --sources is specified")
+    sources_map[targets[0]] = sources
 elif os.path.isfile(jbdb_path):
     # JBDB exists
     with open(jbdb_path, "r") as data:
         jbdb = json.load(data)
     blug_jbdb.absolutize_jbdb(jbdb)
-    jbdb_targets = []
+    filter_source, filter_target = blug_jbdb.get_filters("c-programs")
+    # program_targets are those we prefer, and the only ones used in "automatic" mode;
+    # non_program_targets are only used if the user specified them in the command line.
+    program_targets = []
+    non_program_targets = []
     for f in jbdb:
-        jbdb_targets += [t for t in f["targets"] if blug_jbdb.filter_target(t)]
-    if not jbdb_targets:
+        programs, other_targets = list_partition(filter_target, f["targets"])
+        program_targets += programs
+        non_program_targets += other_targets
+    logging.debug("program_targets: %s", pp_list(program_targets))
+    logging.debug("non_program_targets: %s", pp_list(non_program_targets))
+    all_jbdb_targets = program_targets + non_program_targets
+    if not all_jbdb_targets:
         sys.exit(f"no targets found in JBDB ({jbdb_path})")
     if not targets:
-        # no targets specified in command line; use all from JBDB
-        targets = jbdb_targets
+        # no targets specified in command line; use all programs from JBDB
+        targets = program_targets
     logging.info("Computing sources for each target (%d target(s))...", len(targets))
-    unknown_targets = []
+    unknown_targets_from_cmdline = []
     graph = blug_jbdb.build_graph(jbdb)
     for target in targets:
-        if target not in jbdb_targets:
-            unknown_targets.append(target)
+        if target not in all_jbdb_targets:
+            unknown_targets_from_cmdline.append(target)
+            # do not return immediately; we want to report all invalid targets at once
         else:
-            if unknown_targets != []:
-                continue # already found a problem; avoid useless computations
-            sources = [s for s in blug_jbdb.collect_leaves(graph, [target]) if blug_jbdb.filter_source(s)]
-            sources_map[target] = sorted(sources)
-    if unknown_targets:
-        targets_pretty = "\n".join(unknown_targets)
+            if unknown_targets_from_cmdline != []:
+                continue # keep looping to accumulate all invalid targets, but avoid extra work
+            sources = blug_jbdb.collect_leaves(graph, [target])
+            c_sources, non_c_sources = list_partition(filter_source, sources)
+            logging.debug("non_c_sources: %s", pp_list(non_c_sources))
+            sources_map[target] = c_sources
+    if unknown_targets_from_cmdline:
+        targets_pretty = "\n".join(unknown_targets_from_cmdline)
         sys.exit("target(s) not found in JBDB:\n{targets_pretty}")
 else:
     if not jbdb_path:
@@ -255,13 +284,16 @@ else:
     else:
         sys.exit(f"error: invalid JBDB path: '{jbdb_path}'")
 
-logging.debug("sources_map: %s", sorted([prettify(k) + ": " + ', '.join(sorted([prettify(s) for s in v])) for (k, v) in sources_map.items()]))
-logging.debug("targets: %s", sorted([prettify(t) for t in targets]))
+logging.debug("sources_map: %s",
+              sorted([prettify(k) + ": " + ', '.join(pp_list(v))
+                      for (k, v) in sources_map.items()]))
+logging.debug("targets: %s", pp_list(targets))
 
 # check that source files exist
-unknown_sources = sorted({s for sources in sources_map.values() for s in sources if not s.exists()})
+unknown_sources = sorted({s for sources in sources_map.values()
+                          for s in sources if not s.exists()})
 if unknown_sources:
-    sys.exit("error: source(s) not found:\n" + "\n".join([prettify(s) for s in unknown_sources]))
+    sys.exit("error: source(s) not found:\n" + "\n".join(pp_list(unknown_sources)))
 
 # Check that the main function is defined exactly once per target.
 # note: this is only based on heuristics (and fails on a few real case studies),
@@ -276,9 +308,11 @@ for target, sources in sources_map.items():
         fundefs = find_definitions(main, source)
         main_definitions[target] += [(source, fundef[0], fundef[1]) for fundef in fundefs]
     if main_definitions[target] == []:
-        logging.warning("function '%s' seems to be never defined in the sources of target '%s'", main, prettify(target))
+        logging.warning("function '%s' seems to be never defined in the sources of target '%s'",
+                        main, prettify(target))
     elif len(main_definitions[target]) > 1:
-        logging.warning("function '%s' seems to be defined multiple times in the sources of target '%s':", main, prettify(target))
+        logging.warning("function '%s' seems to be defined multiple times in the sources of \
+        target '%s':", main, prettify(target))
         for (filename, line, _) in main_definitions[target]:
             print(f"- definition at {filename}:{line}")
 
@@ -302,7 +336,8 @@ if any_has_arguments:
     fc_stubs = copy_fc_stubs()
     for target in targets:
         if any(d[2] for d in main_definitions[target]):
-            logging.debug("target %s has main with args, adding fc_stubs.c to its sources", prettify(target))
+            logging.debug("target %s has main with args, adding fc_stubs.c to its sources",
+                          prettify(target))
             sources_map[target].insert(0, fc_stubs)
 
 gnumakefile = dot_framac_dir / "GNUmakefile"
@@ -312,11 +347,13 @@ template = lines_of_file(share_dir / "analysis-scripts" / "template.mk")
 if machdep:
     machdeps = fc_config['machdeps']
     if machdep not in machdeps:
-        logging.warning("unknown machdep (%s) not in Frama-C's default machdeps:\n%s", machdep, " ".join(machdeps))
+        logging.warning("unknown machdep (%s) not in Frama-C's default machdeps:\n%s",
+                        machdep, " ".join(machdeps))
     replace_line(template, "^MACHDEP = .*", f"MACHDEP = {machdep}")
 
 if jbdb_path:
-    insert_lines_after(template, "^FCFLAGS", [f"  -json-compilation-database {rel_prefix(jbdb_path)} \\"])
+    insert_lines_after(template, "^FCFLAGS",
+                       [f"  -json-compilation-database {rel_prefix(jbdb_path)} \\"])
 
 targets_eva = ([f"  {make_target_name(target)}.eva \\" for target in targets])
 replace_line(template, "^TARGETS = main.eva", "TARGETS = \\")
@@ -328,7 +365,8 @@ for target, sources in reversed(sources_map.items()):
     pp_target = make_target_name(target)
     new_lines = [f"{pp_target}.parse: \\"] + pretty_sources(sources) + [""]
     if any(d[2] for d in main_definitions[target]):
-        logging.debug("target %s has main with args, adding -main eva_main to its FCFLAGS", prettify(target))
+        logging.debug("target %s has main with args, adding -main eva_main to its FCFLAGS",
+                      prettify(target))
         new_lines += [f"{pp_target}.parse: FCFLAGS += -main eva_main", ""]
     insert_lines_after(template, "^### Each target <t>.eva", new_lines)