You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sling.apache.org by cz...@apache.org on 2020/04/08 08:58:08 UTC

[sling-slingfeature-maven-plugin] branch master updated: SLING-9341 : Allow to create a license report about the included artifacts

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

cziegeler pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/sling-slingfeature-maven-plugin.git


The following commit(s) were added to refs/heads/master by this push:
     new b282f33  SLING-9341 : Allow to create a license report about the included artifacts
b282f33 is described below

commit b282f33c96af20dad5ef0c74fce0e6832dd894ae
Author: Carsten Ziegeler <cz...@apache.org>
AuthorDate: Wed Apr 8 10:57:53 2020 +0200

    SLING-9341 : Allow to create a license report about the included artifacts
---
 .../sling/feature/maven/mojos/ApisJarContext.java  | 152 ++++++++++++---
 .../sling/feature/maven/mojos/ApisJarMojo.java     | 209 +++++++++++++++++----
 2 files changed, 304 insertions(+), 57 deletions(-)

diff --git a/src/main/java/org/apache/sling/feature/maven/mojos/ApisJarContext.java b/src/main/java/org/apache/sling/feature/maven/mojos/ApisJarContext.java
index 140cce1..6df40b1 100644
--- a/src/main/java/org/apache/sling/feature/maven/mojos/ApisJarContext.java
+++ b/src/main/java/org/apache/sling/feature/maven/mojos/ApisJarContext.java
@@ -18,19 +18,30 @@ package org.apache.sling.feature.maven.mojos;
 
 import java.io.File;
 import java.util.ArrayList;
+import java.util.Collection;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
+import java.util.TreeMap;
 
 import org.apache.felix.utils.manifest.Clause;
+import org.apache.maven.model.License;
+import org.apache.maven.model.Model;
+import org.apache.maven.plugin.MojoExecutionException;
 import org.apache.sling.feature.ArtifactId;
 import org.apache.sling.feature.extension.apiregions.api.ApiRegion;
 import org.apache.sling.feature.extension.apiregions.api.ApiRegions;
 
+/**
+ * Context for creating the api jars
+ */
 class ApisJarContext {
 
+    /**
+     * Information about a single artifact (bundle) taking part in the api generation.
+     */
     public static final class ArtifactInfo {
 
         private ArtifactId id;
@@ -49,6 +60,8 @@ class ApisJarContext {
 
         private final Set<String> nodeTypes = new HashSet<>();
 
+        private List<License> licenses;
+
         public ArtifactInfo(final ArtifactId id) {
             this.id = id;
         }
@@ -110,13 +123,20 @@ class ApisJarContext {
             return includedResources;
         }
 
-
+        /**
+         * Get all node types from this artifact
+         * @return The set of node types, might be empty
+         */
         public Set<String> getNodeTypes() {
             return this.nodeTypes;
         }
 
-        public void addNodeType(final String name) {
-            this.nodeTypes.add(name);
+        public List<License> getLicenses() {
+            return licenses;
+        }
+
+        public void setLicenses(List<License> licenses) {
+            this.licenses = licenses;
         }
     }
 
@@ -138,13 +158,17 @@ class ApisJarContext {
 
     private final ApiRegions apiRegions;
 
+    private final Map<ArtifactId, Model> modelCache = new HashMap<>();
+
+    private List<String[]> licenseDefaultMatches;
+
     public ApisJarContext(final File mainDir, final ArtifactId featureId, final ApiRegions regions) {
         this.featureId = featureId;
 
         // deflated and source dirs can be shared
-        this.deflatedBinDir = newDir(mainDir, "deflated-bin");
-        this.deflatedSourcesDir = newDir(mainDir, "deflated-sources");
-        this.checkedOutSourcesDir = newDir(mainDir, "checkouts");
+        this.deflatedBinDir = new File(mainDir, "deflated-bin");
+        this.deflatedSourcesDir = new File(mainDir, "deflated-sources");
+        this.checkedOutSourcesDir = new File(mainDir, "checkouts");
         this.apiRegions = regions;
     }
 
@@ -168,26 +192,28 @@ class ApisJarContext {
         return checkedOutSourcesDir;
     }
 
-    public boolean addJavadocClasspath(String classpathItem) {
+    public boolean addJavadocClasspath(final String classpathItem) {
         return javadocClasspath.add(classpathItem);
     }
 
-    public boolean addPackageWithoutJavaClasses(String packageName) {
-        return packagesWithoutJavaClasses.add(packageName);
-    }
-
     public Set<String> getJavadocClasspath() {
         return javadocClasspath;
     }
 
-    public Set<String> getPackagesWithoutJavaClasses() {
-        return packagesWithoutJavaClasses;
+    public File getJavadocDir() {
+        return javadocDir;
+    }
+
+    public void setJavadocDir(final File javadocDir) {
+        this.javadocDir = javadocDir;
+    }
+
+    public boolean addPackageWithoutJavaClasses(final String packageName) {
+        return packagesWithoutJavaClasses.add(packageName);
     }
 
-    private static File newDir(File parent, String child) {
-        File dir = new File(parent, child);
-        dir.mkdirs();
-        return dir;
+    public Set<String> getPackagesWithoutJavaClasses() {
+        return packagesWithoutJavaClasses;
     }
 
     public ArtifactInfo addArtifactInfo(final ArtifactId id) {
@@ -201,11 +227,95 @@ class ApisJarContext {
         return this.infos;
     }
 
-    public File getJavadocDir() {
-        return javadocDir;
+    public Map<ArtifactId, Model> getModelCache() {
+        return this.modelCache;
     }
 
-    public void setJavadocDir(File javadocDir) {
-        this.javadocDir = javadocDir;
+    public Collection<ArtifactInfo> getArtifactInfos(final ApiRegion region) {
+        final Map<ArtifactId, ArtifactInfo> result = new TreeMap<>();
+        for(final ArtifactInfo info : this.infos) {
+            if ( !info.getUsedExportedPackages(region).isEmpty() ) {
+                result.put(info.getId(), info);
+            }
+        }
+        return result.values();
+    }
+
+
+    public void setLicenseDefaults(final List<String> licenseDefaults) throws MojoExecutionException {
+        this.licenseDefaultMatches = parseMatches(licenseDefaults);
+    }
+
+    public String getLicenseDefault(final ArtifactId id) {
+        String result = null;
+        if (this.licenseDefaultMatches != null) {
+            result = match(id);
+        }
+        return result;
+    }
+    private List<String[]> parseMatches(final List<String> licenseDefaults) throws MojoExecutionException {
+        List<String[]> matches = null;
+        if (licenseDefaults != null && !licenseDefaults.isEmpty()) {
+            matches = new ArrayList<>();
+            for (final String t : licenseDefaults) {
+                final String[] parts;
+                if ( t.endsWith("=") ) {
+                    parts = new String[] {t.substring(0, t.length() - 1), ""};
+                } else {
+                    parts = t.split("=");
+                }
+                if ( parts.length != 2 ) {
+                    throw new MojoExecutionException("Illegal license default: " + t);
+                }
+                final String[] val = parts[0].split(":");
+                if (val.length > 5) {
+                    throw new MojoExecutionException("Illegal license default: " + t);
+                }
+                final String[] result = new String[val.length + 1];
+                System.arraycopy(val, 0, result, 1, val.length);
+                result[0] = parts[1];
+                matches.add(result);
+            }
+        }
+        return matches;
+    }
+
+    private boolean match(final String value, final String matcher) {
+        if (matcher.endsWith("*")) {
+            return value.startsWith(matcher.substring(0, matcher.length() - 1));
+        }
+        return matcher.equals(value);
+    }
+
+    private String match(final ArtifactId id) {
+        boolean match = false;
+
+        for(final String[] m : this.licenseDefaultMatches) {
+            match = match(id.getGroupId(), m[1]);
+            if (match && m.length > 2) {
+                match = match(id.getArtifactId(), m[2]);
+            }
+            if (match && m.length == 4) {
+                match = match(id.getVersion(), m[3]);
+            } else if (match && m.length == 5) {
+                match = match(id.getVersion(), m[4]);
+                if (match) {
+                    match = match(id.getType(), m[3]);
+                }
+            } else if (match && m.length == 6) {
+                match = match(id.getVersion(), m[5]);
+                if (match) {
+                    match = match(id.getType(), m[3]);
+                    if (match) {
+                        match = match(id.getClassifier(), m[4]);
+                    }
+                }
+            }
+            if (match) {
+                return m[0];
+            }
+        }
+
+        return null;
     }
 }
diff --git a/src/main/java/org/apache/sling/feature/maven/mojos/ApisJarMojo.java b/src/main/java/org/apache/sling/feature/maven/mojos/ApisJarMojo.java
index b2054f4..1c89336 100644
--- a/src/main/java/org/apache/sling/feature/maven/mojos/ApisJarMojo.java
+++ b/src/main/java/org/apache/sling/feature/maven/mojos/ApisJarMojo.java
@@ -21,6 +21,7 @@ import java.io.FileInputStream;
 import java.io.IOException;
 import java.net.URL;
 import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Calendar;
@@ -35,6 +36,7 @@ import java.util.jar.JarEntry;
 import java.util.jar.JarInputStream;
 import java.util.jar.Manifest;
 import java.util.regex.Pattern;
+import java.util.stream.Collectors;
 
 import javax.json.JsonArray;
 
@@ -49,6 +51,7 @@ import org.apache.maven.artifact.resolver.ArtifactResolutionException;
 import org.apache.maven.artifact.resolver.ArtifactResolutionRequest;
 import org.apache.maven.artifact.resolver.ArtifactResolutionResult;
 import org.apache.maven.artifact.resolver.filter.ArtifactFilter;
+import org.apache.maven.model.License;
 import org.apache.maven.model.Model;
 import org.apache.maven.model.Scm;
 import org.apache.maven.model.building.ModelBuilder;
@@ -106,10 +109,13 @@ public class ApisJarMojo extends AbstractIncludingFeatureMojo {
     /** Alternative ID to a source artifact. */
     private static final String SCM_ID = "source-ids";
 
+    /** Tag for source when using SCM info */
     private static final String SCM_TAG = "scm-tag";
 
+    /** Alternative SCM location. */
     private static final String SCM_LOCATION = "scm-location";
 
+    /** Alternative SCM encoding, default is UTF-8 */
     private static final String SCM_ENCODING = "scm-encoding";
 
     private static final String APIS = "apis";
@@ -242,6 +248,38 @@ public class ApisJarMojo extends AbstractIncludingFeatureMojo {
     @Parameter(defaultValue = "META-INF,SLING-INF")
     private String resourceFolders;
 
+    /**
+     * Create a license report file. This is the name of that file within the jar
+     * @since 1.2.0
+     */
+    @Parameter
+    private String licenseReport;
+
+    /**
+     * A artifact patterns to match artifacts without a license. Follows the pattern
+     * "groupId:artifactId:type:classifier:version". After the patter a "=" followed by
+     * the license information needs to be specified. This information is used in the
+     * license report if no license is specified for an artifact.
+     * @since 1.2.0
+     */
+    @Parameter
+    private List<String> licenseDefaults;
+
+    /**
+     * Header added on top of the license report
+     * @since 1.2.0
+     */
+    @Parameter(defaultValue = "This archive contains files from the following artifacts:")
+    private String licenseReportHeader;
+
+    /**
+     * Footer added at the bottom of the license report
+     * @since 1.2.0
+     */
+    @Parameter
+    private String licenseReportFooter;
+
+
     @Parameter(defaultValue = "${project.build.directory}/apis-jars", readonly = true)
     private File mainOutputDir;
 
@@ -421,6 +459,7 @@ public class ApisJarMojo extends AbstractIncludingFeatureMojo {
         // create an output directory per feature
         final File featureDir = new File(mainOutputDir, feature.getId().getArtifactId());
         final ApisJarContext ctx = new ApisJarContext(this.mainOutputDir, feature.getId(), regions);
+        ctx.setLicenseDefaults(licenseDefaults);
 
         // for each bundle included in the feature file and record directories
         for (final Artifact artifact : feature.getBundles()) {
@@ -947,7 +986,7 @@ public class ApisJarMojo extends AbstractIncludingFeatureMojo {
                 }
             }
             if ( resourceName.endsWith(CND_EXTENSION)) {
-                info.addNodeType(resourceName);
+                info.getNodeTypes().add(resourceName);
             }
         }
 
@@ -1017,6 +1056,36 @@ public class ApisJarMojo extends AbstractIncludingFeatureMojo {
         }
     }
 
+    private Model getArtifactPom(final ApisJarContext ctx, final ArtifactId artifactId) throws MojoExecutionException {
+        final ArtifactId pomArtifactId = artifactId.changeClassifier(null).changeType("pom");
+        // check model cache
+        Model model = ctx.getModelCache().get(pomArtifactId);
+        if ( model == null ) {
+            getLog().debug("Retrieving POM " + pomArtifactId.toMvnId() + "...");
+            // POM file must exist, let the plugin fail otherwise
+            final URL pomURL = retrieve(artifactProvider, pomArtifactId);
+            if (pomURL == null) {
+                throw new MojoExecutionException("Unable to find artifact " + pomArtifactId.toMvnId());
+            }
+
+            File pomFile = null;
+            try {
+                pomFile = IOUtils.getFileFromURL(pomURL, true, null);
+            } catch (IOException e) {
+                throw new MojoExecutionException(e.getMessage());
+            }
+            getLog().debug("POM " + pomArtifactId.toMvnId() + " successfully retrieved, reading the model...");
+
+            // read model
+            model = modelBuilder.buildRawModel(pomFile, ModelBuildingRequest.VALIDATION_LEVEL_MINIMAL, false).get();
+            getLog().debug("POM model " + pomArtifactId.toMvnId() + " successfully read");
+
+            // cache model
+            ctx.getModelCache().put(pomArtifactId, model);
+        }
+        return model;
+    }
+
     private void checkoutSourcesFromSCM(final ApisJarContext ctx,
             final ArtifactInfo info,
             final Artifact sourceArtifact)
@@ -1026,25 +1095,9 @@ public class ApisJarMojo extends AbstractIncludingFeatureMojo {
         String tag = sourceArtifact.getMetadata().get(SCM_TAG);
 
         // Artifacts SCM metadata may not available or are an override, let's fallback to the POM
-        final ArtifactId pomArtifactId = sourceArtifact.getId().changeClassifier(null).changeType("pom");
-        getLog().debug("Falling back to SCM checkout, retrieving POM " + pomArtifactId.toMvnId() + "...");
-        // POM file must exist, let the plugin fail otherwise
-        final URL pomURL = retrieve(artifactProvider, pomArtifactId);
-        if (pomURL == null) {
-            throw new MojoExecutionException("Unable to find artifact " + pomArtifactId.toMvnId());
-        }
-
-        File pomFile = null;
-        try {
-            pomFile = IOUtils.getFileFromURL(pomURL, true, null);
-        } catch (IOException e) {
-            throw new MojoExecutionException(e.getMessage());
-        }
-        getLog().debug("POM " + pomArtifactId.toMvnId() + " successfully retrieved, reading the model...");
-
-        // read model
-        Model pomModel = modelBuilder.buildRawModel(pomFile, ModelBuildingRequest.VALIDATION_LEVEL_MINIMAL, false).get();
-        getLog().debug("POM model " + pomArtifactId.toMvnId() + " successfully read, processing the SCM...");
+        getLog().debug("Falling back to SCM checkout...");
+        final Model pomModel = getArtifactPom(ctx, sourceArtifact.getId());
+        getLog().debug("Processing SCM info from pom...");
 
         final Scm scm = pomModel.getScm();
         if (scm != null) {
@@ -1060,7 +1113,7 @@ public class ApisJarMojo extends AbstractIncludingFeatureMojo {
             getLog().warn("Ignoring sources for artifact " + sourceArtifact.getId().toMvnId() + " : SCM not defined in "
                     + sourceArtifact.getId().toMvnId()
                           + " bundle neither in "
-                    + pomArtifactId.toMvnId() + " POM file.");
+                    + pomModel.getId() + " POM file.");
             return;
         }
 
@@ -1106,11 +1159,10 @@ public class ApisJarMojo extends AbstractIncludingFeatureMojo {
             pomScanner.setIncludes("**/pom.xml");
             pomScanner.scan();
             for (String pomFileLocation : pomScanner.getIncludedFiles()) {
-                pomFile = new File(basedir, pomFileLocation);
-                pomModel = modelBuilder.buildRawModel(pomFile, ModelBuildingRequest.VALIDATION_LEVEL_MINIMAL, false)
-                        .get();
+                final File pomFile = new File(basedir, pomFileLocation);
+                final Model model = modelBuilder.buildRawModel(pomFile, ModelBuildingRequest.VALIDATION_LEVEL_MINIMAL, false).get();
 
-                if (sourceArtifact.getId().getArtifactId().equals(pomModel.getArtifactId())) {
+                if (sourceArtifact.getId().getArtifactId().equals(model.getArtifactId())) {
                     basedir = pomFile.getParentFile();
                     break;
                 }
@@ -1243,19 +1295,19 @@ public class ApisJarMojo extends AbstractIncludingFeatureMojo {
             final List<File> resources) throws MojoExecutionException {
         final JarArchiver jarArchiver = new JarArchiver();
 
+        final Collection<ArtifactInfo> infos = ctx.getArtifactInfos(apiRegion);
+
         if ( APIS.equals(classifier) || SOURCES.equals(classifier) ) {
             // api or source
-            for(final ArtifactInfo includeEntry : ctx.getArtifactInfos()) {
-                final File dir = APIS.equals(classifier) ? includeEntry.getBinDirectory() : includeEntry.getSourceDirectory();
-
-                final String[] usedExportedPackageIncludes = includeEntry.getUsedExportedPackageIncludes(apiRegion);
-                if ( usedExportedPackageIncludes.length > 0 ) {
-                    getLog().debug("Adding directory " + dir.getName() + " with " + Arrays.toString(usedExportedPackageIncludes));
-                    final DefaultFileSet fileSet = new DefaultFileSet(dir);
-                    fileSet.setIncludingEmptyDirectories(false);
-                    fileSet.setIncludes(usedExportedPackageIncludes);
-                    jarArchiver.addFileSet(fileSet);
-                }
+            for(final ArtifactInfo info : infos) {
+                final File dir = APIS.equals(classifier) ? info.getBinDirectory() : info.getSourceDirectory();
+
+                final String[] usedExportedPackageIncludes = info.getUsedExportedPackageIncludes(apiRegion);
+                getLog().debug("Adding directory " + dir.getName() + " with " + Arrays.toString(usedExportedPackageIncludes));
+                final DefaultFileSet fileSet = new DefaultFileSet(dir);
+                fileSet.setIncludingEmptyDirectories(false);
+                fileSet.setIncludes(usedExportedPackageIncludes);
+                jarArchiver.addFileSet(fileSet);
             }
         } else {
             // javadoc
@@ -1295,6 +1347,12 @@ public class ApisJarMojo extends AbstractIncludingFeatureMojo {
             }
         }
 
+        // check for license report
+        if ( this.licenseReport != null ) {
+            final File out = this.createLicenseReport(ctx, apiRegion, infos);
+            jarArchiver.addFile(out, this.licenseReport);
+        }
+
         // build classifier
         final StringBuilder classifierBuilder = new StringBuilder();
         if (ctx.getFeatureId().getClassifier() != null) {
@@ -1474,5 +1532,84 @@ public class ApisJarMojo extends AbstractIncludingFeatureMojo {
         Collections.sort(sorted);
         return sorted;
     }
+
+    private File createLicenseReport(final ApisJarContext ctx, final ApiRegion region, final Collection<ArtifactInfo> infos) throws MojoExecutionException {
+        final File out = new File(this.getTmpDir(), region.getName() + "-license-report.txt");
+        if ( !out.exists() ) {
+
+            final List<String> output = new ArrayList<>();
+            output.add(licenseReportHeader);
+            output.add("");
+            for(final ArtifactInfo info : infos) {
+                final List<License> licenses = this.getLicenses(ctx, info);
+
+                boolean exclude = false;
+                final StringBuilder sb = new StringBuilder(info.getId().toMvnId());
+                if ( !licenses.isEmpty() ) {
+                    sb.append(" - License(s) : ");
+                    sb.append(String.join(", ",
+                            licenses.stream()
+                                    .map(l -> l.getName().concat(" (").concat(l.getUrl()).concat(")"))
+                                    .collect(Collectors.toList())));
+                } else {
+                    final String licenseDefault = ctx.getLicenseDefault(info.getId());
+                    if ( licenseDefault != null ) {
+                        if ( licenseDefault.isEmpty()) {
+                            exclude = true;
+                            getLog().debug("Excluding from license report " + info.getId().toMvnId());
+                        } else {
+                            sb.append(" - License(s) : ");
+                            sb.append(licenseDefault);
+                        }
+                    } else {
+                        getLog().warn("No license info found for " + info.getId().toMvnId());
+                    }
+                }
+                if ( !exclude ) {
+                    output.add(sb.toString());
+                }
+            }
+            if ( this.licenseReportFooter != null ) {
+                output.add("");
+                output.add(this.licenseReportFooter);
+            }
+            try {
+                Files.write(out.toPath(), output);
+            } catch (IOException e) {
+                throw new MojoExecutionException("Unable to write license report: " + e.getMessage(), e);
+            }
+        }
+        return out;
+    }
+
+    private List<License> getLicenses(final ApisJarContext ctx, final ArtifactInfo info) {
+        List<License> result = info.getLicenses();
+        if  ( result == null ) {
+            try {
+                ArtifactId id = info.getId();
+                do {
+                    final Model model = getArtifactPom(ctx, id);
+                    final List<License> ll = model.getLicenses();
+
+                    if ( ll != null && !ll.isEmpty() ) {
+                        result = ll;
+                    } else {
+                        if ( model.getParent() != null ) {
+                            id = new ArtifactId(model.getParent().getGroupId(), model.getParent().getArtifactId(), model.getParent().getVersion(), null, "pom");
+                        } else {
+                            break;
+                        }
+                    }
+                }  while (result == null);
+            } catch (MojoExecutionException m) {
+                // nothing to do
+            }
+            if ( result == null ) {
+                result = Collections.emptyList();
+            }
+            info.setLicenses(result);
+        }
+        return result;
+    }
 }