Native: how to reference the buildTypes value in a component

native

(ZOSROTHKO) #1

Hi

I am using the baseName setting in the root script which is working fine

        components {
            withType(NativeLibrarySpec) {
                    baseName "Poco${project.name}"
            }
        }

but one needs to differentiate the debug baseName from the release baseName as

        components {
            withType(NativeLibrarySpec) {
                if (buildTypes == buildTypes.debug) {
                     baseName "Poco${project.name}d"
                }
                if (buildTypes == buildTypes.release) {
                     baseName "Poco${project.name}"
                }
            }
        }

but this does not work since gradle fails with

A problem occurred configuring project ':CppUnit'.
> Exception thrown while executing model rule: model.components > withType()
   > Could not get unknown property 'debug' for BuildType container.

Using buildType instead of buildTypes as in

        components {
            withType(NativeLibrarySpec) {
                if (buildType == buildTypes.debug) {
                    baseName "Poco${project.name}d"
                }
                if (buildType == buildTypes.release) {
                    baseName "Poco${project.name}"
                }
            }
        }

fails with

* What went wrong:
A problem occurred configuring project ':CppUnit'.
> Exception thrown while executing model rule: model.components > withType()
   > No such property: buildType for class: org.gradle.nativeplatform.NativeLibrarySpec

how can I express this conditioned baseName?


(Daniel Lacasse) #2

Hi zosrothko,

I see where you are coming from and the short answer is buildType is available at the binary level. Basically, a component is a grouping of binary. The binaries are the outputs of a component.

The longer answer have already been explored in great detail here. The solution is quite lengthy and an issue have been opened on Github to address this use case.

Don’t hesitate to ask more questions,

Daniel


(ZOSROTHKO) #3

Thanks for you detailled explanations… I have another issue with gradle for building Poco. There is a need for a user’s defined output directory for NativeBinarySpec. Poco is using the following convention

the Poco’s project organization should be
Poco
--------bin
--------imp
--------lib\

where bin is the place to put all SharedLibraryBinarySpec and NativeExecutableSpec
where imp is the place to put all linking libs when using a dll (Windows concept is an import lib)
where lib is the place to put all StaticLibraryBinarySpec

Poco is using also the convention to suffix with a ‘d’ all components made with the debug buildtype, so that there is only one directory to put in PATH. A debug executable will use the proper debug dll while a release one will link with the released dll. that’s quite important because Poco is providing a portable and uniform API way to invoke dynamic link library both on Windows and Linux/Unix. The code is something like

    std::string path = "TestLibrary";
    path.append(SharedLibrary::suffix());
    SharedLibrary sl;

and the method suffix will return the empty string in release while it return “d” in debug mode.

So I have no idea how to specify the output directory of binaries in gradle which is a mandatory requirment for building Poco.

May be using a customized BinaryNamingScheme?


(Daniel Lacasse) #4

Hi zosrothko,

Thanks for the great detail in the question. The BinaryNamingScheme is an internal API and I haven’t seen any discussion about making it public. The best step forward is really to use the answer I linked in my previous comment. The ground work is exposed there. The little missing detail is you should use the code inside a convention for your project. As the name say it’s a global configuration applied to all the project that need to follow a specific convention. The following code could be use to create a convention for all cpp project:

File appendDebugSuffix(File binaryFile) {
  int extensionSeparatorIndex = binaryFile.path.lastIndexOf('.')
  return new File(binaryFile.path.substring(0, extensionSeparatorIndex) + "_d" + binaryFile.path.substring(extensionSeparatorIndex))
}

allprojects {
  plugins.withId("cpp") {
    model {
      buildTypes {
        Release
        Debug
      }
      components {
        withType(NativeComponentSpec) {
          binaries.withType(NativeBinarySpec) {
            if(buildType == buildTypes.Debug) {
              if (it instanceof SharedLibraryBinarySpec) {
                sharedLibraryFile = appendDebugSuffix(sharedLibraryFile)
                sharedLibraryLinkFile = appendDebugSuffix(sharedLibraryLinkFile)
              } else if (it instanceof StaticLibraryBinarySpec) {
                staticLibraryFile = appendDebugSuffix(staticLibraryFile)
              } else if (it instanceof NativeExecutableBinarySpec) {
                // executable.file = ...
              } else {
                throw new GradleException("Unknown native library binary")
              }
            }
          }
        }
      }
    }
  }
}

As we can see from this code, it will apply an action for allprojects that will only execute if the cpp plugin is applied. It will then proceed in adding the Debug and Release buildTypes and configure NativeBinarySpec for all NativeComponentSpec. This is a convention as it will only do the work if specific condition are met for the entire project. The code is still a bit opinionated as it assume a Debug build type will be present. You could play around are remove this assumption in order to generalize event more the code. You can also only apply the convention on a subset of project by using subprojects or similar behavior.

I hope this answer your question,

Daniel


(ZOSROTHKO) #5

Thank for opening a path to a solution to meet the poco requirment… I will try it. By the way, I would like to propose an extension that can abstract this issue. Why not using a target container as the sources one in the model/components. One could have something like

    model {
        components {
            TestApp(SharedLibraryBinarySpec) {
                sources {
                    cpp {
                        source {
                            srcDir 'src'
                            include 'TestApp*.cpp'
                        }
                        lib project: ':CppUnit', library: 'PocoCppUnit'
                        lib project: ':Foundation', library: 'PocoFoundation'
                    }
                }
                target {
                    baseName         'TestApp'
                    sharedDir    'bin'
                    importDir    'imp'
                    
                }
            }

If not specified, target would default to the current outputDirectory and baseName while allowing to override those fields at user’s discretion.


(ZOSROTHKO) #6

Hi Daniel

Your proposal answered to the point of making the name of the binary depending on the buildTypes, that’s ok. But there is another point to solve: the user’s defined outputDirectory for each binaries which are in my story fixed paths, and this not at install time but at build time (needed for unit testing the dll portable code)


(Daniel Lacasse) #7

Hi zosrothko,

I don’t think I fully understand your last comment. Could you clarify a little bit more?

Also, thanks for the suggestion. We will see what we can do for those making this scenario easier to deal with.

Thanks,

Daniel