[cpp-plugin] Header file change didn't cause recompile

This is for 1.7rc1

I have a directory structure like this:

gradle_test
    main.cpp
    first.h
    build.gradle

Contents of main.cpp

#include <iostream>
#include "first.h"
  int main ()
{
    std::cout << hello << std::endl;
    return 0;
}

Contents of first.h

const char hello[] = "Hello World, Michael";

Contents of build.gradle

apply plugin: 'cpp'
  cpp {
    sourceSets {
        main {
            source.srcDirs = ['./']
            source.include 'main.cpp'
        }
    }
}
  executables {
    main {
        source cpp.sourceSets.main
    }
}

I assumed that the following claim from the release notes would mean that changes to a header dependency would cause the compileMainExecutable task to execute:

Detects changes to compiler and linker settings, in addition to changes in source and header files.

After running gradle mainExecutable, it builds a working executable. Changing the contents of first.h and running the command again results in gradle saying that compileMainExecutable is UP-TO-DATE. Why?

I’m actually surprised that this is working for you at all: you’re not telling Gradle where to find your header file, so it’s not being included in the inputs for compile. Presumably, GCC is smart enough to look in the same directory as the source file to locate your header, but Gradle knows nothing about it.

Try something like this:

cpp {
     sourceSets {
         main {
            source {
                srcDirs project.rootDir
                include '*.cpp'
            }
            exportedHeaders {
                srcDirs project.rootDir
                include '*.h'
            }
        }
     }
}

But header files are not (usually) inputs to a compiler invocation.

That behavior is not specific to GCC. The C++ standard says that it is implementation-defined, but practically any compiler tool worth it’s salt will always look in the same directory as the translation unit being compiled (cpp file).

I’d argue that we shouldn’t have to tell Gradle about it, at least not for the purposes of dependency calculation. I haven’t tried your suggestion yet, but I’d figure that it would cause a recompilation if any .h file in the directory were to change, without regard to whether or not it actually leads to a change in a translation unit. That is not C++ incremental compilation.

The alternative would be to explicitly list every header dependency that an individual .cpp file might need, and that would be exhausting. What Gradle should be doing is parsing the .cpp file for the list of header dependencies, and then make the determination if a compilation should happen (per translation unit) based on that information. I know that it a pain to implement correctly, but anything other than that is simply not incremental compilation for C++.

What is reasonable to declare in the Gradle script are the locations that should be passed to the compiler to instruct it where to look to resolve #include statements.

Hi Michael,

The current C++ support does not do incremental compilation. Did you read somewhere that it does?

This is for 1.7rc1

http://www.gradle.org/docs/release-candidate/release-notes#major-improvements-to-c++-project-support

Improved incremental build

The incremental build support offered by the C++ plugins has been improved in this release, making incremental build very accurate:

Detects changes to compiler and linker settings, in addition to changes in source and header files. No longer recompiles source files when linker settings change. Detects changes to dependencies of a binary and recompiles or relinks as appropriate. Detects changes to the toolchain used to build a binary and recompiles and relinks. Removes stale object files when source files are removed or renamed. Removes stale output files when compiler and linker settings change. For example, removes stale debug files when debug is disabled.

Incremental build is not the same thing as incremental compilation in Gradle terminology.

Incremental build refers to the ability to determine which tasks in the Gradle build need to be executed. So in terms of C++, this would be executing (or not) the a particular compile task.

Incremental compilation is something else, and is potentially complimentary to incremental build. The C++ support does not support incremental compilation at this time.

Detects changes to compiler and linker settings, in addition to changes in source and header files.

That’s not a very useful form of incremental behavior for C++, and in fact, it would be undesirable. I would not want to rebuild a project just because a header file in one of my include search paths has changed.

You’ll also get a lot of C++ folks getting the same impression that I had if you use the term “incremental build”. It has been a well-established term for a long time. Maybe you should just call this “Improved Dependency Management” in the release notes instead?

I’m confused. Your first concern was that Gradle didn’t recompile when a header file changed: now you’re concerned that it will. The way it works is that Gradle will recompile the sources when an incoming header file has changed. So if your C++ source includes a header file, and that file has changed, then the source file will be recompiled. Is this not what should happen?

It’s understood that the current implementation is not efficient, which is why pretty soon we’ll implement incremental compile, which means that we’ll only recompile the sources that actually need recompiling. So if ‘foo.cpp’ includes ‘bar.h’ and ‘bar.h’ changes, we’ll recompile ‘foo.cpp’.

The terminology ‘incremental-build’ has for a long time been used in gradle to mean ‘only re-run the build steps that are required’. I’m sure you’re correct in thinking that we need to get a bit more with the lingo of the C++ community, but the meaning of this term in gradle is pretty well established: “build” is much more than “compile”.

There are two concerns. 1) Not rebuilding when an included header file has changed. 2) Rebuilding when a header file that is not included has changed. The implementation in 1.7rc1 allows you to do either of those things, but both are wrong. Perhaps the second concern is just suboptimal instead of wrong.

I’m assuming that one could list the include dependencies explicitly instead of using fileset wildcards, but that would be a disaster for any real C++ project.

What you’ve described in the second paragraph is the correct implementation.

I totally understand that it can be difficult to meld the domain language from two domains and that there will always be some amount of confusion. Would it be useful to have a glossary to clarify where there might be collisions between terms in the two domains? Or rather, would the usefulness of such a thing outweigh the cost of maintaining it?

  1. Not rebuilding when an included header file has changed.

This only happens (I think) if you don’t tell Gradle what the included header files are, via the ‘exportedHeaders’ block, and instead rely on GCC to pick up the headers from the source directory. Gradle relies on a strong model of what sources/headers are included in your project, and this model does not yet automatically include these header files.

We’ll probably help in 2 ways: i) make it easy to conventionally include all “*.h” files in any source directory and ii) stop GCC from automatically using header files in the same directory if they aren’t in the Gradle model (since it makes your compilation succeed without Gradle being aware of the required header files).

You need to tell Gradle about your project sources and header files: that’s how Gradle works. The way you currently tell Gradle about these things is not always convenient: that’s something we’ll work on. Priority is to first make things possible, later to make them easier.

  1. Rebuilding when a header file that is not included has changed

For now, if you tell us about a header file for a compilation, we assume it is included . Later we’ll be smarter about this, perhaps by parsing the source files and determining the files that are actually included. If you need this feature now, you could implement this parsing yourself and generate the correct “exportedHeaders” declaration based on the result.

1 Like