Remove old muzzle check plugin (#3741)

* Remove old muzzle check plugin

* Polish
This commit is contained in:
Nikita Salnikov-Tarnovski 2021-08-03 09:21:50 +03:00 committed by GitHub
parent 7634394664
commit 6e706778fd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 2 additions and 730 deletions

View File

@ -29,7 +29,8 @@ dependencies {
implementation(gradleApi())
implementation(localGroovy())
implementation("io.opentelemetry.instrumentation.muzzle-generation:io.opentelemetry.instrumentation.muzzle-generation.gradle.plugin:0.1.0-SNAPSHOT")
implementation("io.opentelemetry.instrumentation.muzzle-generation:io.opentelemetry.instrumentation.muzzle-generation.gradle.plugin:0.2.0-SNAPSHOT")
implementation("io.opentelemetry.instrumentation.muzzle-check:io.opentelemetry.instrumentation.muzzle-check.gradle.plugin:0.2.0-SNAPSHOT")
implementation("org.eclipse.aether:aether-connector-basic:1.1.0")
implementation("org.eclipse.aether:aether-transport-http:1.1.0")

View File

@ -1,359 +0,0 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
import io.opentelemetry.instrumentation.gradle.muzzle.AcceptableVersions
import io.opentelemetry.instrumentation.gradle.muzzle.BogusClassLoader
import io.opentelemetry.instrumentation.gradle.muzzle.MuzzleDirective
import io.opentelemetry.instrumentation.gradle.muzzle.MuzzleExtension
import org.apache.maven.repository.internal.MavenRepositorySystemUtils
import org.eclipse.aether.RepositorySystem
import org.eclipse.aether.RepositorySystemSession
import org.eclipse.aether.artifact.Artifact
import org.eclipse.aether.artifact.DefaultArtifact
import org.eclipse.aether.connector.basic.BasicRepositoryConnectorFactory
import org.eclipse.aether.repository.LocalRepository
import org.eclipse.aether.repository.RemoteRepository
import org.eclipse.aether.resolution.VersionRangeRequest
import org.eclipse.aether.resolution.VersionRangeResult
import org.eclipse.aether.spi.connector.RepositoryConnectorFactory
import org.eclipse.aether.spi.connector.transport.TransporterFactory
import org.eclipse.aether.transport.http.HttpTransporterFactory
import org.eclipse.aether.version.Version
import java.net.URL
import java.net.URLClassLoader
import java.util.stream.StreamSupport
plugins {
`java-library`
}
// Select a random set of versions to test
val RANGE_COUNT_LIMIT = 10
val muzzleConfig = extensions.create<MuzzleExtension>("muzzle")
val muzzleTooling by configurations.creating {
isCanBeConsumed = false
isCanBeResolved = true
}
val muzzleBootstrap by configurations.creating {
isCanBeConsumed = false
isCanBeResolved = true
}
val compileMuzzle by tasks.registering {
dependsOn(muzzleBootstrap)
dependsOn(muzzleTooling)
dependsOn(tasks.named("classes"))
}
val muzzle by tasks.registering {
group = "Muzzle"
description = "Run instrumentation muzzle on compile time dependencies"
dependsOn(compileMuzzle)
}
tasks.register("printMuzzleReferences") {
group = "Muzzle"
description = "Print references created by instrumentation muzzle"
dependsOn(compileMuzzle)
doLast {
val instrumentationCL = createInstrumentationClassloader()
val assertionMethod = instrumentationCL
.loadClass(
"io.opentelemetry.javaagent.tooling.muzzle.matcher.MuzzleGradlePluginUtil")
.getMethod("printMuzzleReferences", ClassLoader::class.java)
assertionMethod.invoke(null, instrumentationCL)
}
}
val projectRepositories = mutableListOf<RemoteRepository>().apply {
// Manually add mavenCentral until https://github.com/gradle/gradle/issues/17295
// Adding mavenLocal is much more complicated but hopefully isn't required for normal usage of
// Muzzle.
add(
RemoteRepository.Builder(
"MavenCentral", "default", "https://repo.maven.apache.org/maven2/")
.build())
for (repository in repositories) {
if (repository is MavenArtifactRepository) {
add(
RemoteRepository.Builder(
repository.getName(),
"default",
repository.url.toString())
.build())
}
}
}.toList()
val hasRelevantTask = gradle.startParameter.taskNames.any {
// removing leading ':' if present
val taskName = it.removePrefix(":")
val projectPath = project.path.substring(1)
// Either the specific muzzle task in this project or the top level, full-project
// muzzle task.
// Either the specific muzzle task in this project or the top level, full-project
// muzzle task.
taskName == "${projectPath}:muzzle" || taskName == "muzzle"
}
if (hasRelevantTask) {
val system = newRepositorySystem()
val session = newRepositorySystemSession(system)
afterEvaluate {
var runAfter = muzzle
for (muzzleDirective in muzzleConfig.directives.get()) {
logger.info("configured ${muzzleDirective}")
if (muzzleDirective.coreJdk.get()) {
runAfter = addMuzzleTask(muzzleDirective, null, runAfter)
} else {
for (singleVersion in muzzleDirectiveToArtifacts(muzzleDirective, system, session)) {
runAfter = addMuzzleTask(muzzleDirective, singleVersion, runAfter)
}
if (muzzleDirective.assertInverse.get()) {
for (inverseDirective in inverseOf(muzzleDirective, system, session)) {
for (singleVersion in muzzleDirectiveToArtifacts(inverseDirective, system, session)) {
runAfter = addMuzzleTask(inverseDirective, singleVersion, runAfter)
}
}
}
}
}
}
}
fun createInstrumentationClassloader(): ClassLoader {
logger.info("Creating instrumentation classpath for: ${name}")
val runtimeClasspath = sourceSets.main.get().runtimeClasspath
return classpathLoader(runtimeClasspath, createMuzzleCheckLoader())
}
fun classpathLoader(classpath: FileCollection, parent: ClassLoader): ClassLoader {
val urls: Array<URL> = StreamSupport.stream(classpath.spliterator(), false)
.map {
logger.info("--${it}")
it.toURI().toURL()
}
.toArray(::arrayOfNulls)
return URLClassLoader(urls, parent)
}
fun createMuzzleCheckLoader(): ClassLoader {
logger.info("creating classpath for auto-tooling")
return classpathLoader(muzzleTooling, ClassLoader.getPlatformClassLoader())
}
fun newRepositorySystem(): RepositorySystem {
return MavenRepositorySystemUtils.newServiceLocator().apply {
addService(RepositoryConnectorFactory::class.java, BasicRepositoryConnectorFactory::class.java)
addService(TransporterFactory::class.java, HttpTransporterFactory::class.java)
}.run {
getService(RepositorySystem::class.java)
}
}
fun newRepositorySystemSession(system: RepositorySystem): RepositorySystemSession {
val muzzleRepo = file("${buildDir}/muzzleRepo")
val localRepo = LocalRepository(muzzleRepo)
return MavenRepositorySystemUtils.newSession().apply {
setLocalRepositoryManager(system.newLocalRepositoryManager(this, localRepo))
}
}
fun addMuzzleTask(muzzleDirective: MuzzleDirective, versionArtifact: Artifact?, runAfter: TaskProvider<Task>)
: TaskProvider<Task> {
val taskName = if (versionArtifact == null) {
"muzzle-Assert${muzzleDirective}"
} else {
StringBuilder("muzzle-Assert").apply {
if (muzzleDirective.assertPass.get()) {
append("Pass")
} else {
append("Fail")
}
append('-')
.append(versionArtifact.groupId)
.append('-')
.append(versionArtifact.artifactId)
.append('-')
.append(versionArtifact.version)
if (!muzzleDirective.name.get().isEmpty()) {
append(muzzleDirective.nameSlug)
}
}.run { toString() }
}
val config = configurations.create(taskName)
if (versionArtifact != null) {
val dep = (dependencies.create(versionArtifact.run { "${groupId}:${artifactId}:${version}" }) as ModuleDependency).apply {
isTransitive = true
exclude("com.sun.jdmk", "jmxtools")
exclude("com.sun.jmx", "jmxri")
for (excluded in muzzleDirective.excludedDependencies.get()) {
val (group, module) = excluded.split(':')
exclude(group, module)
}
}
config.dependencies.add(dep)
for (additionalDependency in muzzleDirective.additionalDependencies.get()) {
val additional = if (additionalDependency.count { it == ':' } < 2) {
// Dependency definition without version, use the artifact's version.
"${additionalDependency}:${versionArtifact.version}"
} else {
additionalDependency
}
val additionalDep = (dependencies.create(additional) as ModuleDependency).apply {
isTransitive = true
}
config.dependencies.add(additionalDep)
}
}
val muzzleTask = tasks.register(taskName) {
dependsOn(configurations.named("runtimeClasspath"))
doLast {
val instrumentationCL = createInstrumentationClassloader()
val ccl = Thread.currentThread().contextClassLoader
val bogusLoader = BogusClassLoader()
val userCL = createClassLoaderForTask(config)
Thread.currentThread().contextClassLoader = bogusLoader
try {
// find all instrumenters, get muzzle, and assert
val assertionMethod = instrumentationCL
.loadClass("io.opentelemetry.javaagent.tooling.muzzle.matcher.MuzzleGradlePluginUtil")
.getMethod(
"assertInstrumentationMuzzled",
ClassLoader::class.java,
ClassLoader::class.java,
Boolean::class.javaPrimitiveType)
assertionMethod.invoke(
null,
instrumentationCL,
userCL,
muzzleDirective.assertPass.get())
} finally {
Thread.currentThread().contextClassLoader = ccl
}
for (thread in Thread.getAllStackTraces().keys) {
if (thread.contextClassLoader === bogusLoader
|| thread.contextClassLoader === instrumentationCL
|| thread.contextClassLoader === userCL) {
throw GradleException(
"Task ${taskName} has spawned a thread: ${thread} with classloader ${thread.contextClassLoader}. " +
"This will prevent GC of dynamic muzzle classes. Aborting muzzle run.")
}
}
}
}
runAfter.configure { finalizedBy(muzzleTask) }
return muzzleTask
}
fun createClassLoaderForTask(muzzleTaskConfiguration: Configuration): ClassLoader {
val userUrls = objects.fileCollection()
logger.info("Creating task classpath")
userUrls.from(muzzleTaskConfiguration.resolvedConfiguration.files)
return classpathLoader(userUrls.plus(muzzleBootstrap), ClassLoader.getPlatformClassLoader())
}
fun inverseOf(muzzleDirective: MuzzleDirective, system: RepositorySystem, session: RepositorySystemSession): Set<MuzzleDirective> {
val inverseDirectives = mutableSetOf<MuzzleDirective>()
val allVersionsArtifact = DefaultArtifact(
muzzleDirective.group.get(),
muzzleDirective.module.get(),
muzzleDirective.classifier.get(),
"jar",
"[,)")
val directiveArtifact = DefaultArtifact(
muzzleDirective.group.get(),
muzzleDirective.module.get(),
muzzleDirective.classifier.get(),
"jar",
muzzleDirective.versions.get())
val repos = projectRepositories
val allRangeRequest = VersionRangeRequest().apply {
repositories = repos
artifact = allVersionsArtifact
}
val allRangeResult = system.resolveVersionRange(session, allRangeRequest)
val rangeRequest = VersionRangeRequest().apply {
repositories = repos
artifact = directiveArtifact
}
val rangeResult = system.resolveVersionRange(session, rangeRequest)
allRangeResult.versions.removeAll(rangeResult.versions)
for (version in filterVersions(allRangeResult, muzzleDirective.normalizedSkipVersions)) {
val inverseDirective = objects.newInstance(MuzzleDirective::class).apply {
group.set(muzzleDirective.group)
module.set(muzzleDirective.module)
classifier.set(muzzleDirective.classifier)
versions.set(version)
assertPass.set(!muzzleDirective.assertPass.get())
excludedDependencies.set(muzzleDirective.excludedDependencies)
}
inverseDirectives.add(inverseDirective)
}
return inverseDirectives
}
fun filterVersions(range: VersionRangeResult, skipVersions: Set<String>) = sequence {
val predicate = AcceptableVersions(skipVersions)
if (predicate.test(range.lowestVersion)) {
yield(range.lowestVersion.toString())
}
if (predicate.test(range.highestVersion)) {
yield(range.highestVersion.toString())
}
val copy: List<Version> = range.versions.shuffled()
for (version in copy) {
if (predicate.test(version)) {
yield(version.toString())
}
}
}.distinct().take(RANGE_COUNT_LIMIT)
fun muzzleDirectiveToArtifacts(muzzleDirective: MuzzleDirective, system: RepositorySystem, session: RepositorySystemSession) = sequence<Artifact> {
val directiveArtifact: Artifact = DefaultArtifact(
muzzleDirective.group.get(),
muzzleDirective.module.get(),
muzzleDirective.classifier.get(),
"jar",
muzzleDirective.versions.get())
val rangeRequest = VersionRangeRequest().apply {
repositories = projectRepositories
artifact = directiveArtifact
}
val rangeResult = system.resolveVersionRange(session, rangeRequest)
val allVersionArtifacts = filterVersions(rangeResult, muzzleDirective.normalizedSkipVersions)
.map {
DefaultArtifact(
muzzleDirective.group.get(),
muzzleDirective.module.get(),
muzzleDirective.classifier.get(),
"jar",
it)
}
allVersionArtifacts.ifEmpty {
throw GradleException("No muzzle artifacts found for $muzzleDirective")
}
yieldAll(allVersionArtifacts)
}

View File

@ -1,42 +0,0 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.instrumentation.gradle.muzzle
import org.eclipse.aether.version.Version
import java.util.Locale
import java.util.function.Predicate
internal class AcceptableVersions(private val skipVersions: Collection<String>) :
Predicate<Version?> {
override fun test(version: Version?): Boolean {
if (version == null) {
return false
}
val versionString = version.toString().toLowerCase(Locale.ROOT)
if (skipVersions.contains(versionString)) {
return false
}
val draftVersion = versionString.contains("rc")
|| versionString.contains(".cr")
|| versionString.contains("alpha")
|| versionString.contains("beta")
|| versionString.contains("-b")
|| versionString.contains(".m")
|| versionString.contains("-m")
|| versionString.contains("-dev")
|| versionString.contains("-ea")
|| versionString.contains("-atlassian-")
|| versionString.contains("public_draft")
|| versionString.contains("snapshot")
|| GIT_SHA_PATTERN.matches(versionString)
return !draftVersion
}
companion object {
private val GIT_SHA_PATTERN = Regex("^.*-[0-9a-f]{7,}$")
}
}

View File

@ -1,14 +0,0 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.instrumentation.gradle.muzzle
import java.security.SecureClassLoader
internal class BogusClassLoader : SecureClassLoader() {
override fun toString(): String {
return "bogus"
}
}

View File

@ -1,104 +0,0 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.instrumentation.gradle.muzzle
import org.gradle.api.provider.ListProperty
import org.gradle.api.provider.Property
import org.gradle.api.provider.SetProperty
import java.util.stream.Collectors
@Deprecated("Should be removed when we start using external muzzle check plugin")
abstract class MuzzleDirective {
abstract val name: Property<String>
abstract val group: Property<String>
abstract val module: Property<String>
abstract val classifier: Property<String>
abstract val versions: Property<String>
abstract val skipVersions: SetProperty<String>
abstract val additionalDependencies: ListProperty<String>
abstract val excludedDependencies: ListProperty<String>
abstract val assertPass: Property<Boolean>
abstract val assertInverse: Property<Boolean>
internal abstract val coreJdk: Property<Boolean> // use coreJdk() function below to enable
init {
name.convention("")
classifier.convention("")
skipVersions.convention(emptySet())
additionalDependencies.convention(listOf())
excludedDependencies.convention(listOf())
assertPass.convention(false)
assertInverse.convention(false)
coreJdk.convention(false)
}
fun coreJdk() {
coreJdk.set(true)
}
/**
* Adds extra dependencies to the current muzzle test.
*
* @param compileString An extra dependency in the gradle canonical form:
* '<group_id>:<artifact_id>:<version_id>'.
*/
fun extraDependency(compileString: String?) {
additionalDependencies.add(compileString!!)
}
/**
* Adds transitive dependencies to exclude from the current muzzle test.
*
* @param excludeString A dependency in the gradle canonical form: '<group_id>:<artifact_id>'
*/
fun excludeDependency(excludeString: String?) {
excludedDependencies.add(excludeString!!)
}
fun skip(vararg version: String?) {
skipVersions.addAll(*version)
}
internal val nameSlug: String
get() = NORMALIZE_NAME_SLUG.replace(name.get().trim(), "-")
internal val normalizedSkipVersions: Set<String>
get() = skipVersions.getOrElse(setOf()).stream()
.map(String::toLowerCase)
.collect(Collectors.toSet())
override fun toString(): String {
val sb = StringBuilder()
if (coreJdk.getOrElse(false)) {
if (assertPass.getOrElse(false)) {
sb.append("Pass")
} else {
sb.append("Fail")
}
sb.append("-core-jdk")
} else {
if (assertPass.getOrElse(false)) {
sb.append("pass")
} else {
sb.append("fail")
}
sb.append(group.get())
.append(':')
.append(module.get())
.append(':')
.append(versions.get())
if (classifier.isPresent) {
sb.append(':').append(classifier.get())
}
}
return sb.toString()
}
companion object {
private val NORMALIZE_NAME_SLUG = Regex("[^a-zA-Z0-9]+")
}
}

View File

@ -1,31 +0,0 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.instrumentation.gradle.muzzle
import org.gradle.api.Action
import org.gradle.api.model.ObjectFactory
import org.gradle.api.provider.ListProperty
import javax.inject.Inject
@Deprecated("Should be removed when we start using external muzzle check plugin")
abstract class MuzzleExtension @Inject constructor(private val objectFactory: ObjectFactory) {
internal abstract val directives: ListProperty<MuzzleDirective>
fun pass(action: Action<MuzzleDirective>) {
val pass = objectFactory.newInstance(MuzzleDirective::class.java)
action.execute(pass)
pass.assertPass.set(true)
directives.add(pass)
}
fun fail(action: Action<MuzzleDirective>) {
val fail = objectFactory.newInstance(MuzzleDirective::class.java)
action.execute(fail)
fail.assertPass.set(false)
directives.add(fail)
}
}

View File

@ -1,42 +0,0 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.instrumentation.gradle.muzzle;
import static org.assertj.core.api.Assertions.assertThat;
import java.util.Collections;
import org.eclipse.aether.version.Version;
import org.junit.jupiter.api.Test;
class MuzzlePluginTest {
@Test
void rangeRequest() {
AcceptableVersions predicate = new AcceptableVersions(Collections.emptyList());
assertThat(predicate.test(new TestVersion("10.1.0-rc2+19-8e20bb26"))).isFalse();
assertThat(predicate.test(new TestVersion("2.4.5.BUILD-SNAPSHOT"))).isFalse();
}
static class TestVersion implements Version {
private final String version;
TestVersion(String version) {
this.version = version;
}
@Override
public int compareTo(Version o) {
return toString().compareTo(o.toString());
}
@Override
public String toString() {
return version;
}
}
}

View File

@ -1,137 +0,0 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.javaagent.tooling.muzzle.matcher;
import static java.lang.System.lineSeparator;
import io.opentelemetry.javaagent.extension.instrumentation.InstrumentationModule;
import io.opentelemetry.javaagent.extension.muzzle.ClassRef;
import io.opentelemetry.javaagent.extension.muzzle.FieldRef;
import io.opentelemetry.javaagent.extension.muzzle.MethodRef;
import io.opentelemetry.javaagent.extension.muzzle.Source;
import io.opentelemetry.javaagent.tooling.muzzle.ClassLoaderMatcher;
import io.opentelemetry.javaagent.tooling.muzzle.Mismatch;
import io.opentelemetry.javaagent.tooling.muzzle.ReferenceMatcher;
import java.util.List;
import java.util.Map;
import java.util.ServiceLoader;
/** Entry point for the muzzle gradle plugin. */
// Runs in special classloader so tedious to provide access to the Gradle logger.
@SuppressWarnings("SystemOut")
@Deprecated
public final class MuzzleGradlePluginUtil {
private static final String INDENT = " ";
/**
* Verifies that all instrumentations present in the {@code agentClassLoader} can be safely
* applied to the passed {@code userClassLoader}.
*
* <p>This method throws whenever one of the following step fails (and {@code assertPass} is
* true):
*
* <ol>
* <li>{@code userClassLoader} is not matched by the {@link
* InstrumentationModule#classLoaderMatcher()} method
* <li>{@link ReferenceMatcher} of any instrumentation module finds any mismatch
* <li>any helper class defined in {@link InstrumentationModule#getMuzzleHelperClassNames()}
* fails to be injected into {@code userClassLoader}
* </ol>
*
* <p>When {@code assertPass = false} this method behaves in an opposite way: failure in any of
* the first two steps is expected (helper classes are not injected at all).
*
* <p>This method is repeatedly called by the {@code :muzzle} gradle task - each tested dependency
* version passes different {@code userClassLoader}.
*/
public static void assertInstrumentationMuzzled(
ClassLoader agentClassLoader, ClassLoader userClassLoader, boolean assertPass) {
ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
Thread.currentThread().setContextClassLoader(agentClassLoader);
Map<String, List<Mismatch>> allMismatches;
try {
allMismatches = ClassLoaderMatcher.matchesAll(userClassLoader, assertPass);
allMismatches.forEach(
(moduleName, mismatches) -> {
boolean passed = mismatches.isEmpty();
if (passed && !assertPass) {
System.err.println("MUZZLE PASSED " + moduleName + " BUT FAILURE WAS EXPECTED");
throw new IllegalStateException(
"Instrumentation unexpectedly passed Muzzle validation");
} else if (!passed && assertPass) {
System.err.println("FAILED MUZZLE VALIDATION: " + moduleName + " mismatches:");
for (Mismatch mismatch : mismatches) {
System.err.println("-- " + mismatch);
}
throw new IllegalStateException("Instrumentation failed Muzzle validation");
}
});
} finally {
Thread.currentThread().setContextClassLoader(contextClassLoader);
}
int validatedModulesCount = allMismatches.size();
if (validatedModulesCount == 0) {
String errorMessage = "Did not found any InstrumentationModule to validate!";
System.err.println(errorMessage);
throw new IllegalStateException(errorMessage);
}
}
/**
* Prints all references from all instrumentation modules present in the passed {@code
* instrumentationClassLoader}.
*
* <p>Called by the {@code printMuzzleReferences} gradle task.
*/
public static void printMuzzleReferences(ClassLoader instrumentationClassLoader) {
for (InstrumentationModule instrumentationModule :
ServiceLoader.load(InstrumentationModule.class, instrumentationClassLoader)) {
try {
System.out.println(instrumentationModule.getClass().getName());
for (ClassRef ref : instrumentationModule.getMuzzleReferences().values()) {
System.out.print(prettyPrint(ref));
}
} catch (RuntimeException e) {
String message =
"Unexpected exception printing references for "
+ instrumentationModule.getClass().getName();
System.out.println(message);
throw new IllegalStateException(message, e);
}
}
}
private static String prettyPrint(ClassRef ref) {
StringBuilder builder = new StringBuilder(INDENT).append(ref).append(lineSeparator());
if (!ref.getSources().isEmpty()) {
builder.append(INDENT).append(INDENT).append("Sources:").append(lineSeparator());
for (Source source : ref.getSources()) {
builder
.append(INDENT)
.append(INDENT)
.append(INDENT)
.append("at: ")
.append(source)
.append(lineSeparator());
}
}
for (FieldRef field : ref.getFields()) {
builder.append(INDENT).append(INDENT).append(field).append(lineSeparator());
}
for (MethodRef method : ref.getMethods()) {
builder.append(INDENT).append(INDENT).append(method).append(lineSeparator());
}
return builder.toString();
}
private MuzzleGradlePluginUtil() {}
}