How can I get all the source dependencies SCons computed for a given target?

Solution for How can I get all the source dependencies SCons computed for a given target?
is Given Below:

I want to do this programmatically right after a given target is built, during SCons build run, not with --tree or any other command to scons. I have a target node. It might have had some explicit dependencies, used scanners, file extension-based scanners, and whatever else SCons calculated. So like:

all_source_nodes = tgt_node.get_all_sources(...)

I searched the docs and the APIs. Only saw get_stored_implicit on FS nodes. I get None on that and also for .prerequisites and .implicit Node members.

I also found that .sources Node member shows direct sources that were passed into the builder. That’s not enough too, of course, because I need essentially all the nodes of the dependency sub-tree, which is a lot more.

You won’t get most of that information at the time the SConstruct/SConscript is processed.

The dependency graph is fully populated after that.

Eureka! 🙂 A combination of build info data and recursive scanner application got me everything scons --tree=prune reports. Full code below, along with some informational messages.

deps = set()
_find_deps(tgt, None, env, deps)
dep_paths = set(map(str, deps))    # Some nodes refer to the same paths    
print ("'{}' - got {} source dependencies:nt{}".format(
        name_str, len(dep_paths), "nt".join(sorted(map(str, deps)))))

def _find_deps(node, children_func, env, visited):
    """Recursively traverses children dependencies from build info and by applying 
    children_func (scanner) to collect the full set of all dependencies ('visited') 
    starting with node as the dependency tree root.
        - node          the current target we check (root node of the sub-tree)
        - children_func the function called to get the children of a node
        - env           the current environment
        - visited:      the set of visited nodes since we started - the final result
    build_info = node.get_binfo()  # current build info
    children = set(build_info.bsources + build_info.bdepends + build_info.bimplicit)
    binfo_deps = set(build_info.bsources + build_info.bdepends + build_info.bimplicit)
    # Apply children func, if available and merge in the results
    scanned_deps = set()
    if children_func:
        scanned_deps = set(children_func(node))
        children |= scanned_deps

    total = len(binfo_deps) + len(scanned_deps)
    total_unique = len(children)
    n_prev_visited = len(visited)
    if total == 0:
        print("'{}' initial   - 0 deps!".format(str(node)))
        print("'{}' initial   - {} deps ({} unique): {} from build info, {} from child func"
            .format(str(node), total, total_unique, len(binfo_deps), len (scanned_deps)))
    # Iterate all the unvisited children and recursively call _find_deps with children_func 
    # specific to each child
    for child in children:
        if child and child not in visited:
            visited.add(child)              # record the dependency
            scanner = node.get_source_scanner(child)
                        # Note: get_build_scanner_path fails on nodes without executor
            path = node.get_build_scanner_path(scanner) if scanner and node.get_executor() else None
            def children_func(node, env=env, scanner=scanner, path=path):
                return node.get_found_includes(env, scanner, path)
            _find_deps(child, children_func, env, visited)

    if len(visited) != n_prev_visited:
        print("'{}' traversed - {} additional deps found.".format(str(node), len(visited) - n_prev_visited))

Getting 700+ dependencies in about a second on a high performance machine. This should be called in a post-action, i.e. after the node is built. Probably will still work fine when called just before the node is built.