[CMake] CMake 3.9 change to dependencies of object compilation

Puetz Kevin A PuetzKevinA at JohnDeere.com
Fri Aug 4 15:46:37 EDT 2017


> -----Original Message-----
> From: Ben Boeckel [mailto:ben.boeckel at kitware.com]
> Sent: Friday, August 04, 2017 12:55 PM
> To: Puetz Kevin A <PuetzKevinA at JohnDeere.com>
> Cc: cmake at cmake.org; Robert Maynard <robert.maynard at kitware.com>
> Subject: Re: CMake 3.9 change to dependencies of object compilation
> 
> On Fri, Aug 04, 2017 at 17:35:53 +0000, Puetz Kevin A wrote:

> > > > 2. MSVC's #import construct which needs the indirect dependencies
> > > > (dependencies of the #import-ed dependency) be registered, which
> > > > is handled as part of the target using add_custom_command(TARGET
> > > > foo POST_BUILD COMMAND ...)
> > >
> > > So there's an issue here that there's a dependency between your
> > > build rules which CMake doesn't know about (though I don't know
> > > #import well enough, the docs don't state where the information
> *goes*).
> >
> > #import will load a COM typelib during preprocessing, possibly
> > following registry keys to locate other typelibs which the specified
> > one refers to. It will have the byproduct of creating .tlh/.tli files
> > next to the other compiler outputs (e.g. .o file) Arguably the
> > .tlh/.tli files should be listed in OBJECT_OUTPUTS, but I can't
> > because I don't know their location; CMake doesn't have a
> > variable/property/generator expression that reveals where it's going
> > to place the object files (i.e. /Fo$out), so I don't know where they
> > will end up. Luckily the .tlh/.tli files aren't important to list for
> > dependency resolution anyway, because the #import also automatically
> > #includes the just-generated headers, (though this is not mentioned in
> > /showIncludes). So CMake is at least *consistently* unaware of these
> > files, and they get regenerated any time they would have been read so
> > it doesn't really need to know.
> 
> OK, a genex for where object outputs may be useful anyways. I think there's
> something along those lines with Cuda's PTX file generation?

It would also be really nice for things like precompiled headers; I have some custom commands where it really feels right to put their outputs with the other object files for a target (this automatically getting things right for multi-configuration generators and such), but can't because there's no expression for that. Something like $<TARGET_OBJ_DIR:tgt> would be very welcome.

> > The important missing dependency is the one between
> > creating/regstering the typelib (we'll call this target COMServer) and
> > the #import that will read it in a source file in another target
> > (we'll call it COMClient).  I have a call add_custom_command(TARGET
> > COMServer POST_BUILD COMMAND regsvr32 $<TARGET_FILE:
> COMServer>),
> > which will create the registry keys under HKEY_CLASSES_ROOT. This
> > needs to happen before the source file in COMClient can preprocess the
> > #import successfully. Prior to CMake 3.9, I could inform CMake of this
> > by just using add_dependencies(COMClient COMServer) to tell CMake that
> > it couldn't build (any of) Client until Server had been built (and
> > thus its POST_BUILD had also run to register it). But in 3.9,
> > add_dependencies has changed in meaning; although the documentation
> > still says "to ensure that they build before <target> does", in
> > practice this now only means "to ensure that they build before
> > <target> *links*"; these edges do not apply to object compilation or
> > add_custom_command rules.
> >
> > add_custom_command is no problem; it already had a DEPENDS argument
> > that allows target-level dependencies, and arguably such dependencies
> > needed to be stated there anyway since an add_custom_command output
> > can get reused by multiple targets in the same subdir. But object
> > compilation is a problem because there's nowhere to add them
> > per-source, and add_dependencies doesn't work anymore to add them
> > per-target.
> 
> It sounds like the logic may need fixing then. Do you have an example case
> where add_dependencies doesn't work anymore in Ninja?

CMakeLists.txt:
cmake_minimum_required(VERSION 3.7)

add_library(A SHARED a.c)

add_custom_command(TARGET A POST_BUILD
	COMMENT "hello A"
	COMMAND ${CMAKE_COMMAND} -E sleep 3
	COMMAND ${CMAKE_COMMAND} -E echo "hello A")

add_custom_command(OUTPUT b.custom
	COMMENT "hello B"
	COMMAND ${CMAKE_COMMAND} -E touch b.custom)

add_executable(B b.c b.custom)
add_dependencies(B A)

a.c:
void foo() {}

b.c:
int main() { return 0; }

In CMake 3.7:

build cmake_order_depends_target_B: phony || A.dll b.custom
build b.custom: CUSTOM_COMMAND || A.dll
build CMakeFiles\B.dir\b.c.obj: C_COMPILER__B C$:\Users\re41236\Desktop\test\cmake\b.c || cmake_order_depends_target_B
build B.exe: C_EXECUTABLE_LINKER__B CMakeFiles\B.dir\b.c.obj || A.dll

In CMake 3.9:
build cmake_object_order_depends_target_B: phony || b.custom cmake_object_order_depends_target_A
build CMakeFiles\B.dir\b.c.obj: C_COMPILER__B C$:\Users\re41236\Desktop\test\cmake\b.c || cmake_object_order_depends_target_B
build b.custom: CUSTOM_COMMAND || A.dll
build B.exe: C_EXECUTABLE_LINKER__B CMakeFiles\B.dir\b.c.obj || A.dll

So in 3.7, the add_dependencies(B A) put A.dll as an order-only dependency of all the rules that were part of B; nothing in B builds until everything in A does.
In 3.9 only the custom commands and link rules get it, the object rules don't. And there doesn't seem to be a way to explicitly get an order-only target dependency into the object rules in the (rare) cases where it actually was needed, like #import. So not only is it a breaking change, it’s not readily fixed.

Admittedly right now this is pretty much limited to weird compiler (mis-)features like MSVC's #import that reference input besides the source/headers. Although it might become more common if the C++ modules TS ever catches on (you'll need the .ifc file generated by compiling the dependency before compiling objects that use it)

> > > When adding
> > > this custom command, you may use the `BYPRODUCTS` argument
> > > (introduced in 3.2.0) to let CMake know what's going on here. It
> > > only affects Ninja, but the other generators do target-level
> > > dependencies anyways. That output can then be depended on via
> > > `OBJECT_DEPENDS` and the dependency should link up properly.
> >
> > There is not an explicit file output, though I could do the usual
> > workaround of a stamp/witness file listed in BYPRODUCTS to the
> > add_custom_command(TARGET ... POST_BUILD ...). But I don't think that
> > will work with most generators, since CMake doesn't generally allow
> > file-level depends to set the order in which targets are built. I
> > suppose it might work out in practice for ninja since that writes a
> > monolithic set of rules, but conditional code where I have to peek at
> > CMAKE_GENERATOR and use BYPRODUCTS/OBJECT_DEPENDS for ninja
> and
> > add_dependencies for other generators seems like the sort of thing
> > this list would tell me not to do :-)
> 
> Well, other generators are generally target-ordered anyways. Ninja is the
> oddball here (which is why it's the only one to get the feature). I don't know
> the effect it'd have in other generators, but I feel like I'd be surprised if it
> *broke* them since excess dependencies (usually) only result in either
> slower builds or circular dependency loops and Ninja complains loudly about
> the latter. And since BYPRODUCTS only affects Ninja, if BYPRODUCTS is used,
> other generators shouldn't care anyways.

True, I suppose I don’t need to peek at CMAKE_GENERATOR, I could just do it both ways all the time. As long as nobody comes up with a generator that can separate compile vs link dependencies, but still can't handle cross-subdir file dependencies.

> > And even for ninja I think I'd have to be making undocumented
> > assumptions about the binary dir layout to refer to my witness file
> > that was generated in a different subdir's CMakelists.txt.
> 
> There's nothing stopping the witness files couldn't all be under a single
> directory (such as ${CMAKE_BINARY_DIR}/CMakeFiles/tlb).

Ok, good point. They don't have to be alongside the target's other artifacts.

> > > If it instead gets registered somewhere in the aether (as far as
> > > CMake is concerned), adding support for generator expressions to
> > > `OBJECT_DEPENDS` so that `$<TARGET_FILE:tgt>` may be used there
> would be the next solution.
> >
> > Yes, the dependency in question for #import is on information
> > "somewhere in the aether" (or rather the Win32 registry).
> >
> > Supporting $<TARGET_FILE> does in OBJECT_DEPENDS would be a great
> > solution for my first use case of a embedding that file in a resource.
> > But I don't think that helps with #import, since I don't actually want
> > to read the $<TARGET_FILE>, I just want the post-build that registers
> > it to have run.
> 
> POST_BUILD rules are attached to the target, so depending on the target also
> guarantees that the POST_BUILD command(s) have run as well.

Right, which is how I did it before. What I was saying (not very clearly) is that I only want the target to have been registered (so it's POST_BUILD has run), I don't care if it's newer. I only care if the .tlb file is newer, and I can do that with OBJECT_DEPENDS already. So the target part ought to be an order-only dependency (as it was in 3.8). Depending on $<TARGET_FILE> is sufficient, but actually too strong.

> > Also, in the cases of .tlb files that are *not* embedded in DLL
> > resources, the target in question is going to be an add_custom_target
> > from another subdirectory; the .tlb file is built by an
> > add_custom_command(OUTPUT...) but this rule gets emitted in an
> > add_custom_target that depends on this file to build it and then
> > registers it. If each subdir had the add_custom_command instead of
> > using an intermediate target, multiple targets would each end up with
> > their own copy of the rule to build the .tlb file, leading to race
> > conditions where they all try to build it at once and get file-in-use
> > errors (they can't just build individual copies, because it has to end
> > up with a unique key referencing the .tlb path in the win32 regist>
> > ry).
> 
> Yeah, there should be just one .tlb rule writer. Usually I handle that by
> collecting information in global properties and writing a rule at the end to
> handle all of them.

Yeah. It would be really cool to have an end-of-input callback so include files that define their own commands could get a callback hook to write such "rule at the end" functions :-).

> > You're not currently allowed to use $<TARGET_FILE:x> on UTILITY
> > targets even if the LOCATION property has been set (it's blocked in
> > TargetFilesystemArtifact::Evaluate with "Target <x> is not an
> > executable or library"). Maybe that could be changed as well (which
> > would be nice), but it seems like if one is adding support for
> > $<TARGET_FILE> generator expressions in OBJECT_DEPENDS (which
> implies
> > supporting the generator context and context->DependTargets), it seems
> > like you as well go the rest of the way and just treat them completely
> > the same as the DEPENDS argument to add_custom_command, allowing
> both
> > file and target dependencies to be listed in the first place.
> 
> That sounds like a likely path to follow when supporting genexes in
> OBJECT_DEPENDS.

This would definitely solve my complaint; I'd be able to put target depends on an object rule in the (rare) case that it really needs them.

> > > Making `POST_BUILD` write out a stamp file would also work and then
> > > using `OBJECT_DEPENDS` on that would also work.
> >
> > No, as above I don't think that would be legal across subdirs, at
> > least in the context of a CMake file that's supposed to work with
> > various generators. Feel free to correct me if I'm wrong about that...
> 
> Experiments would be more useful. add_custom_* have some of the most
> complicated interaction semantics in CMake. I can't keep all of them straight
> all the time (usually I rediscover them when necessary; I should probably
> write up some docs next time I need to do so).
> 
> --Ben


More information about the CMake mailing list