Skip to content

Commit

Permalink
Start migrating Muzzle plugin to Java (#2996)
Browse files Browse the repository at this point in the history
  • Loading branch information
Anuraag Agrawal authored May 15, 2021
1 parent fe41885 commit 95c16c4
Show file tree
Hide file tree
Showing 21 changed files with 181 additions and 141 deletions.
134 changes: 19 additions & 115 deletions buildSrc/src/main/groovy/MuzzlePlugin.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,13 @@
* SPDX-License-Identifier: Apache-2.0
*/

import io.opentelemetry.instrumentation.gradle.muzzle.MuzzleDirective
import io.opentelemetry.instrumentation.gradle.muzzle.MuzzleExtension
import java.lang.reflect.Method
import java.security.SecureClassLoader
import java.util.concurrent.atomic.AtomicReference
import java.util.function.Predicate
import java.util.regex.Pattern
import javax.inject.Inject
import org.apache.maven.repository.internal.MavenRepositorySystemUtils
import org.eclipse.aether.DefaultRepositorySystemSession
import org.eclipse.aether.RepositorySystem
Expand All @@ -25,12 +26,11 @@ 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 org.gradle.api.Action
import org.gradle.api.GradleException
import org.gradle.api.Plugin
import org.gradle.api.Project
import org.gradle.api.model.ObjectFactory
import org.gradle.api.tasks.TaskProvider

/**
* muzzle task plugin which runs muzzle validation against a range of dependencies.
*/
Expand Down Expand Up @@ -90,16 +90,16 @@ class MuzzlePlugin implements Plugin<Project> {
// use runAfter to set up task finalizers in version order
TaskProvider runAfter = muzzle

for (MuzzleDirective muzzleDirective : project.muzzle.directives) {
for (MuzzleDirective muzzleDirective : project.muzzle.directives.get()) {
project.getLogger().info("configured $muzzleDirective")

if (muzzleDirective.coreJdk) {
if (muzzleDirective.coreJdk.get()) {
runAfter = addMuzzleTask(muzzleDirective, null, project, runAfter)
} else {
muzzleDirectiveToArtifacts(project, muzzleDirective, system, session).collect() { Artifact singleVersion ->
runAfter = addMuzzleTask(muzzleDirective, singleVersion, project, runAfter)
}
if (muzzleDirective.assertInverse) {
if (muzzleDirective.assertInverse.get()) {
inverseOf(project, muzzleDirective, system, session).collect() { MuzzleDirective inverseDirective ->
muzzleDirectiveToArtifacts(project, inverseDirective, system, session).collect() { Artifact singleVersion ->
runAfter = addMuzzleTask(inverseDirective, singleVersion, project, runAfter)
Expand Down Expand Up @@ -184,19 +184,19 @@ class MuzzlePlugin implements Plugin<Project> {
* Convert a muzzle directive to a list of artifacts
*/
private static Set<Artifact> muzzleDirectiveToArtifacts(Project instrumentationProject, MuzzleDirective muzzleDirective, RepositorySystem system, RepositorySystemSession session) {
Artifact directiveArtifact = new DefaultArtifact(muzzleDirective.group, muzzleDirective.module, "jar", muzzleDirective.versions)
Artifact directiveArtifact = new DefaultArtifact(muzzleDirective.group.get(), muzzleDirective.module.get(), "jar", muzzleDirective.versions.get())

VersionRangeRequest rangeRequest = new VersionRangeRequest()
rangeRequest.setRepositories(getProjectRepositories(instrumentationProject))
rangeRequest.setArtifact(directiveArtifact)
VersionRangeResult rangeResult = system.resolveVersionRange(session, rangeRequest)

Set<Artifact> allVersionArtifacts = filterVersions(rangeResult, muzzleDirective.skipVersions).collect { version ->
new DefaultArtifact(muzzleDirective.group, muzzleDirective.module, "jar", version)
Set<Artifact> allVersionArtifacts = filterVersions(rangeResult, muzzleDirective.normalizedSkipVersions).collect { version ->
new DefaultArtifact(muzzleDirective.group.get(), muzzleDirective.module.get(), "jar", version)
}.toSet()

if (allVersionArtifacts.isEmpty()) {
throw new GradleException("No muzzle artifacts found for $muzzleDirective.group:$muzzleDirective.module $muzzleDirective.versions")
throw new GradleException("No muzzle artifacts found for $muzzleDirective")
}

return allVersionArtifacts
Expand All @@ -214,8 +214,8 @@ class MuzzlePlugin implements Plugin<Project> {
private static Set<MuzzleDirective> inverseOf(Project instrumentationProject, MuzzleDirective muzzleDirective, RepositorySystem system, RepositorySystemSession session) {
Set<MuzzleDirective> inverseDirectives = new HashSet<>()

Artifact allVersionsArtifact = new DefaultArtifact(muzzleDirective.group, muzzleDirective.module, "jar", "[,)")
Artifact directiveArtifact = new DefaultArtifact(muzzleDirective.group, muzzleDirective.module, "jar", muzzleDirective.versions)
Artifact allVersionsArtifact = new DefaultArtifact(muzzleDirective.group.get(), muzzleDirective.module.get(), "jar", "[,)")
Artifact directiveArtifact = new DefaultArtifact(muzzleDirective.group.get(), muzzleDirective.module.get(), "jar", muzzleDirective.versions.get())

List<RemoteRepository> repos = getProjectRepositories(instrumentationProject)
VersionRangeRequest allRangeRequest = new VersionRangeRequest()
Expand All @@ -230,8 +230,8 @@ class MuzzlePlugin implements Plugin<Project> {

allRangeResult.getVersions().removeAll(rangeResult.getVersions())

filterVersions(allRangeResult, muzzleDirective.skipVersions).each { version ->
MuzzleDirective inverseDirective = new MuzzleDirective()
filterVersions(allRangeResult, muzzleDirective.normalizedSkipVersions).each { version ->
MuzzleDirective inverseDirective = instrumentationProject.objects.newInstance(MuzzleDirective)
inverseDirective.group = muzzleDirective.group
inverseDirective.module = muzzleDirective.module
inverseDirective.versions = version
Expand Down Expand Up @@ -316,14 +316,14 @@ class MuzzlePlugin implements Plugin<Project> {
*/
private static TaskProvider addMuzzleTask(MuzzleDirective muzzleDirective, Artifact versionArtifact, Project instrumentationProject, TaskProvider runAfter) {
def taskName
if (muzzleDirective.coreJdk) {
if (muzzleDirective.coreJdk.get()) {
taskName = "muzzle-Assert$muzzleDirective"
} else {
taskName = "muzzle-Assert${muzzleDirective.assertPass ? "Pass" : "Fail"}-$versionArtifact.groupId-$versionArtifact.artifactId-$versionArtifact.version${muzzleDirective.name ? "-${muzzleDirective.getNameSlug()}" : ""}"
taskName = "muzzle-Assert${muzzleDirective.assertPass ? "Pass" : "Fail"}-$versionArtifact.groupId-$versionArtifact.artifactId-$versionArtifact.version${!muzzleDirective.name.get().isEmpty() ? "-${muzzleDirective.getNameSlug()}" : ""}"
}
def config = instrumentationProject.configurations.create(taskName)

if (!muzzleDirective.coreJdk) {
if (!muzzleDirective.coreJdk.get()) {
def dep = instrumentationProject.dependencies.create("$versionArtifact.groupId:$versionArtifact.artifactId:$versionArtifact.version") {
transitive = true
}
Expand All @@ -334,7 +334,7 @@ class MuzzlePlugin implements Plugin<Project> {

config.dependencies.add(dep)
}
for (String additionalDependency : muzzleDirective.additionalDependencies) {
for (String additionalDependency : muzzleDirective.additionalDependencies.get()) {
if (additionalDependency.count(":") < 2) {
// Dependency definition without version, use the artifact's version.
additionalDependency += ":${versionArtifact.version}"
Expand Down Expand Up @@ -362,7 +362,7 @@ class MuzzlePlugin implements Plugin<Project> {
// find all instrumenters, get muzzle, and assert
Method assertionMethod = instrumentationCL.loadClass('io.opentelemetry.javaagent.tooling.muzzle.matcher.MuzzleGradlePluginUtil')
.getMethod('assertInstrumentationMuzzled', ClassLoader.class, ClassLoader.class, boolean.class)
assertionMethod.invoke(null, instrumentationCL, userCL, muzzleDirective.assertPass)
assertionMethod.invoke(null, instrumentationCL, userCL, muzzleDirective.assertPass.get())
} finally {
Thread.currentThread().contextClassLoader = ccl
}
Expand Down Expand Up @@ -406,99 +406,3 @@ class MuzzlePlugin implements Plugin<Project> {
return session
}
}

// plugin extension classes

/**
* A pass or fail directive for a single dependency.
*/
class MuzzleDirective {

/**
* Name is optional and is used to further define the scope of a directive. The motivation for this is that this
* plugin creates a config for each of the dependencies under test with name '...-<group_id>-<artifact_id>-<version>'.
* The problem is that if we want to test multiple times the same configuration under different conditions, e.g.
* with different extra dependencies, the plugin would throw an error as it would try to create several times the
* same config. This property can be used to differentiate those config names for different directives.
*/
String name

String group
String module
String versions
Set<String> skipVersions = new HashSet<>()
List<String> additionalDependencies = new ArrayList<>()
boolean assertPass
boolean assertInverse = false
boolean coreJdk = false

void coreJdk() {
coreJdk = 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>'.
*/
void extraDependency(String compileString) {
additionalDependencies.add(compileString)
}

/**
* Slug of directive name.
*
* @return A slug of the name or an empty string if name is empty. E.g. 'My Directive' --> 'My-Directive'
*/
String getNameSlug() {
if (null == name) {
return ""
}

return name.trim().replaceAll("[^a-zA-Z0-9]+", "-")
}

String toString() {
if (coreJdk) {
return "${assertPass ? 'Pass' : 'Fail'}-core-jdk"
} else {
return "${assertPass ? 'pass' : 'fail'} $group:$module:$versions"
}
}
}

/**
* Muzzle extension containing all pass and fail directives.
*/
class MuzzleExtension {
final List<MuzzleDirective> directives = new ArrayList<>()
private final ObjectFactory objectFactory

@Inject
MuzzleExtension(final ObjectFactory objectFactory) {
this.objectFactory = objectFactory
}

void pass(Action<? super MuzzleDirective> action) {
MuzzleDirective pass = objectFactory.newInstance(MuzzleDirective)
action.execute(pass)
postConstruct(pass)
pass.assertPass = true
directives.add(pass)
}

void fail(Action<? super MuzzleDirective> action) {
MuzzleDirective fail = objectFactory.newInstance(MuzzleDirective)
action.execute(fail)
postConstruct(fail)
fail.assertPass = false
directives.add(fail)
}

private postConstruct(MuzzleDirective directive) {
// Make skipVersions case insensitive.
directive.skipVersions = directive.skipVersions.collect {
it.toLowerCase()
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.instrumentation.gradle.muzzle;

import java.util.Collections;
import java.util.Set;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import org.gradle.api.provider.ListProperty;
import org.gradle.api.provider.Property;
import org.gradle.api.provider.SetProperty;

public abstract class MuzzleDirective {

private static final Pattern NORMALIZE_NAME_SLUG = Pattern.compile("[^a-zA-Z0-9]+");

public MuzzleDirective() {
getName().convention("");
getSkipVersions().convention(Collections.emptySet());
getAdditionalDependencies().convention(Collections.emptyList());
getAssertPass().convention(false);
getAssertInverse().convention(false);
getCoreJdk().convention(false);
}

public abstract Property<String> getName();

public abstract Property<String> getGroup();

public abstract Property<String> getModule();

public abstract Property<String> getVersions();

public abstract SetProperty<String> getSkipVersions();

public abstract ListProperty<String> getAdditionalDependencies();

public abstract Property<Boolean> getAssertPass();

public abstract Property<Boolean> getAssertInverse();

public abstract Property<Boolean> getCoreJdk();

public void coreJdk() {
getCoreJdk().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>'.
*/
public void extraDependency(String compileString) {
getAdditionalDependencies().add(compileString);
}

public void skip(String... version) {
getSkipVersions().addAll(version);
}

@Override
public String toString() {
StringBuilder sb = new StringBuilder();
if (getCoreJdk().getOrElse(false)) {
if (getAssertPass().getOrElse(false)) {
sb.append("Pass");
} else {
sb.append("Fail");
}
sb.append("-core-jdk");
} else {
if (getAssertPass().getOrElse(false)) {
sb.append("pass");
} else {
sb.append("fail");
}
sb.append(getGroup().get())
.append(':')
.append(getModule().get())
.append(':')
.append(getVersions().get());
}
return sb.toString();
}

String getNameSlug() {
return NORMALIZE_NAME_SLUG.matcher(getName().get().trim()).replaceAll("-");
}

Set<String> getNormalizedSkipVersions() {
return getSkipVersions().getOrElse(Collections.emptySet()).stream()
.map(String::toLowerCase)
.collect(Collectors.toSet());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.instrumentation.gradle.muzzle;

import javax.inject.Inject;
import org.gradle.api.Action;
import org.gradle.api.model.ObjectFactory;
import org.gradle.api.provider.ListProperty;

public abstract class MuzzleExtension {

private final ObjectFactory objectFactory;

@Inject
public MuzzleExtension(ObjectFactory objectFactory) {
this.objectFactory = objectFactory;
}

public abstract ListProperty<MuzzleDirective> getDirectives();

public void pass(Action<? super MuzzleDirective> action) {
MuzzleDirective pass = objectFactory.newInstance(MuzzleDirective.class);
action.execute(pass);
pass.getAssertPass().set(true);
getDirectives().add(pass);
}

public void fail(Action<? super MuzzleDirective> action) {
MuzzleDirective fail = objectFactory.newInstance(MuzzleDirective.class);
action.execute(fail);
fail.getAssertPass().set(false);
getDirectives().add(fail);
}
}
6 changes: 3 additions & 3 deletions docs/contributing/muzzle.md
Original file line number Diff line number Diff line change
Expand Up @@ -98,16 +98,16 @@ muzzle {
// versions from this range are checked
versions = "[,4.0)"
// this version is not checked by muzzle
skipVersions += '3.1-jenkins-1'
skip('3.1-jenkins-1')
}
// it is expected that muzzle passes the runtime check for this component
pass {
group = 'org.springframework'
module = 'spring-webmvc'
versions = "[3.1.0.RELEASE,]"
// except these versions
skipVersions += ['1.2.1', '1.2.2', '1.2.3', '1.2.4']
skipVersions += '3.2.1.RELEASE'
skip('1.2.1', '1.2.2', '1.2.3', '1.2.4')
skip('3.2.1.RELEASE')
// this dependency will be added to the classpath when muzzle check is run
extraDependency "javax.servlet:javax.servlet-api:3.0.1"
// verify that all other versions - [,3.1.0.RELEASE) in this case - fail the muzzle runtime check
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ muzzle {
module = 'java-client'
versions = "[2.6.0,3)"
// these versions were released as ".bundle" instead of ".jar"
skipVersions += ['2.7.5', '2.7.8']
skip('2.7.5', '2.7.8')
assertInverse = true
}
fail {
Expand Down
Loading

0 comments on commit 95c16c4

Please sign in to comment.