Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Start migrating Muzzle plugin to Java #2996

Merged
merged 1 commit into from
May 15, 2021
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
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