You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sling.apache.org by si...@apache.org on 2019/07/22 14:39:37 UTC

[sling-org-apache-sling-feature-analyser] 01/01: SLING-8594 - Create an API Jar analyser that checks that it's transitively closed

This is an automated email from the ASF dual-hosted git repository.

simonetripodi pushed a commit to branch SLING-8594
in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-feature-analyser.git

commit a5e78d548e3e3438bc636891e3ad8a449a4fe799
Author: Simo Tripodi <st...@adobe.com>
AuthorDate: Mon Jul 22 16:39:22 2019 +0200

    SLING-8594 - Create an API Jar analyser that checks that it's
    transitively closed
    
    initial checkin
---
 .../feature/analyser/task/impl/CheckJDeps.java     | 216 +++++++++++++++++++++
 ...apache.sling.feature.analyser.task.AnalyserTask |   1 +
 .../feature/analyser/task/impl/CheckJDepsTest.java | 111 +++++++++++
 ...-maven-plugin-test-1.0.0-SNAPSHOT-base-apis.jar | Bin 0 -> 27323 bytes
 ...en-plugin-test-1.0.0-SNAPSHOT-extended-apis.jar | Bin 0 -> 28981 bytes
 5 files changed, 328 insertions(+)

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.CheckApiRegionsDependencies
 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
Binary files /dev/null and b/src/test/resources/jdeps/slingfeature-maven-plugin-test-1.0.0-SNAPSHOT-base-apis.jar 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
Binary files /dev/null and b/src/test/resources/jdeps/slingfeature-maven-plugin-test-1.0.0-SNAPSHOT-extended-apis.jar differ