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
92 stars 8 forks source link

Cannot use cpp-application and cpp-library together #987

Closed zosrothko closed 5 years ago

zosrothko commented 5 years ago

When building a multi modules project, that contains both cpp library and cpp-application, one needs to specify global options/parameters/tasks for some or all submodules. Below is the organisation of a the Poco Foundation module

     Poco
      | Foundation                             
             | src                                    // is a cpp-library
             | include
             | testsuite                           
                     | src                             // is a unitTest testing TestApp & TestLibrary
                           | TestApp.cpp        // is a cpp-application
                           | TestLibrary.cpp    // is a cpp-library
                     | include
             | samples
                    | sample1                    
                         | src                        // is a cpp-application   
                    | sample2                       
                         | src                        // is a cpp-application   

Expected Behavior

A working build as with the Rule based plugins

Current Behavior

Build file 'Z:\git\gradle-bug\build.gradle' line: 2

Context

Cannot use the new cpp-plugins

Steps to Reproduce (for bugs)

build the following build.gradle script

        apply plugin:  'cpp-library'
        apply plugin:  'cpp-application'

Your Environment


Gradle 5.1.1

Build time: 2019-01-10 23:05:02 UTC Revision: 3c9abb645fb83932c44e8610642393ad62116807

Kotlin DSL: 1.1.1 Kotlin: 1.3.11 Groovy: 2.5.4 Ant: Apache Ant(TM) version 1.9.13 compiled on July 10 2018 JVM: 1.8.0_181 (Oracle Corporation 25.181-b13) OS: Windows 10 10.0 amd64

lacasseio commented 5 years ago

At the moment, we don't allow mixing cpp-library and cpp-application into the same project to create multi-components project like in the software model. The reason being it goes against the pattern expressed everywhere else in Gradle. Multiple components in a project will be expressed in a more focused way like JNI project or multiple test suite per project.

For your specific scenario, you could model the structure of the project as a setting plugin that simplifies the project creation. Given the pattern used throughout the Poco organization is pretty clear, you should be able to model it so that you can use a custom DSL that does the right configuration to keep the organization of the Gradle logic concise. For example, you could have the following settings.gradle. Keep in mind the code could be moved to buildSrc and apply the plugin.

import javax.inject.Inject

class PocoLibraryProject {
    private final String path
    final List<String> samples = []

    @Inject
    PocoLibraryProject(String path) {
        this.path = path
    }

   void sample(String name) {
       samples.add(name)
   }
}

class PocoLayoutExtension {
    private final Settings settings
    private final ObjectFactory objectFactory

    @Inject
    PocoLayoutExtension(ObjectFactory objectFactory, Settings settings) {
        this.objectFactory = objectFactory
        this.settings = settings
    }

    void library(String path, Action<? extends PocoLibraryProject> action = null) {
        settings.include(path)

        if (action != null) {
            def spec = objectFactory.newInstance(PocoLibraryProject, path)
            action.execute(spec)
            for (String it : spec.samples) {
                String projectPath = "${path}:${it}"
                settings.include(projectPath)
                settings.project(":${projectPath}").projectDir = new File(settings.rootDir, path.replace(":", "/") + "/samples/${it}")
                settings.gradle.rootProject { proj ->
                    proj.project(":${projectPath}") {
                        // TODO: Configure convention for the sample such as:
                        //   - applying `cpp-application`
                        //   - adding a dependency to the library
                    }
                }
            }
            // TODO: Handle the test case in a similar way
        }
    }
}

class PocoProjectLayoutPlugin implements Plugin<Settings> {
    private final ObjectFactory objectFactory

    @Inject
    PocoProjectLayoutPlugin(ObjectFactory objectFactory) {
        this.objectFactory = objectFactory
    }

    void apply(Settings settings) {
        def subprojects = objectFactory.newInstance(PocoLayoutExtension, settings)
        settings.extensions.add("subprojects", subprojects)
    }
}

apply plugin: PocoProjectLayoutPlugin

subprojects {
    library("Foundation") {
        sample("ActiveMethod")
        sample("Activity")
        //...
    }

    library("SQL") {
        sample("Binding")
        sample("RecordSet")
        //...
    }

    library("SQL:MySQL")
}
lacasseio commented 5 years ago

Let favour https://github.com/gradle/gradle-native/issues/988 for now and reopen when we get a clearer need for this scenario.

lacasseio commented 5 years ago

Note that you can still configure all the sample and testsuite from a single build.gradle file by doing something like:

project("ActiveMethod") {
    // ...
}
project("Activity") {
    // ...
}

You could also keep the subproject samples under each library by avoiding the redirection of the projectDir inside samples so that a sample would have the following path :Foundation:samples:ActiveMethod.

lacasseio commented 5 years ago

My bad refer to https://github.com/gradle/gradle-native/issues/216.

zosrothko commented 5 years ago

@lacasseio From my perspective, the current limitation that forbids the mix of cpp-library/cpp-application plugins is quite ennoying. Ideally I would like to something like the root project script as below

subprojects {
    apply plugin:  'cpp-library'
    apply plugin:  'cpp-application'
    apply plugin:  'cpp-unit-test'

    library {
        def toolChain = toolChain.getOrNull()
        task.WhenType(CppCompile) {
            if (toolChain instanceof VisualCpp) {
                  compilerArgs = ["/EHsc" /* additional global compiler options go there */]
            }
        }
    }
    application {
        def toolChain = toolChain.getOrNull()
        task.WhenType(CppCompile) {
            if (toolChain instanceof VisualCpp) {
                  compilerArgs = ["/EHsc" /* additional global compiler options go there */]
            }
        }
    }
    unitTest {
        def toolChain = toolChain.getOrNull()
        task.WhenType(CppCompile) {
            if (toolChain instanceof VisualCpp) {
                  compilerArgs = ["/EHsc" /* additional global compiler options go there */]
            }
        }
    }
}

Poco has 19 submodules, each one beeing a cpp-library with a testsuite as a cpp-application. Each submodule has a link dimension [static, semistatic, shared], a mode dimension [debug, release] and an architecture dimension [x86, adm64]. Moreover 15 of all submodules have also a set of samples which are cpp-application. There are about 64 samples to build refering the matrix of the submodules above.

The number of the if/else choice for setting the proper compiler options and linker options for the matrix above is 3x2x2 = 12. If there is no way to have a global setting inherited by each cpp-application/cpp-library/unitTest as does the Software Model, one would have to make (19 x 12) * 2 + 64 x 12 = 996 editions to 102 gradle scripts. That's not a workable solution.

lacasseio commented 5 years ago

The build scripts in Gradle are meant to configure the model for a particular project. How you model the project is a different issue. In the case of Poco, the pattern is really clear. It would be recommended to model the pattern which would allow each library to be treated as it's own meta-project which includes all the subproject. To this end, the meta-project would push the configuration on 1 or several sub-project depending on what is the right thing to do.

Given those meta-projects are highly opinionated for each organization, we are trying to focus on ways to simplify the modelling as opposed to make the low-level API more flexible (aka allowing declaring multiple components). On itself, the cpp-application and cpp-library are kind of like meta-component. They configure multiple components which are invisible to the user. We may allow users to create meta-component inside a project but the functional plugin app-application and cpp-library are not meant to be mixed together within the same project. In Java, more "components" are created via the sourceSets DSL. Similar patterns may be implemented in native but not to the extent of the software model.

The solution at this stage is to create those meta-projects what would configure multiple projects at the same time making it possible to only configure a handful of build scripts. Having to do 996 editions for a change of flags or anything with that regards is a problem with the modelling of the current pattern.

From my perspective, each Poco library (Foundation, Crypto, Net, JSON, etc) should only have a single build script applying a plugin like org.pocoproject.poco-library that configure each subproject. You can have a DSL the way that makes the most sense for your project like:

apply plugin: 'org.pocoproject.poco-library'

library {
    // configuration for the library
}

testSuites.all {
    compilerArgs.set([...])
}

samples.all {
    // something else here
}

The plugin can configure lifecycle tasks that hook into the subprojects so you can do something like: ./gradlew :Foundation:check or ./gradlew :Foundation:prepareSampleZip

Gradle should allow you to easily build those modelling.