7

I have long build.gradle file which have functions I want to move into separate .gradle file to keep logic clean. The docs suggests to use external build scripts for this case. I have next function at build.gradle file:

android{
  buildTypes {
    debug {
        signingConfig = loadFromPropertiesFile("DEBUG_KEY_PROPERTIES")
    }
  }
}

import com.android.builder.signing.DefaultSigningConfig
import com.android.builder.model.SigningConfig

SigningConfig loadFromPropertiesFile(keyProperty) {
    // Load signing config from singning properties file
    println keyProperty
    println ("${keyProperty}")
    if (project.hasProperty(keyProperty)) {
        File releasePropsFile = new File(project.property(keyProperty))
        println("Loading config from properties file: ${releasePropsFile}")
        if (releasePropsFile.exists()) {
            Properties releaseProps = new Properties()
            releaseProps.load(new FileInputStream(releasePropsFile))
            println releaseProps

            def signingConfig = new DefaultSigningConfig(keyProperty)
            signingConfig.storeFile = file(releasePropsFile.getParent() + "/" + releaseProps['keystore.file'])
            signingConfig.storePassword = releaseProps['keystore.password']
            //signingConfig.storeType = 'PKCS12'
            signingConfig.keyAlias = releaseProps['alias.name']
            signingConfig.keyPassword = releaseProps['alias.password']
            return signingConfig
        } else {
            println("Can't read configuration file: ${releasePropsFile}")
            return null
        }
    } else {
        println("Project has not define configuration file: ${keyProperty}")
        return null
    }
}

The code logic doesn't matter, it is working fine when it is placed at build.gradle file. But it fails when I move this method to the external file and include it with:

apply from: "$rootDir/gradle/android-signing.gradle"

I got next error:

Cannot cast object 'DefaultSigningConfig{..}' with class
com.android.builder.signing.DefaultSigningConfig' to class 
'com.android.builder.model.SigningConfig'

Basically it says that it can not cast implementation to the interface. Because DefaultSigningConfig implements SigningConfig see here. Which makes no sense, until I see next answer.

Two classes are treated as entirely different classes, even if they have the same package and name (and even implementation/fields/methods) when loaded by different classloaders. Which is the case when you are using plugin or external build script.

But then, how can I split up methods from build.gradle into modular separate files?

Community
  • 1
  • 1
Sergii Pechenizkyi
  • 22,227
  • 7
  • 60
  • 71
  • when do you use apply from: ? do you define only the method loadFromPropertiesFile ? if you want to use android {} clause and it's relative classes you have to make sure you have applied the android plugin BEFORE you cal apply from! the apply from is basically just like include except that local variables if defined are not passed on. – codeScriber Jun 25 '15 at 19:50
  • @Sergii Pechenizkyi Ever work this one out? – Jesse Vogt Jul 13 '15 at 14:43

1 Answers1

0

I have found (thanks, opensource!) that android plugin DSL api is inconsistent by letting the setter take SigningConfig interface, but forcing getter to cast to internal.dsl.SigningConfig class.

So it should be like this:

package com.android.builder.core;

import com.android.builder.internal.BaseConfigImpl;
import com.android.builder.model.BuildType;
import com.android.builder.model.SigningConfig;

public class DefaultBuildType extends BaseConfigImpl implements BuildType {

    ...

    public BuildType setSigningConfig(SigningConfig signingConfig) {
        mSigningConfig = signingConfig;
        return this;
    }

    @Override
    public SigningConfig getSigningConfig() {
        return mSigningConfig;
    }
}

But then BuiltTypeDSL force cast to SigningConfigDSL class:

package com.android.build.gradle.internal.dsl

import com.android.builder.core.DefaultBuildType

/**
 * DSL object to configure build types.
 */
public class BuildType extends DefaultBuildType implements Serializable {

    ...

    /** The signing configuration. */
    @Override
    SigningConfig getSigningConfig() {
        return (SigningConfig) super.signingConfig
    }
}

Note that both methods getSigningConfig() have return type with the same name SigningConfig but different packagename. One is interface com.android.builder.model.SigningConfig and the other is class com.android.build.gradle.internal.dsl.SigningConfig which extends com.android.builder.signing.DefaultSigningConfig which implements com.android.builder.model.SigningConfig and that's where my code stops working, because due to OOP principles we can cast DefaultSigningConfig to the SigningConfig interface, but can't cast it to internal.dsl.SigningConfig class.

To make code working, we can either create internal.dsl.SigningConfig instead of DefaultSigningConfig:

import com.android.build.gradle.internal.dsl.SigningConfig

def signingConfig = new SigningConfig(keyProperty) // note that this is class which extends DefaultSigningConfig, not interface
signingConfig.storeFile = ... // same as before
signingConfig.storePassword = ...
signingConfig.keyAlias = ...
signingConfig.keyPassword = ...
return signingConfig

or wrap DefaultSigningConfig with internal dsl model:

android{
  signingConfigs {
      debug {
          initWith loadFromPropertiesFile("DEBUG_KEY_PROPERTIES")
      }
  }

  buildTypes {
    debug {
        signingConfig signingConfigs.debug
    }
  }
}
Sergii Pechenizkyi
  • 22,227
  • 7
  • 60
  • 71