Direct dependencies of action are implicit dependencies

59 views
Skip to first unread message

Alfred Zien

unread,
Feb 6, 2025, 7:14:14 AMFeb 6
to gn-dev
Hi!

I'm not sure this is a bug or expected, but direct dependencies of action are implicit dependencies, not order-only. I checked on latest main in examples/simple_build environment, and this code

action("s") {
script = "s.py"
inputs = [ "b.info" ]
outputs = [ "$target_out_dir/b.processes" ]
deps = [ ":a" ]
}
source_set("a") {
sources = [
"hello_shared.cc",
"hello_shared.h",
]
}

generates

# toolchain.ninja
rule ___s___build_toolchain_gcc__rule
command = python ../s.py
description = ACTION //:s(//build/toolchain:gcc)
restat = 1

build obj/b.stamp: ___s___build_toolchain_gcc__rule | ../s.py ../b.info phony/a

So any change in any sources of ":a" triggers ":s" rebuild. Adding dep_file doesn't help either.

Workaround is to introduce some dummy empty source_set in between, so ":s" would depend on phony/dummy, and ":dummy" depends on ":a" via order only deps.

# dummy.ninja
build phony/dummy: phony || phony/a

Is it expected behaviour?

Nico Weber

unread,
Feb 6, 2025, 9:35:49 AMFeb 6
to Alfred Zien, gn-dev

Dirk Pranke

unread,
Feb 6, 2025, 6:38:19 PMFeb 6
to Alfred Zien, gn-dev, Nico Weber
I would expect the behavior you are describing. I'm not seeing how GN would be able to know that s.py doesn't actually require hello_shared.cc to have been compiled prior to it running?

-- Dirk

To unsubscribe from this group and stop receiving emails from it, send an email to gn-dev+un...@chromium.org.

David Turner

unread,
Feb 7, 2025, 6:07:52 AMFeb 7
to Dirk Pranke, Alfred Zien, gn-dev, Nico Weber
As Dirk said, this is completely normal / working as intended.

More specifically, if action A depends on another target B, this means that its command depends on the outputs of B, even if those are not listed in A's `inputs` or `sources` argument (because the command could still read these outputs and list them in its depfile).

Why does `s` depend on `a` if the s command does not depend on the content of hello_shared.cc then?

Alfred Zien

unread,
Feb 7, 2025, 6:32:48 AMFeb 7
to David Turner, Dirk Pranke, gn-dev, Nico Weber
I think there might be many cases, but I faced one particular, then `a` is a part of template with source_set + script target. So the real dependencies are more like this:
template("foo") {
  action(target_name + "__script") {
    ...
    outputs = [ some_header ]
    deps = invoker.deps
  }
  source_set(target_name) {
    sources = [ ..., some_header ]
    deps = invoker.deps + [":${target_name}__script"]
  }
}

So, it's completely normal for two targets of `foo` template to depend on each other, but what we get is the situation I described. For now, I made a workaround with a dummy source_set:


template("foo") {
  source_set(target_name + "__dummy") {
    deps = invoker.deps
  }
  action(target_name + "__script") {
    ...
    outputs = [ some_header ]
    deps =[":${target_name}__dummy"]
  }
  source_set(target_name) {
    sources = [ ..., some_header ]
    deps = [":${target_name}__script", ":${target_name}__dummy"]
  }
}

Even though script carefully builds its depfile, it still reruns on any source change.

David Turner

unread,
Feb 7, 2025, 8:58:25 AMFeb 7
to Alfred Zien, Dirk Pranke, gn-dev, Nico Weber
To be frank, this is completely different from what you originally described (now the source set depends on the action), and frankly I think you are still not providing enough information.
Can you provide an actual reproduction case for this?

If not, I suggest you debug your build plan with `ninja -d explain <some_header>` after modifying the source file. This will tell you why Ninja decided to re-run the action.

Alfred Zien

unread,
Feb 7, 2025, 10:14:58 AMFeb 7
to David Turner, Dirk Pranke, gn-dev, Nico Weber
Here is complete code to reproduce:

template("foo") {
  header = "$target_gen_dir/$target_name.h"
  _deps = []
  if (defined(invoker.deps)) {
    _deps = invoker.deps
  }
  action(target_name + "__script") {
    script = "s.py"
    inputs = invoker.info_files
    outputs = [ header ]
    deps = _deps
  }
  source_set(target_name) {
    sources = [ header ] + invoker.sources
    deps = _deps + [":${target_name}__script"]
  }
}

foo("target1") {
  info_files = [ "target1.json" ]
  sources = [ "target1.cc" ]
}

foo("target2") {
  info_files = [ "target2.json" ]
  sources = [ "target2.cc" ]
  deps = [ ":target1" ]
}

In toolchain.ninja:

rule ___target2__script___build_toolchain_gcc__rule
  command = python ../s.py
  description = ACTION //:target2__script(//build/toolchain:gcc)
  restat = 1

build gen/target2.h: ___target2__script___build_toolchain_gcc__rule | ../s.py ../target2.json phony/target1

build phony/target2__script: phony gen/target2.h

So, `target2__script` depends on `target1`, and that's a situation that I'm describing.


Answering the question
how GN would be able to know that s.py doesn't actually require hello_shared.cc to have been compiled prior to it running?

It can make order only dependency and then script builds its depfile (like source_set does). If there is no depfile, that means all input files are in `inputs` or `sources` variable, so it's already all good.

Roland McGrath

unread,
Feb 7, 2025, 12:55:56 PMFeb 7
to Alfred Zien, David Turner, Dirk Pranke, gn-dev, Nico Weber
I concur.  If all outputs of all deps are going to be made ninja file dependencies, then there's no reason for action() to require sources/inputs at all (except obviously for actual source files that aren't produced by the build in any way)--but it does.  The standard practice is to list all known-fixed inputs in sources or inputs, and then GN will insist that you also list the target(s) that have those outputs as deps.  If the inputs are not already known, then you need a depfile.  If you have a depfile, then an order-only dep is all you need for any of the deps of the action() target.  On the first build, the order-only dep ensures that things have been built before the action, and then the action will populate the depfile.  On the second build, the depfile has provided all the necessary ninja file dependencies to ensure the action is re-run when any of those files has changed.

Dirk Pranke

unread,
Feb 11, 2025, 3:33:20 PMFeb 11
to Alfred Zien, David Turner, gn-dev, Nico Weber, Roland McGrath
Note that depfiles do not override deps to specify the list of dependencies, they are added to them (i.e., you depend on everything in `deps` *plus* everything in the `depfile`. So, the problem still remains and you need to work around it somehow like you're doing if that's not the behavior you want.

-- Dirk
Reply all
Reply to author
Forward
0 new messages