Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
plugins {
alias(libs.plugins.jetbrains.kotlin.jvm) apply false
alias(libs.plugins.kotlin.jvm) apply false
alias(libs.plugins.kover) apply false
alias(libs.plugins.maven.publish) apply false
}
21 changes: 1 addition & 20 deletions gradle.properties
Original file line number Diff line number Diff line change
@@ -1,25 +1,6 @@
# Project-wide Gradle settings.
# IDE (e.g. Android Studio) users:
# Gradle settings configured through the IDE *will override*
# any settings specified in this file.
# For more details on how to configure your build environment visit
# http://www.gradle.org/docs/current/userguide/build_environment.html
# Specifies the JVM arguments used for the daemon process.
# The setting is particularly useful for tweaking memory settings.
org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
# When configured, Gradle will run in incubating parallel mode.
# This option should only be used with decoupled projects. For more details, visit
# https://developer.android.com/r/tools/gradle-multi-project-decoupled-projects
# org.gradle.parallel=true
# AndroidX package structure to make it clearer which packages are bundled with the
# Android operating system, and which are packaged with your app's APK
# https://developer.android.com/topic/libraries/support-library/androidx-rn
org.gradle.jvmargs=-Xmx4096m -Dfile.encoding=UTF-8 -XX:MetaspaceSize=1g
android.useAndroidX=true
# Kotlin code style for this project: "official" or "obsolete":
kotlin.code.style=official
# Enables namespacing of each library's R class so that its R class includes only the
# resources declared in the library itself and none from the library's dependencies,
# thereby reducing the size of the R class for that library
android.nonTransitiveRClass=true

POM_NAME=ProjectGuard Plugin
Expand Down
3 changes: 2 additions & 1 deletion gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,12 @@ kotlin-stdlib = { group = "org.jetbrains.kotlin", name = "kotlin-stdlib", versio
runner = { group = "androidx.test", name = "runner", version.ref = "runner" }
core = { group = "androidx.test", name = "core", version.ref = "core" }
ext-junit = { group = "androidx.test.ext", name = "junit", version.ref = "junitVersion" }
gradle-android = { group = "com.android.tools.build", name = "gradle", version.ref = "agp" }

[plugins]
android-application = { id = "com.android.application", version.ref = "agp" }
kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
jetbrains-kotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" }
kotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" }
maven-publish = { id = "com.vanniktech.maven.publish", version = "0.36.0" }
projectguard = { id = "com.rubensousa.projectguard", version = "unspecified" }
kotlin-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" }
Expand Down
15 changes: 15 additions & 0 deletions projectguard/api/projectguard.api
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,13 @@ public abstract interface class com/rubensousa/projectguard/plugin/GuardScope {
public abstract fun deny (Lorg/gradle/api/provider/Provider;Lorg/gradle/api/Action;)V
}

public final class com/rubensousa/projectguard/plugin/LifecycleTask : java/lang/Enum {
public static final field ASSEMBLE Lcom/rubensousa/projectguard/plugin/LifecycleTask;
public static final field CHECK Lcom/rubensousa/projectguard/plugin/LifecycleTask;
public static fun valueOf (Ljava/lang/String;)Lcom/rubensousa/projectguard/plugin/LifecycleTask;
public static fun values ()[Lcom/rubensousa/projectguard/plugin/LifecycleTask;
}

public abstract interface class com/rubensousa/projectguard/plugin/ModuleRestrictionScope {
public abstract fun allow ([Ljava/lang/String;)V
public abstract fun allow ([Lorg/gradle/api/internal/catalog/DelegatingProjectDependency;)V
Expand All @@ -34,10 +41,16 @@ public abstract interface class com/rubensousa/projectguard/plugin/ModuleRestric
public abstract fun reason (Ljava/lang/String;)V
}

public abstract interface class com/rubensousa/projectguard/plugin/OptionScope {
public abstract fun getLifecycleTask ()Lcom/rubensousa/projectguard/plugin/LifecycleTask;
public abstract fun setLifecycleTask (Lcom/rubensousa/projectguard/plugin/LifecycleTask;)V
}

public abstract class com/rubensousa/projectguard/plugin/ProjectGuardExtension : com/rubensousa/projectguard/plugin/ProjectGuardScope {
public fun <init> (Lorg/gradle/api/model/ObjectFactory;)V
public fun guard (Ljava/lang/String;Lorg/gradle/api/Action;)V
public fun guardRule (Lorg/gradle/api/Action;)Lcom/rubensousa/projectguard/plugin/GuardRule;
public fun options (Lorg/gradle/api/Action;)V
public fun report (Lorg/gradle/api/Action;)V
public fun restrictDependency (Ljava/lang/String;Lorg/gradle/api/Action;)V
public fun restrictDependency (Lorg/gradle/api/provider/Provider;Lorg/gradle/api/Action;)V
Expand All @@ -56,6 +69,8 @@ public abstract interface class com/rubensousa/projectguard/plugin/ProjectGuardS
public abstract fun guard (Ljava/lang/String;Lorg/gradle/api/Action;)V
public fun guard (Lorg/gradle/api/internal/catalog/DelegatingProjectDependency;Lorg/gradle/api/Action;)V
public abstract fun guardRule (Lorg/gradle/api/Action;)Lcom/rubensousa/projectguard/plugin/GuardRule;
public fun options (Lgroovy/lang/Closure;)V
public abstract fun options (Lorg/gradle/api/Action;)V
public fun report (Lgroovy/lang/Closure;)V
public abstract fun report (Lorg/gradle/api/Action;)V
public fun restrictDependency (Ljava/lang/String;Lgroovy/lang/Closure;)V
Expand Down
1 change: 1 addition & 0 deletions projectguard/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ dependencies {
implementation(libs.kotlin.serialization.json)
implementation(libs.jackson.yaml)
implementation(libs.jackson.kotlin)
implementation(libs.gradle.android)
testImplementation(gradleTestKit())
testImplementation(libs.kotlin.test)
testImplementation(libs.truth)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/*
* Copyright 2026 Rúben Sousa
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.rubensousa.projectguard.plugin

enum class LifecycleTask {
ASSEMBLE,
CHECK
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/*
* Copyright 2026 Rúben Sousa
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.rubensousa.projectguard.plugin

interface OptionScope {
/**
* `projectGuardCheck` will be included in either [LifecycleTask.CHECK], [LifecycleTask.ASSEMBLE]
* or none if null
*/
var lifecycleTask: LifecycleTask?
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ import com.rubensousa.projectguard.plugin.internal.GuardScopeImpl
import com.rubensousa.projectguard.plugin.internal.GuardSpec
import com.rubensousa.projectguard.plugin.internal.ModuleRestrictionScopeImpl
import com.rubensousa.projectguard.plugin.internal.ModuleRestrictionSpec
import com.rubensousa.projectguard.plugin.internal.OptionScopeImpl
import com.rubensousa.projectguard.plugin.internal.PluginOptions
import com.rubensousa.projectguard.plugin.internal.ProjectGuardSpec
import com.rubensousa.projectguard.plugin.internal.ReportScopeImpl
import com.rubensousa.projectguard.plugin.internal.ReportSpec
Expand All @@ -41,6 +43,9 @@ abstract class ProjectGuardExtension @Inject constructor(
private val moduleRestrictionSpecs = objects.listProperty<ModuleRestrictionSpec>()
private val dependencyRestrictionSpecs = objects.listProperty<DependencyRestrictionSpec>()
private var reportSpec = ReportSpec(showLibrariesInGraph = false)
private var options = PluginOptions(
lifecycleTask = null
)

override fun restrictModule(modulePath: String, action: Action<ModuleRestrictionScope>) {
val scope = ModuleRestrictionScopeImpl()
Expand Down Expand Up @@ -127,12 +132,21 @@ abstract class ProjectGuardExtension @Inject constructor(
)
}

override fun options(action: Action<OptionScope>) {
val scope = OptionScopeImpl()
action.execute(scope)
options = options.copy(
lifecycleTask = scope.lifecycleTask,
)
}

internal fun getSpec(): ProjectGuardSpec {
return ProjectGuardSpec(
guardSpecs = guardSpecs.get(),
moduleRestrictionSpecs = moduleRestrictionSpecs.get(),
dependencyRestrictionSpecs = dependencyRestrictionSpecs.get(),
reportSpec = reportSpec
reportSpec = reportSpec,
options = options
)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

package com.rubensousa.projectguard.plugin

import com.android.build.api.variant.AndroidComponentsExtension
import com.rubensousa.projectguard.plugin.internal.DependencyGraphBuilder
import com.rubensousa.projectguard.plugin.internal.task.TaskAggregateDependencyDump
import com.rubensousa.projectguard.plugin.internal.task.TaskAggregateRestrictionDump
Expand Down Expand Up @@ -53,6 +54,13 @@ class ProjectGuardPlugin : Plugin<Project> {
private val dependenciesFilePath = "reports/$pluginId/dependencies.json"
private val jsonReportFilePath = "reports/$pluginId/report.json"
private val graphBuilder = DependencyGraphBuilder()
private val androidPluginIds = listOf(
"com.android.test",
"com.android.application",
"com.android.library",
"com.android.dynamic-feature",
"com.android.kotlin.multiplatform.library"
)

override fun apply(target: Project) {
val rootProject = target.rootProject
Expand All @@ -71,6 +79,8 @@ class ProjectGuardPlugin : Plugin<Project> {
individualModuleTasks.add(moduleTasks)
setupModuleTasks(
aggregationTasks = aggregationTasks,
project = targetProject,
extension = extension,
moduleTasks = moduleTasks
)
}
Expand All @@ -93,6 +103,8 @@ class ProjectGuardPlugin : Plugin<Project> {
}

private fun setupModuleTasks(
project: Project,
extension: ProjectGuardExtension,
aggregationTasks: AggregationTasks,
moduleTasks: ModuleTasks,
) {
Expand All @@ -109,6 +121,43 @@ class ProjectGuardPlugin : Plugin<Project> {
outputDir.set(project.layout.buildDirectory.dir(htmlAggregateReportFilePath))
reportFilePath.set(getProjectReportFilePath(project))
}

project.afterEvaluate {
val options = extension.getSpec().options
options.lifecycleTask?.let { lifecycleTask ->
if (lifecycleTask == LifecycleTask.ASSEMBLE) {
attachToAndroidAssembleTasks(project, moduleTasks.check)
project.tasks.findByName("assemble")?.dependsOn(moduleTasks.check)
} else {
project.tasks.findByName("check")?.dependsOn(moduleTasks.check)
}
}
}
}

private fun attachToAndroidAssembleTasks(
project: Project,
checkTask: TaskProvider<TaskCheck>,
) {
androidPluginIds.forEach { pluginId ->
if (project.plugins.hasPlugin(pluginId)) {
val androidComponents = project.extensions.getByType(AndroidComponentsExtension::class.java)
val variantTasks = mutableListOf<String>()
androidComponents.onVariants { variant ->
val variantName = capitalizeVariantName(variant.name)
variantTasks.add("assemble$variantName")
}
project.afterEvaluate {
variantTasks.forEach { variantTask ->
project.tasks.findByName(variantTask)?.dependsOn(checkTask)
}
}
}
}
}

private fun capitalizeVariantName(name: String): String {
return name.substring(0, 1).uppercase() + name.substring(1, name.length)
}

private fun setupAggregationTasks(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -207,4 +207,10 @@ interface ProjectGuardScope {
report(ConfigureUtil.configureUsing(closure))
}

fun options(action: Action<OptionScope>)

fun options(closure: Closure<OptionScope>) {
options(ConfigureUtil.configureUsing(closure))
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/*
* Copyright 2026 Rúben Sousa
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.rubensousa.projectguard.plugin.internal

import com.rubensousa.projectguard.plugin.LifecycleTask
import com.rubensousa.projectguard.plugin.OptionScope

internal class OptionScopeImpl : OptionScope {

override var lifecycleTask: LifecycleTask? = null

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/*
* Copyright 2026 Rúben Sousa
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.rubensousa.projectguard.plugin.internal

import com.rubensousa.projectguard.plugin.LifecycleTask
import java.io.Serializable

internal data class PluginOptions(
val lifecycleTask: LifecycleTask?
): Serializable
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,5 @@ internal data class ProjectGuardSpec(
val moduleRestrictionSpecs: List<ModuleRestrictionSpec>,
val dependencyRestrictionSpecs: List<DependencyRestrictionSpec>,
val reportSpec: ReportSpec,
val options: PluginOptions,
) : Serializable
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ class GroovyIntegrationTest {
pluginRunner.addDependency(from = module, to = dependency)

// then
pluginRunner.assertCheckFails(module)
pluginRunner.assertProjectGuardCheckFails(module)
pluginRunner.assertTaskOutputContains(reason)
}

Expand All @@ -96,7 +96,7 @@ class GroovyIntegrationTest {
pluginRunner.addDependency(from = module, to = dependency)

// then
pluginRunner.assertCheckSucceeds(module)
pluginRunner.assertProjectGuardCheckSucceeds(module)
pluginRunner.assertTaskOutputContains("No fatal matches found")
}

Expand Down
Loading