Add Suffix relative to buildTypes or platforms for native builds

Hello,

I try to add suffix to a generated library using gradle 3.1 depending of the buildTypes.
I have 2 build types, release and debug, for the debug I always want to add suffix “_d” but not for release.

I tried this but without success:

components {
        Core(NativeLibrarySpec)    {
            
            sources {
                cpp {
                    source {
                        srcDir file(ext.SourceDirPath)
                        include "**/*.cpp"
                    }
                    exportedHeaders {
                        srcDirs file(ext.SourceDirPath)
                        include "**/*.h"
                    }
                }
            }
            binaries.all
            {
                if(buildType == buildTypes.Debug) 
                {
                    baseName += "_d"
                }
            }
        }
    }

Thank you for your help!

Thanks for the question Astraya. What you are asking should be simple to do and we will work on improving this scenario. That been said here is a solution for the time been.

First lets start by identifying where your solution fall short. The property baseName doesn’t exist on NativeLibraryBinarySpec. Because of groovy delegation strategy, the baseName you are actually accessing is the one from the NativeComponentSpec (base class of NativeLibrarySpec). Since the configuration has already happy by the time your binary.all closure execute the code has no effect.

At the present time, the solution is:

apply plugin: 'cpp' 
 
File appendDebugSuffix(File binaryFile) { 
  int extensionSeparatorIndex = binaryFile.path.lastIndexOf('.') 
  return new File(binaryFile.path.substring(0, extensionSeparatorIndex) + "_d" + binaryFile.path.substring(extensionSeparatorIndex)) 
} 
 
model { 
  buildTypes { 
    Debug 
  } 
  components { 
    Core(NativeLibrarySpec) { 
      binaries.withType(NativeLibraryBinarySpec) { 
        if(buildType == buildTypes.Debug) { 
          if (it instanceof SharedLibraryBinarySpec) { 
            sharedLibraryFile = appendDebugSuffix(sharedLibraryFile) 
            sharedLibraryLinkFile = appendDebugSuffix(sharedLibraryLinkFile) 
          } else if (it instanceof StaticLibraryBinarySpec) { 
            staticLibraryFile = appendDebugSuffix(staticLibraryFile) 
          } else { 
            throw new GradleException("Unknown native library binary") 
          } 
        } 
      } 
    } 
  } 
} 

Basically, you need to modify directly the path for the ouput files of each binary you wish to modify. Unfortunately, each binary have various naming scheme for these output files. We use appendDebugSuffix function to localize the code logic. You can also use Apache commons-io FilenameUtils class if you want to cut down on custom logic.

Don’t hesitate to ask more question!

Hello Daniel,

Many thanks for your answer, your workaround works perfectly. I just start Gradle 5 days ago and it’s a great tool.
I have a last question about changing install directories.
I made something close to your solution but I don’t know if there is better solutions.

Thank you very much.

/**
* Change install directory for install task
*/
void ChangeInstallDir(Task task)
{
    /** If the install task is debug */
    if( task.name ==~ /.*Debug.*/ ){
        task.destinationDir = file("\\..\\..\\Bin\\Debug")
    }
    /** If the install task is debug */
    else if( task.name ==~ /.*Release.*/ ) {
        task.destinationDir = file("\\..\\..\\Bin\\Release")
    }
}
/**
* Change installation directory
*/
tasks.all { 
    Task task ->
        if( task instanceof InstallExecutable )
            ChangeInstallDir(task);
}

Sure thing Astraya. Here is how I would have written your code, keep in mind that I can be wrong and it all depends on the bigger picture:

/**
 * Change installation directory
 */
tasks.withType(InstallExecutable) /*(1)*/ { task ->
  // If the install task is debug
  if(/*(2)*/ -1 < task.name.indexOf("Debug")) {
    task.destinationDir = /*(4)*/ file(/*(3)*/ "../../Bin/Debug")
  }
  // If the install task is debug
  else if(/*(2)*/ -1 < task.name.indexOf("Release")) {
    task.destinationDir = /*(4)*/ file(/*(3)*/ "../../Bin/Release")
  }
}
  1. Be specific. This will make your code easier to read and maintain. Consider looking at the methods that are provided to you by the various container. For example, the tasks is a TaskContainer which in turn extends one of the fundamental container in Gradle. In the model, the fundamental container becomes a ModelMap. The method withType helps with the filtering of the container.

  2. Prefer Java feature. Instead of using Grooy regex will always be less performant, use simple string find like indexOf.

  3. Rarely use Windows path separator. In Java, you rarely need to explicitly use Windows path separator as it will automatically be converted to the right format. The only time you need to consider those is when you actually use Windows features such as calling Windows tools via command line (Exec task).

  4. file(…) is will already concat your path with the project directory path. Picture file(...) as simply appending what you pass as argument to the project directory with a path separator in between.

  5. Avoid creating too many function. Only create function when it make sense for code sharing and readability. In your code example with the refactoring, it make more sense to merge the function with the configuration closure. In other case, it’s probably good to create functions.

  6. Use one line comment where it make sense. If you are a C developer and new to the Java/Groovy world, you can do one line comment with //. It’s really nitpicking at this point but it’s good to be aware.

Final note, the code will only work when at least two build type are defined as for one build type the name of the task won’t include the build type.