SLING-8594 - Create an API Jar analyser that checks that it's
transitively closed
initial checkin
diff --git a/src/main/java/org/apache/sling/feature/analyser/task/impl/CheckJDeps.java b/src/main/java/org/apache/sling/feature/analyser/task/impl/CheckJDeps.java
new file mode 100644
index 0000000..d920117
--- /dev/null
+++ b/src/main/java/org/apache/sling/feature/analyser/task/impl/CheckJDeps.java
@@ -0,0 +1,216 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with this
+ * work for additional information regarding copyright ownership. The ASF
+ * licenses this file to You 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 org.apache.sling.feature.analyser.task.impl;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.util.Optional;
+
+import org.apache.commons.lang3.SystemUtils;
+import org.apache.sling.feature.ArtifactId;
+import org.apache.sling.feature.analyser.task.AnalyserTaskContext;
+
+public class CheckJDeps extends AbstractApiRegionsAnalyserTask {
+
+ private static final String CLASSIFIER_APIS = "apis";
+
+ private static final String DEP_NOT_FOUND_TOKEN = "not found";
+
+ private static final String JDEPS_CMD = "jdeps";
+
+ private static final String PARAMETER_APIS_JARS_DIR = "apis-jars-dir";
+
+ private static final String PARAMETER_OWNED_PACKAGES = "owned-packages";
+
+ private static final String PARAMETER_EXCEPTION_PACKAGES = "exception-packages";
+
+ private static final String PARAMETER_SYSTEM_PACKAGES = "system-packages";
+
+ @Override
+ protected void execute(ApiRegions apiRegions, AnalyserTaskContext ctx) throws Exception {
+ File jDepsExe;
+ try {
+ jDepsExe = getJDepsExecutable();
+ } catch (Exception e) {
+ ctx.reportError(e.getMessage());
+ return;
+ }
+
+ Optional<String> apisJarDirLocation = getConfigurationParameterValue(PARAMETER_APIS_JARS_DIR, ctx);
+ if (!apisJarDirLocation.isPresent()) {
+ ctx.reportWarning("No apis-jars directory specified, skipping current analyser execution");
+ return;
+ }
+
+ File apisJarDir = new File(apisJarDirLocation.get());
+ if (!apisJarDir.exists() || !apisJarDir.isDirectory()) {
+ ctx.reportWarning("apis-jars directory "
+ + apisJarDir
+ + " does not exist or it is not a valid directory, skipping current analyser execution");
+ return;
+ }
+
+ String[] ownedPackages = getConfigurationParameterValues(PARAMETER_OWNED_PACKAGES, ctx);
+ String[] exceptionPackages = getConfigurationParameterValues(PARAMETER_EXCEPTION_PACKAGES, ctx);
+ String[] systemPackages = getConfigurationParameterValues(PARAMETER_SYSTEM_PACKAGES, ctx);
+
+ for (String apiRegion : apiRegions.getRegions()) {
+ execute(jDepsExe, apisJarDir, apiRegion, ownedPackages, exceptionPackages, systemPackages, ctx);
+ }
+ }
+
+ private void execute(File jDepsExe,
+ File apisJarDir,
+ String apiRegion,
+ String[] ownedPackages,
+ String[] exceptionPackages,
+ String[] systemPackages,
+ AnalyserTaskContext ctx) throws Exception {
+ ArtifactId featureId = ctx.getFeature().getId();
+
+ // classifier is built according to ApisJarMojo
+
+ StringBuilder classifierBuilder = new StringBuilder();
+ if (featureId.getClassifier() != null) {
+ classifierBuilder.append(featureId.getClassifier())
+ .append('-');
+ }
+ String finalClassifier = classifierBuilder.append(apiRegion)
+ .append('-')
+ .append(CLASSIFIER_APIS)
+ .toString();
+
+ String targetName = String.format("%s-%s-%s.jar", featureId.getArtifactId(), featureId.getVersion(), finalClassifier);
+ File apisJar = new File(apisJarDir, targetName);
+
+ if (!apisJar.exists() || !apisJar.isFile()) {
+ ctx.reportWarning("apis-jar file "
+ + apisJar
+ + " does not exist or it is not a valid file, skipping current region '"
+ + apiRegion
+ + "'analyser execution");
+ return;
+ }
+
+ String[] command = { jDepsExe.getAbsolutePath(), "-apionly", "-verbose", apisJar.getAbsolutePath() };
+ ProcessBuilder pb = new ProcessBuilder(command);
+ Process process = pb.start();
+
+ InputStream is = process.getInputStream();
+ InputStreamReader isr = new InputStreamReader(is);
+ BufferedReader br = new BufferedReader(isr);
+
+ String line;
+ while ((line = br.readLine()) != null) {
+ line = line.trim();
+
+ if (line.contains(DEP_NOT_FOUND_TOKEN)
+ && isAbout(line, ownedPackages)
+ && !isAbout(line, exceptionPackages)
+ && !isAbout(line, systemPackages)) {
+ line = line.replaceAll(DEP_NOT_FOUND_TOKEN, "");
+ ctx.reportError(line);
+ }
+ }
+
+ int exitValue = process.waitFor();
+ if (exitValue != 0) {
+ ctx.reportError(JDEPS_CMD + " terminated with code " + exitValue);
+ }
+ }
+
+ private static File getJDepsExecutable() throws Exception {
+ String jDepsCommand = JDEPS_CMD + (SystemUtils.IS_OS_WINDOWS ? ".exe" : "");
+
+ File jDepsExe;
+
+ // For IBM's JDK 1.2
+ if (SystemUtils.IS_OS_AIX) {
+ jDepsExe = getFile(SystemUtils.getJavaHome(), "..", "sh", jDepsCommand);
+ } else {
+ jDepsExe = getFile(SystemUtils.getJavaHome(), "..", "bin", jDepsCommand);
+ }
+
+ // ----------------------------------------------------------------------
+ // Try to find jdeps exe from JAVA_HOME environment variable
+ // ----------------------------------------------------------------------
+ if (!jDepsExe.exists() || !jDepsExe.isFile()) {
+ String javaHome = System.getenv().get("JAVA_HOME");
+ if (javaHome == null || javaHome.isEmpty()) {
+ throw new Exception("The environment variable JAVA_HOME is not correctly set.");
+ }
+
+ File javaHomeDir = new File(javaHome);
+ if ((!javaHomeDir.exists()) || javaHomeDir.isFile()) {
+ throw new Exception("The environment variable JAVA_HOME="
+ + javaHome
+ + " does not exist or is not a valid directory.");
+ }
+
+ jDepsExe = getFile(javaHomeDir, "bin", jDepsCommand);
+ if (!jDepsExe.exists() || !jDepsExe.isFile()) {
+ throw new Exception("The jdeps executable '"
+ + jDepsExe
+ + "' doesn't exist or is not a file. Verify the JAVA_HOME environment variable.");
+ }
+ }
+
+ return jDepsExe;
+ }
+
+ private static Optional<String> getConfigurationParameterValue(String key, AnalyserTaskContext ctx) {
+ String value = ctx.getConfiguration().get(key);
+
+ if (value == null || value.isEmpty()) {
+ ctx.reportWarning("Configuration parameter '" + key + "' is missing.");
+ return Optional.empty();
+ }
+
+ return Optional.of(value);
+ }
+
+ private static String[] getConfigurationParameterValues(String key, AnalyserTaskContext ctx) {
+ Optional<String> value = getConfigurationParameterValue(key, ctx);
+
+ if (!value.isPresent()) {
+ return new String[] {};
+ }
+
+ return value.get().split(",");
+ }
+
+ private static boolean isAbout(String line, String[] packages) {
+ for (String adobePackage : packages) {
+ // adding . to avoid cases with packages that have the same prefixes
+ if (line.startsWith(adobePackage + ".")) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private static File getFile(File parent, String... path) {
+ File tmp = parent;
+ for (String current : path) {
+ tmp = new File(tmp, current);
+ }
+ return tmp;
+ }
+
+}
diff --git a/src/main/resources/META-INF/services/org.apache.sling.feature.analyser.task.AnalyserTask b/src/main/resources/META-INF/services/org.apache.sling.feature.analyser.task.AnalyserTask
index 42c29dd..59271cb 100644
--- a/src/main/resources/META-INF/services/org.apache.sling.feature.analyser.task.AnalyserTask
+++ b/src/main/resources/META-INF/services/org.apache.sling.feature.analyser.task.AnalyserTask
@@ -8,3 +8,4 @@
org.apache.sling.feature.analyser.task.impl.CheckApiRegionsDuplicates
org.apache.sling.feature.analyser.task.impl.CheckApiRegionsOrder
org.apache.sling.feature.analyser.task.impl.CheckContentPackagesDependencies
+org.apache.sling.feature.analyser.task.impl.CheckJDeps
diff --git a/src/test/java/org/apache/sling/feature/analyser/task/impl/CheckJDepsTest.java b/src/test/java/org/apache/sling/feature/analyser/task/impl/CheckJDepsTest.java
new file mode 100644
index 0000000..4d8aaee
--- /dev/null
+++ b/src/test/java/org/apache/sling/feature/analyser/task/impl/CheckJDepsTest.java
@@ -0,0 +1,111 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with this
+ * work for additional information regarding copyright ownership. The ASF
+ * licenses this file to You 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 org.apache.sling.feature.analyser.task.impl;
+
+import static org.junit.Assert.*;
+
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import java.io.File;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.commons.io.FileUtils;
+import org.apache.sling.feature.Artifact;
+import org.apache.sling.feature.ArtifactId;
+import org.apache.sling.feature.Extension;
+import org.apache.sling.feature.ExtensionType;
+import org.apache.sling.feature.Feature;
+import org.apache.sling.feature.analyser.task.AnalyserTaskContext;
+import org.junit.Test;
+
+public class CheckJDepsTest {
+
+ @Test
+ public void jDepsApisJarExecution() throws Exception {
+ AnalyserTaskContext ctx = mock(AnalyserTaskContext.class);
+
+ List<String> warnings = new LinkedList<>();
+ doAnswer(invocation -> {
+ String error = invocation.getArgument(0);
+ warnings.add(error);
+ return null;
+ }).when(ctx).reportWarning(anyString());
+
+ List<String> errors = new LinkedList<>();
+ doAnswer(invocation -> {
+ String error = invocation.getArgument(0);
+ errors.add(error);
+ return null;
+ }).when(ctx).reportError(anyString());
+
+ // setup the testing feature
+
+ Feature testFeature = new Feature(ArtifactId.parse("org.apache.sling:slingfeature-maven-plugin-test:1.0.0-SNAPSHOT"));
+
+ for (String bundleId : new String[] {
+ "org.apache.felix:org.apache.felix.inventory:1.0.6",
+ "org.apache.felix:org.apache.felix.metatype:1.2.2",
+ "org.apache.felix:org.apache.felix.scr:2.1.14"
+ }) {
+ testFeature.getBundles().add(new Artifact(ArtifactId.parse(bundleId)));
+ }
+
+ Extension apiRegionsExtension = new Extension(ExtensionType.JSON, "api-regions", false);
+ apiRegionsExtension.setJSON("[\n" +
+ " {\n" +
+ " \"name\": \"base\",\n" +
+ " \"exports\": [\n" +
+ " \"org.apache.felix.inventory\",\n" +
+ " \"org.apache.felix.metatype\"\n" +
+ " ]\n" +
+ " },\n" +
+ " {\n" +
+ " \"name\": \"extended\",\n" +
+ " \"exports\": [\n" +
+ " \"org.apache.felix.scr.component\",\n" +
+ " \"org.apache.felix.scr.info\"\n" +
+ " ]\n" +
+ " }\n" +
+ " ]");
+ testFeature.getExtensions().add(apiRegionsExtension);
+
+ when(ctx.getFeature()).thenReturn(testFeature);
+
+ // setup the configurations parameters
+
+ Map<String,String> configuration = new HashMap<>();
+ File apisJarDir = FileUtils.toFile(getClass().getClassLoader().getResource("jdeps"));
+ configuration.put("apis-jars-dir", apisJarDir.getAbsolutePath());
+
+ when(ctx.getConfiguration()).thenReturn(configuration);
+
+ // execute the jdeps check
+
+ CheckJDeps jDepsAnalyser = new CheckJDeps();
+ jDepsAnalyser.execute(ctx);
+
+ assertFalse(warnings.isEmpty());
+ assertTrue(errors.isEmpty());
+ }
+
+}
diff --git a/src/test/resources/jdeps/slingfeature-maven-plugin-test-1.0.0-SNAPSHOT-base-apis.jar b/src/test/resources/jdeps/slingfeature-maven-plugin-test-1.0.0-SNAPSHOT-base-apis.jar
new file mode 100644
index 0000000..c75074f
--- /dev/null
+++ b/src/test/resources/jdeps/slingfeature-maven-plugin-test-1.0.0-SNAPSHOT-base-apis.jar
Binary files differ
diff --git a/src/test/resources/jdeps/slingfeature-maven-plugin-test-1.0.0-SNAPSHOT-extended-apis.jar b/src/test/resources/jdeps/slingfeature-maven-plugin-test-1.0.0-SNAPSHOT-extended-apis.jar
new file mode 100644
index 0000000..f6d654e
--- /dev/null
+++ b/src/test/resources/jdeps/slingfeature-maven-plugin-test-1.0.0-SNAPSHOT-extended-apis.jar
Binary files differ