iurysza / module-graph

A Gradle Plugin for visualizing your project's structure, powered by mermaidjs
https://plugins.gradle.org/plugin/dev.iurysouza.modulegraph
MIT License
345 stars 13 forks source link
android automation gradle gradle-kotlin-dsl gradle-plugin kotlin mermaidjs

Pre Merge Checks License Language

module graph icon

Module Graph Plugin

This plugin generates a Mermaid graph of your project's module relationships, so you can see how your modules interact at a glance.

A diagram about the current system is only useful if it's generated. If it is produced by hand it documents the author's belief, not the system. Still, important, but not an input for decision making. Development is primarily decision-making. Enable it through custom tools. source

You can read more about the background story of this plugin here.

Main Features ⭐

Getting Started

You'll just need to add it to your project's root build.gradle or build.gradle.kts file.

build.gradle (Groovy DSL) #### Using the plugins DSL ```groovy plugins { id "dev.iurysouza.modulegraph" version "0.10.1" } ```
Using Legacy Plugin application ```groovy buildscript { repositories { maven { url "https://plugins.gradle.org/m2/" } } dependencies { classpath "dev.iurysouza:modulegraph:0.10.1" } } apply plugin: "dev.iurysouza.modulegraph" ```
### Configuring the plugin ```groovy import dev.iurysouza.modulegraph.LinkText import dev.iurysouza.modulegraph.ModuleType import dev.iurysouza.modulegraph.Orientation import dev.iurysouza.modulegraph.Theme moduleGraphConfig { readmePath = "./README.md" heading = "### Module Graph" // showFullPath = false // optional // orientation = Orientation.LEFT_TO_RIGHT // optional // linkText = LinkText.NONE // optional // excludedConfigurationsRegex = ".*test.*" // optional // excludedModulesRegex = ".*moduleName.*" // optional // focusedModulesRegex = ".*(projectName).*" // optional // rootModulesRegex = ".*moduleName.*" // optional // setStyleByModuleType = true // optional // theme = Theme.NEUTRAL // optional // Or you can fully customize it by using the BASE theme: // theme = new Theme.BASE( // [ // "primaryTextColor": "#F6F8FAff", // All text colors // "primaryColor": "#5a4f7c", // Node color // "primaryBorderColor": "#5a4f7c", // Node border color // "tertiaryColor": "#40375c", // Container box background // "lineColor": "#f5a623", // "fontSize": "12px" // ], // focusColor = "#F5A622" // Color of the focused nodes if any // ) // theme.set( // new Theme.BASE( // themeVariables: [ // "primaryTextColor": "#F6F8FAff", // All text colors // "primaryColor": "#5a4f7c", // Node color // "primaryBorderColor": "#5a4f7c", // Node border color // "tertiaryColor": "#40375c", // Container box background // "lineColor": "#f5a623", // "fontSize": "12px" // ], // focusColor: "#F5A622", // Color of the focused nodes if any // moduleTypes: [ // new ModuleType.AndroidLibrary("#2C4162") // ] // ) // ) // You can add additional graphs. // A separate graph will be generated for each config below. // graph( // "./README.md", // "# Graph with root: gama", // ) { // it.rootModulesRegex = ".*gama.*" // } // graph( // "./SomeOtherReadme.md", // "# Graph", // ) { // it.rootModulesRegex = ".*zeta.*" // } } ```


build.gradle.kts (Kotlin DSL)

#### Using the plugins DSL ```kotlin plugins { id("dev.iurysouza.modulegraph") version "0.10.1" } ```
Using Legacy Plugin application ```kotlin buildscript { repositories { maven { url = uri("https://plugins.gradle.org/m2/") } } dependencies { classpath("dev.iurysouza:modulegraph:0.10.1") } } apply(plugin = "dev.iurysouza:modulegraph") ```
### Configuring the plugin ```kotlin import dev.iurysouza.modulegraph.LinkText import dev.iurysouza.modulegraph.ModuleType import dev.iurysouza.modulegraph.Orientation import dev.iurysouza.modulegraph.Theme moduleGraphConfig { readmePath.set("./README.md") heading = "### Module Graph" // showFullPath.set(false) // optional // orientation.set(Orientation.LEFT_TO_RIGHT) //optional // linkText.set(LinkText.NONE) // optional // setStyleByModuleType.set(true) // optional // excludedConfigurationsRegex.set(".*test.*") // optional // excludedModulesRegex.set(".*moduleName.*") // optional // focusedModulesRegex.set(".*(projectName).*") // optional // rootModulesRegex.set(".*moduleName.*") // optional // theme.set(Theme.NEUTRAL) // optional // or you can fully customize it by using the BASE theme: // Theme.BASE( // themeVariables = mapOf( // "primaryTextColor" to "#F6F8FAff", // Text // "primaryColor" to "#5a4f7c", // Node // "primaryBorderColor" to "#5a4f7c", // Node border // "tertiaryColor" to "#40375c", // Container box background // "lineColor" to "#f5a623", // "fontSize" to "12px", // ), // focusColor = "#F5A622", // moduleTypes = listOf( // ModuleType.AndroidLibrary("#2C4162"), // ) // ), // ) // You can add additional graphs. // A separate graph will be generated for each config below. // graph( // readmePath = "./README.md", // heading = "# Graph with root: gama", // ) { // rootModulesRegex = ".*gama.*" // } // graph( // readmePath = "./SomeOtherReadme.md", // heading = "# Graph", // ) { // rootModulesRegex = ".*zeta.*" // } } ```

Usage

Make sure you have a heading in your README with the same format as the one you set in the configuration, if not, the plugin will append it with the graph to the end of the file.

After that, just run the following command:

./gradlew createModuleGraph

Now, just look for the generated graph in your project's README file.

Configuration Docs

Each Graph has the following configuration parameters.

Required settings:

Optional settings:

Multiple graphs

You can apply configuration options directly in the root of the moduleGraphConfig block like so:

moduleGraphConfig {
    readmePath.set("${rootDir}/README.md")
    heading.set("### Module Graph")
    showFullPath.set(false)
}

When you do this, you are configuring the 'Primary Graph'. This is useful if you only need one graph to be generated.

But sometimes you want multiple graphs to be generated. To achieve this you can add additional graph configs using graph. Each additional graph has exactly the same configuration parameters as the primary graph:

moduleGraphConfig {
    graph(
        readmePath = "${rootDir}/README.md",
        heading = "### Module Graph",
    ) {
        showFullPath = false
    }
}

Note that graph requires the required parameters to be provided in the function call, while the optional parameters can be provided in the configuration block.

You can add as many graph calls as you like: each one will generate a separate graph:

moduleGraphConfig {
    graph(
        readmePath = "${rootDir}/README.md",
        heading = "### Module Graph",
    ) {
        showFullPath = false
    }
    graph(
        readmePath = "${rootDir}/README.md",
        heading = "### Another Module Graph",
    ) {
        showFullPath = true
    }
}

For this plugin to work, you need to configure at least one graph. This can be via the Primary Graph, or via a graph call.

If using only graph calls, then the Primary Graph doesn't need to be setup at all! You can see this in the samples above.

Show me that graph!

This is an example of using the plugin on an Android project with a multi-module setup. Here, the following configuration was used:

moduleGraphConfig {
    readmePath.set("${rootDir}/README.md")
    heading.set("### Module Graph")
    theme.set(
        Theme.BASE(
            mapOf(
                "primaryTextColor" to "#fff",
                "primaryColor" to "#5a4f7c",
                "primaryBorderColor" to "#5a4f7c",
                "lineColor" to "#f5a623",
                "tertiaryColor" to "#40375c",
                "fontSize" to "12px",
            ),
            focusColor = "#FA8140"
        ),
    )
}

And we got this graph:

%%{
  init: {
    'theme': 'base',
    'themeVariables': {"primaryTextColor":"#fff","primaryColor":"#5a4f7c","primaryBorderColor":"#5a4f7c","lineColor":"#f5a623","tertiaryColor":"#40375c","fontSize":"12px"}
  }
}%%
graph LR
    subgraph app
        main
        playground
    end
    subgraph core
        common
        design-system
        footballinfo
        reddit
        webview-to-native-player
    end
    subgraph features
        match-day
        match-thread
    end
    footballinfo --> common
    match-day --> common
    match-day --> footballinfo
    match-day --> design-system
    match-day --> reddit
    match-thread --> webview-to-native-player
    match-thread --> common
    match-thread --> footballinfo
    match-thread --> design-system
    match-thread --> reddit
    playground --> webview-to-native-player
    playground --> match-thread
    playground --> match-day
    playground --> design-system
    reddit --> common
    webview-to-native-player --> common
    main --> match-thread
    main --> match-day
    main --> design-system
    main --> common

Too much information? We can fix that.

Focusing on specific nodes

If you want to focus on specific nodes in the graph, you can use the focusedModulesRegex property in the configuration.

moduleGraphConfig {
    //... keep previous configs
    focusedModulesRegex.set(".*(reddit).*")
}

By doing this, the plugin will highlight the nodes that match the pattern, and will only show the other nodes that are connected to them. It will generate the following graph:

%%{
  init: {
    'theme': 'base',
    'themeVariables': {"primaryTextColor":"#fff","primaryColor":"#5a4f7c","primaryBorderColor":"#5a4f7c","lineColor":"#f5a623","tertiaryColor":"#40375c","fontSize":"12px"}
  }
}%%
graph LR
    subgraph core
        common
        reddit
    end
    subgraph features
        match-day
        match-thread
    end
    match-day --> reddit
    match-thread --> reddit
    reddit --> common
    classDef focus fill:#E04380,stroke:#fff,stroke-width:2px,color:#fff;
    class reddit focus

Since it's just a regex pattern, you can, for example, match multiple nodes by using the | operator, or you can come up with whatever cryptic regex patterns you want if you're into that kind of thing.

When was the last time Regex made you happy? =)

// This matches module names that contain "reddit" or "match-day"
focusedModulesRegex.set(".*(reddit|match-day).*")
%%{
  init: {
    'theme': 'base',
    'themeVariables': {"primaryTextColor":"#fff","primaryColor":"#5a4f7c","primaryBorderColor":"#5a4f7c","lineColor":"#f5a623","tertiaryColor":"#40375c","fontSize":"12px"}
  }
}%%
graph LR
    subgraph app
        main
        playground
    end
    subgraph core
        common
        design-system
        footballinfo
        reddit
    end
    subgraph features
        match-day
        match-thread
    end
    match-day --> common
    match-day --> footballinfo
    match-day --> design-system
    match-day --> reddit
    match-thread --> reddit
    playground --> match-day
    reddit --> common
    main --> match-day
    classDef focus fill:#E04380,stroke:#fff,stroke-width:2px,color:#fff;
    class match-day focus
    class reddit focus

Module type based styling

This feature enables detecting and rendering modules based on their type, eg.: kotlin, java, android-library, kotlin-multiplatform, etc

Getting Started

Just toggle this option on:

moduleGraphConfig {
    //..
    setStyleByModuleType.set(true)
}

That's it. Just run the task and you'll get a graph identifying modules by their type.

Batteries included

We have default styling for these module types:

These supported plugins are pre-configured with a default color pattern, but can be customized further if needed. You can also add you own module type.

Customization

The supported plugins already have a default color pattern , but you can also customize them via the Theme.BASE object.

Additionally, you can detect and customize styling for other plugins by providing a CustomPlugin with an id and its color. The ID will be used to match a Gradle plugin applied to that module and will have higher precedence than all the others. For example, if you have a plugin defined with the app.compose id, you can pass it as Custom("app.compose", "#0E0E0E") and the graph will be generated accordingly. eg.:

import dev.iurysouza.modulegraph.ModuleType.*
import dev.iurysouza.modulegraph.Theme

moduleGraphConfig {
    theme.set(
        Theme.BASE(
            moduleTypes = listOf(
                Custom(id = "app.compose", color = "#0E0E0E"),
                AndroidApp("#3CD483"),
                AndroidLibrary("#292B2B"),
            ),
        ),
    )
}

Below is an example of how the module graph would show up:

%%{
  init: {
    'theme': 'base',
    'themeVariables': {"lineColor":"#676767"},
  }
}%%
graph LR
    subgraph app
        playground
        main
    end
    subgraph core
        webview-to-native-player
        common
        footballinfo
        design-system
        reddit
    end
    subgraph features
        match-thread
        match-day
    end
    match-thread --> webview-to-native-player
    match-thread --> common
    match-thread --> footballinfo
    match-thread --> design-system
    match-thread --> reddit
    match-day --> common
    match-day --> footballinfo
    match-day --> design-system
    match-day --> reddit
    playground --> webview-to-native-player
    playground --> match-thread
    playground --> design-system
    playground --> match-day
    main --> match-thread
    main --> match-day
    main --> design-system
    main --> common
    reddit --> common
    webview-to-native-player --> common
    footballinfo --> common
    classDef android_library fill:#292B2B,stroke:#fff,stroke-width:2px,color:#fff;
    classDef app_compose fill:#82AAFF,stroke:#fff,stroke-width:2px,color:#fff;
    classDef android_application fill:#3CD483,stroke:#fff,stroke-width: 2px,color:#fff;
    class match-thread app_compose
    class webview-to-native-player android_library
    class common android_library
    class footballinfo android_library
    class design-system app_compose
    class reddit android_library
    class match-day app_compose
    class playground android_application
    class main android_application

[!NOTE] Modules can only have one type. So we're using a hardcoded precedence order for identifying them.

Precedence

The system determines the module type based on the hierarchy of applied plugins. For instance:

Contributing 🤝

Feel free to open an issue or submit a pull request for any bugs/improvements.

License 📄

This project is licensed under the MIT License - see the License file for details.

Buy Me a Coffee

If you found this project useful or want to support the development, consider buying me a coffee! Any donations are greatly appreciated and help to support the development. Relevant xkcd.

Buy Me A Pingado