I'm either confused about toolchain_args, or think about it the wrong way, or have issues with either its usability or with the usability of forward_variables_from(). Please help me figure out which one it is :)
I have a build that has a normal host toolchain used to build clang, and then a second toolchain that uses the just-built clang to build something else.
There's a gn arg "clang_base_path" that, when set, tells the host toolchain to use the clang at that path as compiler. The build sets a few other args (is_clang, ...) depending on if this arg is set.
For the second toolchain, I figured it'd be a good idea to do `toolchain_args = { clang_base_path = "." }` since that way the other args get a good default. The full setup looks roughly like so:
template("unix_toolchain") {
toolchain(target_name) {
cc = "cc"
if (clang_base_path != "") {
cc = "$clang_base_path/bin/clang"
}
tool("cc") {
command = "$cc -o {{output}} -c {{source}}
outputs = [ "{{source_out_dir}}/{{label_name}}.{{source_name_part}}.o" ]
}
}
}
unix_toolchain("host_default") {
toolchain_args = {
current_os = host_os
current_cpu = host_cpu
}
}
unix_toolchain("stage2_toolchain") {
toolchain_args = {
clang_base_path = "."
current_os = host_os
current_cpu = host_cpu
}
deps = [ "//:clang($host_toolchain)" ]
}
Now, for stage2_toolchain, clang_base_path is not set in the unix_toolchain template, but it is set in the normal build files that are evaluated in the context of stage2_toolchain. I'm guessing the reason for that is that stage2_toolchain is defined in the default toolchain, not in itself, so the toolchain_args haven't had an effect yet.
Is that correct? Is that expected? Is that ever what we want? Should secondary toolchains implicitly forward all variables from toolchain_args into the definition of the toolchain, just like they forward the toolchain_args to targets using that toolchain?
Assuming this is correct and intentional, I figured I'd do the forwarding of toolchain_args myself, by adding this in unix_toolchain:
forward_variables_from(invoker.toolchain_args, "*")
But, unix_toolchain doesn't use current_os or current_cpu as variable, so I get an error about stage2_toolchain setting these in toolchain_args not having an effect.
So instead I could do:
forward_variables_from(invoker.toolchain_args, ["clang_base_path"])
There are two problems with this:
1. forward_variables_from() has different semantics when forwarding "*" then when forwarding an explicit list. "*" is allowed to clobber existing variables, but the explicit list doesn't have this permission and errors on clobbers. Since clang_base_path is a declare_args, this does clobber and I get a warning. Why do explicit lists have this error? It makes forwarding things other than * fairly verbose and repetitive.
2. I need to explicitly list all toolchain_args I used directly in the toolchain definition, and if I add a use of another toolchain_arg later I need to remember to update this list, else I silently get the default toolchain value, not the secondary toolchain value.
1 can be fixed by doing this instead:
invoker_toolchain = invoker.toolchain_args
if (defined(invoker_toolchain.clang_base_path) {
clang_base_path = invoker_toolchain.clang_base_path
}
This works! But it doesn't help with 2, and I need to repeat "clang_base_path" 3 times, and I need this for every toolchain_arg that I use directly in the toolchain definition. (In my real project, there's more than just 1.)
Yet another approach would be to set clang_base_path in stage2_toolchain not as a toolchain_arg but as a normal variable, do
forward_variables_from(invoker, "*", ["toolchain_args"])
in unix_toolchain, and then also in unix_toolchain copy it into toolchain_args. But this runs into the same forward_variables_from() explicit-list ugliness mentioned above since I have to define toolchain_args in unix_toolchain like so:
toolchain_args = {
forward_variables_from(invoker.toolchain_args, "*")
if (defined(invoker.clang_base_path)) {
clang_base_path = invoker.clang_base_path
}
}
Here the failure mode for 2 (if I add a use of a new variable in unix_toolchain) is that I forget to update toolchain_args instead, which means I always use the right value of the variable when defining the toolchain, but if I forget to update the list then I'll get the wrong value in the targets defined with this toolchain.
This seems the least bad approach I can currently use. Maybe I missed some other alternative?
1. toolchain() behaved as if toolchain_args are defined as variables in the toolchain automatically
2. forward_variables_from(invoker.toolchain_args, "*") wouldn't cause unused variable errors for variables from toolchain_args (since these arguably have a use through the toolchain_args already)
3. forward_variables_from(..., [list]) could clobber values (but this is worse than 1 and 2 since it doesn't help with problem 2 above)