Présentation de la compilation Gradle

Les applications Android sont généralement créées à l'aide du système de compilation Gradle. Avant d'entrer dans les détails de la configuration de votre compilation, nous allons explorer les concepts qui la sous-tendent afin que vous puissiez voir le système dans son ensemble.

Qu'est-ce qu'un build ?

Un système de compilation transforme votre code source en une application exécutable. Les builds impliquent souvent plusieurs outils pour analyser, compiler, lier et empaqueter votre application ou bibliothèque. Gradle utilise une approche basée sur les tâches pour organiser et exécuter ces commandes.

Les tâches encapsulent les commandes qui traduisent leurs entrées en sorties. Les plug-ins définissent les tâches et leur configuration. L'application d'un plug-in à votre build enregistre ses tâches et les associe à l'aide de leurs entrées et sorties. Par exemple, l'application du plug-in Android Gradle (AGP) à votre fichier de compilation enregistre toutes les tâches nécessaires pour créer un APK ou une bibliothèque Android. Le plug-in java-library vous permet de créer un fichier JAR à partir du code source Java. Il existe des plug-ins similaires pour Kotlin et d'autres langages, mais ils sont destinés à étendre les plug-ins. Par exemple, le plug-in protobuf est destiné à ajouter la compatibilité avec protobuf aux plug-ins existants tels que AGP ou java-library.

Gradle privilégie les conventions à la configuration. Les plug-ins sont donc fournis avec des valeurs par défaut adaptées, mais vous pouvez configurer davantage la compilation via un langage spécifique au domaine (DSL) déclaratif. Le DSL est conçu pour vous permettre de spécifier ce que vous souhaitez créer, plutôt que comment le créer. La logique des plug-ins gère le "comment". Cette configuration est spécifiée dans plusieurs fichiers de compilation de votre projet (et de vos sous-projets).

Les entrées de tâche peuvent être des fichiers et des répertoires, ainsi que d'autres informations encodées en tant que types Java (entiers, chaînes ou classes personnalisées). Les sorties ne peuvent être que des répertoires ou des fichiers, car elles doivent être écrites sur le disque. En connectant la sortie d'une tâche à l'entrée d'une autre, vous liez les tâches entre elles de sorte que l'une doit s'exécuter avant l'autre.

Bien que Gradle permette d'écrire du code arbitraire et des déclarations de tâches dans vos fichiers de compilation, cela peut compliquer la compréhension de votre compilation par les outils et sa maintenance par vous-même. Par exemple, vous pouvez écrire des tests pour le code dans les plug-ins, mais pas dans les fichiers de compilation. À la place, vous devez limiter la logique de compilation et les déclarations de tâches aux plug-ins (que vous ou quelqu'un d'autre définissez) et déclarer comment vous souhaitez utiliser cette logique dans vos fichiers de compilation.

Que se passe-t-il lors de l'exécution d'un build Gradle ?

Les compilations Gradle s'exécutent en trois phases. Chacune de ces phases exécute différentes parties du code que vous définissez dans vos fichiers de compilation.

  • L'initialisation détermine les projets et sous-projets inclus dans la compilation, et configure les chemins de classe contenant vos fichiers de compilation et les plug-ins appliqués. Cette phase se concentre sur un fichier de paramètres dans lequel vous déclarez les projets à compiler et les emplacements à partir desquels récupérer les plug-ins et les bibliothèques.
  • La configuration enregistre les tâches pour chaque projet et exécute le fichier de compilation pour appliquer la spécification de compilation de l'utilisateur. Il est important de comprendre que votre code de configuration n'aura pas accès aux données ni aux fichiers produits lors de l'exécution.
  • L'exécution effectue la "compilation" proprement dite de votre application. Le résultat de la configuration est un graphe orienté acyclique (DAG) de tâches, représentant toutes les étapes de compilation requises qui ont été demandées par l'utilisateur (les tâches fournies sur la ligne de commande ou par défaut dans les fichiers de compilation). Ce graphique représente la relation entre les tâches, qu'elle soit explicite dans la déclaration d'une tâche ou basée sur ses entrées et ses sorties. Si une tâche a une entrée qui est la sortie d'une autre tâche, elle doit s'exécuter après l'autre tâche. Cette phase exécute les tâches obsolètes dans l'ordre défini dans le graphique. Si les entrées d'une tâche n'ont pas changé depuis sa dernière exécution, Gradle l'ignore.

Pour en savoir plus, consultez Cycle de vie des builds dans Gradle.

DSL de configuration

Gradle utilise un langage spécifique au domaine (DSL, Domain-Specific Language) pour configurer les builds. Cette approche déclarative se concentre sur la spécification de vos données plutôt que sur l'écriture d'instructions détaillées (impératives). Vous pouvez écrire vos fichiers de compilation en Kotlin ou en Groovy, mais nous vous recommandons vivement d'utiliser Kotlin.

Les DSL visent à faciliter la contribution à un projet pour tous, experts du domaine et programmeurs, en définissant un petit langage qui représente les données de manière plus naturelle. Les plug-ins Gradle peuvent étendre le DSL pour configurer les données dont ils ont besoin pour leurs tâches.

Par exemple, la configuration de la partie Android de votre build peut se présenter comme suit :

Kotlin

android {
    namespace = "com.example.app"
    compileSdk {
        version = release(36) {
            minorApiLevel = 1
        }
    }
    // ...

    defaultConfig {
        applicationId = "com.example.app"
        minSdk {
            version = release(23)
        }
        targetSdk {
            version = release(36)
        }
        // ...
    }
}

Groovy

android {
    namespace = 'com.example.app'
    compileSdk {
        version = release(36) {
            minorApiLevel = 1
        }
    }
    // ...

    defaultConfig {
        applicationId = 'com.example.app'
        minSdk {
            version = release(23)
        }
        targetSdk {
            version = release(36)
        }
        // ...
    }
}

En coulisses, le code DSL ressemble à ceci :

fun Project.android(configure: ApplicationExtension.() -> Unit) {
    ...
}

interface ApplicationExtension {
    var namespace: String?

    fun compileSdk(configure: CompileSdkSpec.() -> Unit) {
        ...
    }

    val defaultConfig: DefaultConfig

    fun defaultConfig(configure: DefaultConfig.() -> Unit) {
        ...
    }
}

Chaque bloc du DSL est représenté par une fonction qui utilise un lambda pour le configurer, et une propriété du même nom pour y accéder. Le code de vos fichiers de compilation ressemble ainsi davantage à une spécification de données.

Dépendances externes

Le système de compilation Maven a introduit un système de spécification, de stockage et de gestion des dépendances. Les bibliothèques sont stockées dans des dépôts (serveurs ou répertoires), avec des métadonnées incluant leur version et leurs dépendances vis-à-vis d'autres bibliothèques. Vous spécifiez les dépôts à rechercher, les versions des dépendances que vous souhaitez utiliser, et le système de compilation les télécharge pendant la compilation.

Les artefacts Maven sont identifiés par le nom du groupe (entreprise, développeur, etc.), le nom de l'artefact (nom de la bibliothèque) et la version de cet artefact. Il est généralement représenté par group:artifact:version.

Cette approche améliore considérablement la gestion des builds. On parle souvent de "dépôts Maven", mais il s'agit en fait de la façon dont les artefacts sont empaquetés et publiés. Ces dépôts et métadonnées ont été réutilisés dans plusieurs systèmes de compilation, y compris Gradle (et Gradle peut publier dans ces dépôts). Les dépôts publics permettent de partager des dépendances pour que tout le monde puisse les utiliser, tandis que les dépôts d'entreprise conservent les dépendances internes en interne.

Vous pouvez également modulariser votre projet en sous-projets (également appelés "modules" dans Android Studio), qui peuvent également être utilisés comme dépendances. Chaque sous-projet produit des résultats (tels que des fichiers JAR) qui peuvent être utilisés par des sous-projets ou par votre projet de premier niveau. Cela peut améliorer le temps de compilation en isolant les parties à recompiler, et mieux séparer les responsabilités dans l'application.

Nous verrons plus en détail comment spécifier des dépendances dans Ajouter des dépendances de compilation.

Variantes de compilation

Lorsque vous créez une application Android, vous devez généralement créer plusieurs variantes. Les variantes contiennent un code différent ou sont créées avec des options différentes. Elles sont composées de types de compilation et de types de produit.

Les types de compilation varient en fonction des options de compilation déclarées. Par défaut, AGP configure les types de compilation "release" et "debug", mais vous pouvez les ajuster et en ajouter d'autres (par exemple, pour l'environnement de préproduction ou les tests internes).

Une version de débogage ne réduit pas ni n'obscurcit votre application, ce qui accélère sa compilation et préserve tous les symboles tels quels. Elle marque également l'application comme "débogable", en la signant avec une clé de débogage générique et en permettant l'accès aux fichiers de l'application installés sur l'appareil. Cela permet d'explorer les données enregistrées dans des fichiers et des bases de données lors de l'exécution de l'application.

Une compilation de release optimise l'application, la signe avec votre clé de release et protège les fichiers de l'application installée.

Les variantes de produit vous permettent de modifier la source incluse et les variantes de dépendance de l'application. Par exemple, vous pouvez créer des types "démo" et "complet" pour votre application, ou encore des types "sans frais" et "payant". Vous écrivez votre source commune dans un répertoire d'ensemble de sources "principal", puis vous remplacez ou ajoutez une source dans un ensemble de sources nommé d'après la version.

AGP crée des variantes pour chaque combinaison de types de compilation et de types de produit. Si vous ne définissez pas de types de produit, les variantes sont nommées d'après les types de compilation. Si vous définissez les deux, la variante est nommée <flavor><Buildtype>. Par exemple, avec les types de compilation release et debug, et les types de produit demo et full, AGP créera les variantes suivantes :

  • demoRelease
  • demoDebug
  • fullRelease
  • fullDebug

Étapes suivantes

Maintenant que vous avez vu les concepts de compilation, examinez la structure de compilation Android dans votre projet.