gradle / gradle-native

The home of Gradle's support for natively compiled languages
https://blog.gradle.org/introducing-the-new-cpp-plugins
Apache License 2.0
91 stars 8 forks source link

C++ compilation uses precompiled headers to compile source files more quickly #23

Open adammurdoch opened 7 years ago

adammurdoch commented 7 years ago

Allow precompiled headers to be used when compiling C++, to make the build faster.

fletcher-sumglobal commented 5 years ago

Need sample and possible code modifications to support precompiled headers using current model. A sample has been done with the deprecated software model included in the all distro. However, in order to port functionality to current model, I suspect the class PreCompiledHeader (an internal class reading in DSL on behalf of CppPreCompiledHeaderCompile) may need to the provider api ported in to allow support for wiring in the inputs: pchObjects, prefixHeaderFile, and includeString, similar to how we had to port the provider API to wire compilerArgs in for the WindowsResourceCompile task. Is this a fair assessment on a path to move forward on this issue?

fletcher-sumglobal commented 5 years ago

@big-guy @lacasseio

Description

The PCH sample below originated from the 'all' distro. The goal to is to convert it from the software model to the current model. build-deprecated.gradle is based on the software model, and build.gradle is my current attempt at making the build based on the current model. There are a few gaps I need assistance with on it.

Context

In the build-deprecated.gradle, hidden tasks in the s/w model create a prefix-header.h. I replicate the same file using a custom task PrefixHeaderGen below. Not sure why this file is necessary, but it is heavily baked in the s/w model.

Gaps

The following gaps are based on assumptions that may be false, but so far I've been on my own here with no documentation other than the native-samples and reading the code, be here we go: 1) CppPreCompiledHeaderCompile depends on PrefixHeaderGen. Is the prefix-header.h necessary? It was modeled in the s/w model version, so I ported it over to the current model. If it is, then I can produce it given the build.gradle below using the prefixGen custom task below using PCHUtils. It is currently required for CppPreCompiledHeaderCompile

2) When firing CppPreCompiledHeaderCompile, (the producer of the PCH file) I am assuming it be created from within the 'cpp-library' using STATIC & SHARED linkage. Wiring the includes/source are for this task appear correct, but do not work, and do not resolve the include headers, and as a result, the PCH file is never produced.

3) the application block {} is the consumer side of the PCH and as such requires additional inputs be set for the task, namely pchObjects, prefixHeaderFile, & includeString. However, this additional DSL requires the provider API which I have added to my custom local version Again, not sure if this assumption is correct. But since in 2), I'm still unable to produce the PCH, it cannot be consumed in 3), I haven't been able to get to that point yet to try my provider api changes out yet.

4) All of these items above I am trying to understand their role in the build lifecycle for the application {} and library {} DSL, but with no documentation and armed with my experience with the WindowsResourceCompile task, I may be off base in more than one area. It would be nice if there were some cleaner way to express native builds, because I find it pretty confusing nesting the CppPreCompiledHeaderCompile or task creating within an application or library block when I would much prefer the task be cleanly defined on its own block. Maybe it should be in its own plugin. Not sure.. It would be nice to have an interactive screen sharing session from developer to developer to discuss the architecture, and any associated diagrams, etc.

At any rate, here is my sample, and any pointers/suggestions/corrections/comments/criticisms are welcome.

Project Structure (below)

Also here -> git clone https://github.com/fletcher-sumglobal/pre-compiled-headers.git

.
├── build.gradle
├── build-deprecated.gradle
├── README.md
├── settings.gradle
└── src
    ├── hello
    │   ├── cpp
    │   │   └── hello.cpp
    │   └── headers
    │       ├── hello.h
    │       └── pch.h
    └── main
        └── cpp
            └── main.cpp

6 directories, 8 files

main.cpp

#include "hello.h"

int main () {
    Greeter greeter;
    greeter.hello();
    return 0;
}

pch.h

#ifndef PCH_H
#define PCH_H
#include <iostream>
#include "hello.h"
#endif

hello.h

#ifndef HELLO_H
#define HELLO_H
#if defined(_WIN32) && defined(DLL_EXPORT)
#define LIB_FUNC __declspec(dllexport)
#else
#define LIB_FUNC
#endif

class Greeter {
    public:
    void LIB_FUNC hello();
};
#endif

hello.cpp

#include "pch.h"

void LIB_FUNC Greeter::hello () {
    std::cout << "Hello world!" << std::endl;
}

settings.gradle

rootProject.name = 'pre-compiled-headers'

To assemble library, all tasks are explicitly called. There are problems with this build script TBD.

E:\pre-compiled-headers>e:\local-build-gradle-5.0\bin\gradle clean prefixGen preCompileHeaderMainDebugShared preCompileHeaderMainDebugStatic preCompileHeaderMainReleaseShared preCompileHeaderMainReleaseStatic assemble --info

build.gradle PCH Sample using cpp-library

import org.gradle.nativeplatform.toolchain.internal.PCHUtils
import com.google.common.collect.Lists

buildscript {
    dependencies { classpath 'com.google.guava:guava:23.0' }
}

plugins { id 'cpp-library' }

task prefixGen(type: PrefixHeaderGen) {
    header = 'pch.h'
    prefixHeaderFile = project.file('build/tmp/hello/cpp/prefixHeaders/prefix-headers.h')
}

def pchTask = null

library {
    linkage =  [
        Linkage.STATIC,
        Linkage.SHARED
    ]
    source.from project.file('src/hello/cpp')
    source.from project.file('src/main/cpp')
    publicHeaders.from project.file('src/hello/headers')
//    pchObjects.from pchTask.objectFileDir
//    headerFile.from prefixGen.prefixHeaderFile
//    includeString ='pch.h'

    binaries.whenElementFinalized { binary ->

        if (binary.targetPlatform.operatingSystemFamily.isWindows()) {

            def pchTaskName = "preCompileHeader" + binary.name.capitalize()
            pchTask = project.tasks.register(pchTaskName, CppPreCompiledHeaderCompile) {
                targetPlatform = binary.targetPlatform
                toolChain = binary.toolChain

                objectFileDir = new File(project.buildDir, "pch/${binary.name}PCH")
                macros.put('DLL_EXPORT', null)
                includes.from project.file ('src/hello/headers')
                includes.from project.file ('src/hello/cpp')
                includes.from project.file ('C:/Program Files (x86)/Microsoft Visual Studio/2017/Professional/VC/Tools/MSVC/14.13.26128/include')
                includes.from project.file ('C:/Program Files (x86)/Windows Kits/10/Include/10.0.16299.0/um')
                includes.from project.file ('C:/Program Files (x86)/Windows Kits/10/Include/10.0.16299.0/shared')
                includes.from project.file ('C:/Program Files (x86)/Windows Kits/10/Include/10.0.16299.0/ucrt')
                source.from prefixGen.prefixHeaderFile
                dependsOn prefixGen
            }

            //FileTree pchOutput = pchTask.get().getOutputs().getFiles().getAsFileTree().matching(new PatternSet().include("**/*.pch"));
            //binary.linkTask.get().source(pchOutput)
        }
    }
}

/*
 * It would be nice to declare the CppPreCompiledHeaderCompile task this way
 * but there is no visibility to targetPlatform & toolchain
 */
task pchGen(type: CppPreCompiledHeaderCompile) {
    source.from project.file('src/hello/headers')
    includes.from prefixGen.prefixHeaderFile
    objectFileDir = new File(project.buildDir, "pchOut")
    dependsOn prefixGen
}

//tasks.withType(CppCompile) {
//    pchObjects.from pchTask.objectFileDir
//    headerFile.from prefixGen.prefixHeaderFile
//    includeString = 'pch.h'
//}
//

tasks.withType(LinkExecutable) {

    linkerArgs.addAll toolChain.map { NativeToolChain toolChain ->
        List<String> linkerSpecificArgs = []
        if (toolChain instanceof VisualCpp) {
            linkerSpecificArgs << 'user32.lib'
        }
        return linkerSpecificArgs
    }
}

class PrefixHeaderGen extends DefaultTask {

    @OutputFile
    final RegularFileProperty prefixHeaderFile = project.objects.fileProperty()

    @Input
    final Property<String> header = project.objects.property(String)

    @TaskAction
    void generatePrefixHeaderFile() {
        PCHUtils.generatePrefixHeaderFile(Lists.newArrayList(header.get()), prefixHeaderFile.getAsFile().get());
    }
}

To assemble the PCH, THIS MAY ACTUALLY BE WORKING!! Need to verify. Some ugliness in the DSL needs to be resolved. Questions TBD.

E:\pre-compiled-headers>e:\local-build-gradle-5.0\bin\gradle clean assemble -b build2.gradle --info

build2.gradle Alternate PCH sample using cpp-application

import org.gradle.nativeplatform.toolchain.internal.PCHUtils
import com.google.common.collect.Lists

buildscript {
    dependencies { classpath 'com.google.guava:guava:23.0' }
}

plugins { id 'cpp-application' }

task prefixGen(type: PrefixHeaderGen) {
    header = 'pch.h'
    prefixHeaderFile = project.file('build/tmp/hello/cpp/prefixHeaders/prefix-headers.h')
}

def pchTask = null

application {
    source.from project.file('src/hello/cpp')
    source.from project.file('src/main/cpp')
    privateHeaders.from project.file('src/hello/headers')
//    pchObjects.from pchTask.objectFileDir
//    headerFile.from prefixGen.prefixHeaderFile
//    includeString ='pch.h'

    binaries.whenElementFinalized { binary ->

        if (binary.targetPlatform.operatingSystemFamily.isWindows()) {

            def pchTaskName = "preCompileHeader" + binary.name.capitalize()
            pchTask = project.tasks.register(pchTaskName, CppPreCompiledHeaderCompile) {
                targetPlatform = binary.targetPlatform
                toolChain = binary.toolChain

                objectFileDir = new File(project.buildDir, "pch/${binary.name}PCH")
                macros.put('DLL_EXPORT', null)
                includes.from project.file ('src/hello/headers')
                includes.from project.file ('src/hello/cpp')
                includes.from project.file ('C:/Program Files (x86)/Microsoft Visual Studio/2017/Professional/VC/Tools/MSVC/14.13.26128/include')
                includes.from project.file ('C:/Program Files (x86)/Windows Kits/10/Include/10.0.16299.0/um')
                includes.from project.file ('C:/Program Files (x86)/Windows Kits/10/Include/10.0.16299.0/shared')
                includes.from project.file ('C:/Program Files (x86)/Windows Kits/10/Include/10.0.16299.0/ucrt')
                source.from prefixGen.prefixHeaderFile
                dependsOn prefixGen
            }

            FileTree pchOutput = pchTask.get().getOutputs().getFiles().getAsFileTree().matching(new PatternSet().include("**/*.obj"));
            binary.linkTask.get().source(pchOutput)
        }
    }
}

/*
 * It would be nice to declare the CppPreCompiledHeaderCompile task this way
 * but there is no visibility to targetPlatform & toolchain
 */
task pchGen(type: CppPreCompiledHeaderCompile) {
    source.from project.file('src/hello/headers')
    includes.from prefixGen.prefixHeaderFile
    objectFileDir = new File(project.buildDir, "pchOut")
    dependsOn prefixGen
}

//tasks.withType(CppCompile) {
//    pchObjects.from pchTask.objectFileDir
//    headerFile.from prefixGen.prefixHeaderFile
//    includeString = 'pch.h'
//}

tasks.withType(LinkExecutable) {

    linkerArgs.addAll toolChain.map { NativeToolChain toolChain ->
        List<String> linkerSpecificArgs = []
        if (toolChain instanceof VisualCpp) {
            linkerSpecificArgs << 'user32.lib'
        }
        return linkerSpecificArgs
    }
}

class PrefixHeaderGen extends DefaultTask {

    @OutputFile
    final RegularFileProperty prefixHeaderFile = project.objects.fileProperty()

    @Input
    final Property<String> header = project.objects.property(String)

    @TaskAction
    void generatePrefixHeaderFile() {
        PCHUtils.generatePrefixHeaderFile(Lists.newArrayList(header.get()), prefixHeaderFile.getAsFile().get());
    }
}

build-deprecated.gradle

This build.gradle is based on the deprecated software model and works to produce/consume the PCH, just using here as a reference so that the build lifecyle is replicated in the "current model" build.gradle shown above.

// tag::apply-plugin[]
apply plugin: 'cpp'
// end::apply-plugin[]

// tag::libraries[]
model {
    components {
        hello(NativeLibrarySpec) {
            sources {
                cpp {
                    preCompiledHeader "pch.h"
                }
            }
        }
    }
}
// end::libraries[]

// tag::executables[]
model {
    components {
        main(NativeExecutableSpec) {
// tag::source-library[]
            sources {
                cpp {
                    lib library: "hello"
                }
            }
// end::source-library[]
        }
    }
}

// end::executables[]

// For any shared library binaries built with Visual C++, define the DLL_EXPORT macro
model {
    binaries {
        withType(SharedLibraryBinarySpec) {
            if (toolChain in VisualCpp) {
                cppCompiler.define "DLL_EXPORT"
            }
        }
    }
}