Динамическое генерирование продуктов

Я создал приложение для Android, которое необходимо создать во многих (30+) вариантах.

Моя идея состояла в том, чтобы сгенерировать различные productFlavors непосредственно из структуры папок в моем каталоге src, так как конфигурация всегда очень похожа (в основном просто другое имя пакета, новый значок запуска и некоторые строки изменяются).

Папка src выглядит следующим образом:

└── src
    ├── flavor1
    │   ├── flavor2.keystore
    │   ├── res
    ├── flavor2
    │   ├── res
    │   ├── flavor2.keystore    
    └── main
        ├── AndroidManifest.xml
        ├── java
        └── res

Если бы мне пришлось вручную создавать свойства gradle, это выглядело бы так:

android {

    ....

    productFlavors {
        flavor1 {
            packageName 'com.example.flavor1'
        }
        flavor2 {
            packageName 'com.example.flavor2'
        }
    }

}

Каждый раз, когда я пытаюсь изменить конфигурацию productFlavors после ее создания, я получаю либо ошибку, либо изменения/дополнения игнорируются молча.
Это может быть проблемой, вызванной мной, потому что мой опыт gradle/Groovy очень ограничен, или это невозможно.

В основном я получаю ошибку, говоря, что GroupableProductFlavorDsl_Decorated невозможно манипулировать так, как я хочу.

То, что я пытаюсь заархивировать, должно выглядеть примерно так:

android {

    ....

    def flavors = getMyFlavorsFromFileSystem()

    productFlavors {

    }

    flavors.each { name, config ->
        productFlavors[name] << config
    }

}

Примечание: Я знаю, что этот вопрос в основном представляет собой дубликат старого вопроса, о котором, к сожалению, никогда не отвечали. Поскольку gradle является новым для Android-мира, я надеюсь получить больше ответов, поскольку с момента последнего вопроса был задан вопрос, потому что теперь больше разработчиков используют gradle.

Update:

Вот некоторые очень простые подходы, которые я пробовал:

Вариант 1:

android {

    productFlavors {

    }

    productFlavors['flavor1'] << {
        packageName "com.example.flavor1"
    }

    productFlavors['flavor2'] << {
        packageName "com.example.flavor2"
    }
}

/*

A problem occurred evaluating root project 'MyProject'.
> GroupableProductFlavorDsl with name 'flavor1' not found.

*/

Вариант 2:

android {

    productFlavors {

    }

    productFlavors['flavor1'] = {
        packageName "com.example.flavor1"
    }

    productFlavors['flavor2'] = {
        packageName "com.example.flavor2"
    }
}

/*

no error, but does not work

*/

Вариант 3:

android {

    productFlavors {

    }

    productFlavors['flavor1'] = [packageName: "com.example.flavor1"]

    productFlavors['flavor2'] = [packageName: "com.example.flavor2"]
}

/*

no error, but does not work

*/

Все они как Gist.

Ответы

Ответ 1

Решается методом проб и ошибок:

android {

    // let assume these are return by a function which reads the filesystem
    def myFlavors = [
        flavor1: [
            packageName: "com.example.flavor1"
        ],
        flavor2: [
            packageName: "com.example.flavor2"
        ]
    ]

    productFlavors {
        myFlavors.each { name, config ->
            "$name" {
                packageName config.packageName
            }
        }
    }

}

Ответ 2

Я знаю, что для этого уже есть ответ, но я как бы комбинировал подход Chris.Zou с TheHippo. И добавьте свой собственный метод для этого.

В принципе, при работе с ароматами мы обычно работаем с разными каталогами в /app/src/, который содержит ресурсы. И поскольку имя каталога равно имени пакета, я просто перечислял каталоги в этой папке (исключая "main" и "androidTest" ).

Итак, вот мой полный, рабочий gradle script:

def map = [:]

new File("./app/src").eachFile() { file->

    def fileName = file.getName()
    if( fileName == "main" || fileName == "androidTest") {
        return
    }

    map.put(fileName, 'com.mypackagename.' + fileName )


}
productFlavors {
    map.each { flavorName, packagename ->
        "$flavorName" {
            applicationId packagename
        }
    }
}

Edit:

  • Также хотел бы добавить, com.mypackagename в основном является корневым путем для всех вкусов.
  • У меня есть отдельный script, который скопирует каталог аромата в папку /app/src/.

Ответ 3

Так как автор вопроса не хотел делиться своим кодом для чтения файлов. Я напишу о том, что я сделал. Я поместил все имена вариантов в файл с именем "app/build_variants.txt", по одной строке для каждого, что-то вроде этого:

flavor1
flavor2
flavor3
flavor4
flavor5

И в "app/build.gradle":

//...other configs...

android {
    productFlavors {
        new File('app/build_variants.txt').eachLine { "$it" { resValue "string", "build_variant_name", "$it" } }
    }
}

//...other configs

Это будет иметь тот же результат, что и следующий код:

//...other configs...

android {
    productFlavors {
        flavor1 { resValue "string", "build_variant_name", "flavor1" } 
        flavor2 { resValue "string", "build_variant_name", "flavor2" } 
        flavor3 { resValue "string", "build_variant_name", "flavor3" } 
        flavor4 { resValue "string", "build_variant_name", "flavor4" } 
        flavor5 { resValue "string", "build_variant_name", "flavor5" } 
    }
}

//...other configs

Ключевым здесь является строка new File('app/build_variants.txt').eachLine { "$it" { resValue "string", "build_variant_name", "$it" } }. Он читает файл "app/build_variants.txt" и для каждой строки генерирует аромат продукта с именем в строке, которую он читает. $it - это строка, в которую он проходит. Это просто синтаксис закрытия groovy. Если вы хотите более глубокого понимания. Я настоятельно рекомендую смотреть @видео Daniel Lew о groovy и gradle. Это потрясающе!