├── .gitignore ├── .travis.yml ├── src ├── main │ ├── java │ │ └── org │ │ │ └── complykit │ │ │ └── licensecheck │ │ │ ├── model │ │ │ └── LicenseDescriptor.java │ │ │ └── mojo │ │ │ └── OpenSourceLicenseCheckMojo.java │ └── resources │ │ └── licenses.txt └── test │ └── java │ └── org.complykit.licensecheck.mojo │ └── OpenSourceLicenseCheckMojoTest.java ├── LICENSE ├── README.md └── pom.xml /.gitignore: -------------------------------------------------------------------------------- 1 | .classpath 2 | .project 3 | .settings/ 4 | target/ 5 | .idea 6 | *.iml 7 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: java 2 | jdk: 3 | - oraclejdk7 4 | - openjdk7 5 | install: true 6 | script: mvn test -Dmaven.compiler.target=1.6 -Dmaven.compiler.source=1.6 -B 7 | -------------------------------------------------------------------------------- /src/main/java/org/complykit/licensecheck/model/LicenseDescriptor.java: -------------------------------------------------------------------------------- 1 | package org.complykit.licensecheck.model; 2 | 3 | public class LicenseDescriptor { 4 | private String code; 5 | private String licenseName; 6 | private String regex; 7 | 8 | public String getCode() { 9 | return code; 10 | } 11 | public void setCode(String code) { 12 | this.code = code; 13 | } 14 | 15 | public String getLicenseName() { 16 | return licenseName; 17 | } 18 | public void setLicenseName(String licenseName) { 19 | this.licenseName = licenseName; 20 | } 21 | 22 | public String getRegex() { 23 | return regex; 24 | } 25 | public void setRegex(String regex) { 26 | this.regex = regex; 27 | } 28 | 29 | } 30 | -------------------------------------------------------------------------------- /src/test/java/org.complykit.licensecheck.mojo/OpenSourceLicenseCheckMojoTest.java: -------------------------------------------------------------------------------- 1 | package org.complykit.licensecheck.mojo; 2 | 3 | import org.junit.Test; 4 | 5 | import java.util.HashSet; 6 | import java.util.Set; 7 | 8 | import static org.junit.Assert.assertTrue; 9 | 10 | public class OpenSourceLicenseCheckMojoTest { 11 | 12 | @Test 13 | public void testGetAsLowerCaseSet() { 14 | String src[] = {"Test1", "Test2", "Test3"}; 15 | Set expected = new HashSet(); 16 | expected.add("test1"); 17 | expected.add("test2"); 18 | expected.add("test3"); 19 | 20 | Set result = new OpenSourceLicenseCheckMojo().getAsLowerCaseSet(src); 21 | 22 | assertTrue(result.containsAll(expected)); 23 | } 24 | 25 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | Copyright (c) 2014 Michael Rice (me@michaelrice.com) 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 5 | 6 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 7 | 8 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 9 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | license-check 2 | ============= 3 | 4 | [![Build Status](https://travis-ci.org/mrice/license-check.png)](https://travis-ci.org/mrice/license-check) 5 | 6 | Current version: 0.5.3 (Sep 5, 2015) 7 | 8 | What is it? 9 | -------------- 10 | For now, **license-check** just checks to make sure that your Maven dependencies have a license declared in their POM 11 | files and that you aren't including a license on your project's blacklist. There's more on the horizon but this is an 12 | early release. 13 | 14 | How it works 15 | -------------- 16 | License-check looks at each dependency and runs a query against your Maven respository to see if the dependency declares 17 | a license that it recognizes. If not, then your build will **fail**. (Don't worry if you're hoping for a different 18 | result, there's a way around this if your dependency isn't clear on its licensing. See the configuration options below.) 19 | 20 | Isn't there already something like this? 21 | --------------- 22 | **Eh, not really.** There are a few different Maven plugins for doing license "things." But the purpose of this plugin 23 | is (or, I should say, will be) to help you make sure you're not including licenses you don't want to. For now, however, 24 | the plugin just makes sure it recognizes the declared licenses as one of the [opensource.org](http://www.opensource.org/) registered licenses. 25 | 26 | This doesn't sound like much, but it's critically important. If the license isn't recognized or isn't declared at all, 27 | it's very possible that the authors or contributors could claim fully copyright in the library and expose you to a lot 28 | of liability. 29 | 30 | How to use it 31 | --------------- 32 | Put license-check into your build process by adding the following to your pom.xml: 33 | 34 | ```xml 35 | 36 | 37 | 38 | 39 | org.complykit 40 | license-check-maven-plugin 41 | 0.5.3 42 | 43 | 44 | verify 45 | 46 | os-check 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | ``` 55 | 56 | When you do this, **your builds will start failing** if you include a dependency with an unrecognized license (or, 57 | worse, an undeclared a license). 58 | 59 | Configuration options 60 | --------------- 61 | **Create a blacklist of licenses:** Right or wrong, many organizations and their legal teams have declared certain 62 | licenses to be incompatible with their own licenseing goals. Detecting those licenses and failing your build when you 63 | accidentally include one is one of the principal goals of this project. For now, you'll need to add licenses to your 64 | blacklist manually. Add a configuration setting to your plugin such as the following: 65 | 66 | ```xml 67 | 68 | ... 69 | 70 | 71 | agpl-3.0 72 | gpl-2.0 73 | gpl-3.0 74 | 75 | 76 | 77 | ``` 78 | 79 | **To exclude artifacts:** Add the following configuration setting to the plugin: 80 | 81 | ```xml 82 | 83 | ... 84 | 85 | 86 | com.bigco.webapp:internal-common-library:1.0.23 87 | 88 | 89 | 90 | ``` 91 | 92 | Notice you need to add all three coordinates to the artifact. They should be familiar, and the correspond to the 93 | groupId, artifactId, and version that are the common elements of most poms. To add more than just one artifact to your 94 | exclude list, just add multiple param elements. 95 | 96 | **To exclude scope:** if you don't want to consider dependencies from the pom with certain scopes, especially provided 97 | or test, then you can exclude them: 98 | 99 | ```xml 100 | 101 | ... 102 | 103 | 104 | test 105 | provided 106 | 107 | 108 | 109 | ``` 110 | 111 | The idea here is that you may feel comfortable excluding some artifacts from considering. Not clear at all whether this 112 | solves difficult licensing issues, but you may want to do it. 113 | 114 | W/R/T IANAL 115 | --- 116 | For the record, the original author actually is a lawyer -- but the usual qualifications about "this is not legal advice" apply with full force. Of course. 117 | 118 | 119 | License 120 | --- 121 | Open source licensed under the [MIT License](https://opensource.org/licenses/MIT). 122 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 4.0.0 7 | 8 | 9 | 10 | org.sonatype.oss 11 | oss-parent 12 | 7 13 | 14 | 15 | org.complykit 16 | license-check-maven-plugin 17 | 0.5.4 18 | maven-plugin 19 | 20 | License Check Plugin 21 | Maven plugin to report on the licenses in use and check compatibility with project goals. 22 | http://complykit.org 23 | 2013 24 | 25 | 26 | https://github.com/mrice/license-check 27 | scm:git:git@github.com:mrice/license-check.git 28 | 29 | 30 | 31 | 32 | MIT license 33 | http://www.opensource.org/licenses/mit-license.php 34 | 35 | 36 | 37 | 38 | Michael Rice 39 | me@michaelrice.com 40 | 41 | Developer 42 | 43 | -7 44 | 45 | 46 | 47 | 48 | 3.1.1 49 | 3.1.0 50 | 3.1 51 | 2.3 52 | 4.3.3 53 | 4.12 54 | 55 | 56 | 57 | 58 | 59 | org.apache.maven 60 | maven-core 61 | ${maven.version} 62 | 63 | 64 | 65 | org.apache.maven.plugin-tools 66 | maven-plugin-annotations 67 | ${maven.plugin.version} 68 | jar 69 | compile 70 | 71 | 72 | 73 | junit 74 | junit 75 | ${junit.version} 76 | test 77 | 78 | 79 | 80 | 81 | com.google.code.gson 82 | gson 83 | ${gson.version} 84 | 85 | 86 | 87 | org.apache.httpcomponents 88 | httpclient 89 | ${httpclient.version} 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | org.apache.maven.plugins 99 | maven-plugin-plugin 100 | ${maven.plugin.version} 101 | 102 | true 103 | 104 | 105 | 106 | mojo-descriptor 107 | process-classes 108 | 109 | descriptor 110 | 111 | 112 | 113 | 114 | 115 | org.apache.maven.plugins 116 | maven-source-plugin 117 | 118 | 119 | attach-sources 120 | 121 | jar 122 | 123 | 124 | 125 | 126 | 127 | org.apache.maven.plugins 128 | maven-javadoc-plugin 129 | 130 | 131 | attach-javadocs 132 | 133 | jar 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 144 | 145 | 146 | org.sonatype.plugins 147 | nexus-staging-maven-plugin 148 | 1.6.3 149 | true 150 | 151 | ossrh 152 | https://oss.sonatype.org/ 153 | true 154 | 155 | 156 | 157 | 158 | org.apache.maven.plugins 159 | maven-source-plugin 160 | 2.2.1 161 | 162 | 163 | attach-sources 164 | 165 | jar-no-fork 166 | 167 | 168 | 169 | 170 | 171 | 172 | org.apache.maven.plugins 173 | maven-javadoc-plugin 174 | 2.9.1 175 | 176 | 177 | attach-javadocs 178 | 179 | jar 180 | 181 | 182 | 183 | 184 | 185 | 186 | org.apache.maven.plugins 187 | maven-gpg-plugin 188 | 1.5 189 | 190 | 191 | sign-artifacts 192 | verify 193 | 194 | sign 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | ossrh 206 | https://oss.sonatype.org/content/repositories/snapshots 207 | 208 | 209 | ossrh 210 | https://oss.sonatype.org/service/local/staging/deploy/maven2/ 211 | 212 | 213 | 214 | -------------------------------------------------------------------------------- /src/main/resources/licenses.txt: -------------------------------------------------------------------------------- 1 | afl-3.0 afl-3.0 Academic Free License 3.0 (?=.*academic)(?=.*free)(?=.*3) http://opensource.org/licenses/AFL-3.0 apl-1.0 apl-1.0 Adaptive Public License (?=.*adaptive)(?=.*public) http://opensource.org/licenses/APL-1.0 apache-1.1 apache-1.1 Apache License 1.1 (?=.*apache)(?=.*1\.1) apache-2.0 apache-2.0 Apache License 2.0 (?=.*apache)(?=.*2\.0) POPULAR http://opensource.org/licenses/Apache-2.0 apsl-2.0 apsl-2.0 Apple Public Source License (?=.*apple)(?=.*public)(?=.*source) artistic-1.0 artistic-1.0 Artistic license 1.0 (?=.*artistic)(?=.*1\.0) artistic-2.0 artistic-2.0 Artistic license 2.0 (?=.*artistic)(?=.*2\.0) http://opensource.org/licenses/Artistic-2.0 aal aal Attribution Assurance Licenses (?=.*attribution)(?=.*2\.0) http://opensource.org/licenses/AAL bsd-3 bsd-3-clause "BSD 3-Clause ""New"" or ""Revised"" License" (?=.*bsd)(?=.*3) POPULAR http://opensource.org/licenses/BSD-3-Clause bsd-2 bsd-2-clause "BSD 2-Clause ""Simplified"" or ""FreeBSD"" License" (?=.*bsd)(?=.*2) POPULAR http://opensource.org/licenses/BSD-2-Clause bsl-1.0 bsl-1.0 Boost Software License (?=.*boost)(?=.*software) http://opensource.org/licenses/BSL-1.0 catosl-1.1 catosl-1.1 Computer Associates Trusted Open Source License 1.1 (?=.*computer)(?=.*associates)(?=.*trust)(?=.*open) http://opensource.org/licenses/CATOSL-1.1 cddl-1.0 cddl-1.0 Common Development and Distribution License 1.0 (?=.*common)(?=.*develop)(?=.*distribution) POPULAR http://opensource.org/licenses/CDDL-1.0 cpl-1.0 cpl-1.0 Common Public License 1.0 (?=.*common)(?=.*public)(?!.*attribut) cpal-1.0 cpal-1.0 Common Public Attribution License 1.0 (?=.*common)(?=.*public)(?=.*attribut) http://opensource.org/licenses/CPAL-1.0 cua-opl-1.0 cua-opl-1.0 CUA Office Public License Version 1.0 (?=.*cua)(?=.*office)(?=.*public) http://opensource.org/licenses/CUA-OPL-1.0 ecl-1.0 ecl-1.0 "Educational Community License, Version 1.0" (?=.*education)(?=.*community)(?!.*2) http://opensource.org/licenses/ECL-2.0 efl-1.0 efl-1.0 Eiffel Forum License V1.0 (?=.*eiffel)(?=.*forum)(?!.*2) http://opensource.org/licenses/EFL-2.0 eudatagrid eudatagrid EU DataGrid Software License (?=.*datagrid)(?=.*software) http://opensource.org/licenses/EUDatagrid epl-1.0 epl-1.0 Eclipse Public License 1.0 (?=.*eclipse)(?=.*public) POPULAR http://opensource.org/licenses/EPL-1.0 ecl-2.0 ecl-2.0 "Educational Community License, Version 2.0" (?=.*education)(?=.*community)(?=.*2) http://opensource.org/licenses/ECL-2.0 efl-2.0 efl-2.0 Eiffel Forum License V2.0 (?=.*eiffel)(?=.*forum)(?=.*2) http://opensource.org/licenses/EFL-2.0 entessa entessa Entessa Public License (?=.*entessa)(?=.*public) http://opensource.org/licenses/Entessa eupl-1.1 eupl-1.1 "European Union Public License, Version 1.1" (?=.*european)(?=.*union)(?=.*public) http://opensource.org/licenses/EUPL-1.1 fair fair Fair License (?=.*fair) http://opensource.org/licenses/Fair frameworx-1.0 frameworx-1.0 Frameworx License (?=.*frameworx) http://opensource.org/licenses/Frameworx-1.0 agpl-3.0 agpl-3.0 GNU Affero General Public License v3 (?=.*affero)(?=.*3) http://opensource.org/licenses/AGPL-3.0 gpl-1.0 gpl-1.0 GNU General Public License (?=.*general)(?=.*public)(?!.*2)(?!.*3) POPULAR gpl-2.0 gpl-2.0 GNU General Public License version 2.0 (?=.*general)(?=.*public)(?=.*2)(?!.*3) http://opensource.org/licenses/GPL-2.0 gpl-3.0 gpl-3.0 GNU General Public License version 3.0 (?=.*general)(?=.*public)(?=.*3)(?!.*2) http://opensource.org/licenses/GPL-3.0 lgpl-2.1 lgpl-2.1 "GNU Library or ""Lesser"" General Public License version 2.1" (?=.*lesser)(?=.*general)(?=.*public)(?=.*2)(?!.*3) POPULAR http://opensource.org/licenses/LGPL-2.1 lgpl-3.0 lgpl-3.0 "GNU Library or ""Lesser"" General Public License version 3.0" (?=.*lesser)(?=.*general)(?=.*public)(?=.*3)(?!.*2) http://opensource.org/licenses/LGPL-3.0 hpnd hpnd Historical Permission Notice and Disclaimer (?=.*histor)(?=.*permission) http://opensource.org/licenses/HPND ipl-1.0 ipl-1.0 IBM Public License 1.0 (?=.*ibm)(?=.*public) http://opensource.org/licenses/IPL-1.0 ipa ipa IPA Font License (?=.*ipa)(?=.*font) http://opensource.org/licenses/IPA isc isc ISC License (?=.*isc) http://opensource.org/licenses/ISC lppl-1.3c lppl-1.3c LaTeX Project Public License 1.3c (?=.*latex)(?=.*public) http://opensource.org/licenses/LPPL-1.3c lpl-1.0 lpl-1.0 "Lucent Public License (""Plan9""), version 1.0" (?=.*lucent)(?=.*public)(?!.*1\.02) lpl-1.02 lpl-1.02 Lucent Public License Version 1.02 (?=.*lucent)(?=.*public)(?=.*1\.02) http://opensource.org/licenses/LPL-1.02 miros miros MirOS Licence (?=.*miros) http://opensource.org/licenses/MirOS ms-pl ms-pl Microsoft Public License (?=.*microsoft)(?!.*reciprocal) http://opensource.org/licenses/MS-PL ms-rl ms-rl Microsoft Reciprocal License (?=.*microsoft)(?=.*reciprocal) http://opensource.org/licenses/MS-RL mit mit MIT license (?=.*mit) POPULAR http://opensource.org/licenses/MIT motosoto motosoto Motosoto License (?=.*motosoto) http://opensource.org/licenses/Motosoto mpl-1.0 mpl-1.0 Mozilla Public License 1.0 (?=.*mozilla)(?!.*1\.1)(?!.*2) mpl-1.1 mpl-1.1 Mozilla Public License 1.1 (?=.*mozilla)(?=.*1\.1)(?!.*2) mpl-2.0 mpl-2.0 Mozilla Public License 2.0 (?=.*mozilla)(?!.*1\.1)(?=.*2) POPULAR http://opensource.org/licenses/MPL-2.0 multics multics Multics License (?=.*multics) http://opensource.org/licenses/Multics nasa-1.3 nasa-1.3 NASA Open Source Agreement 1.3 (?=.*nasa) http://opensource.org/licenses/NASA-1.3 ntp ntp NTP License (?=.*ntp) http://opensource.org/licenses/NTP naumen naumen Naumen Public License (?=.*naumen) http://opensource.org/licenses/Naumen ngpl ngpl Nethack General Public License (?=.*nethack) http://opensource.org/licenses/NGPL nokia nokia Nokia Open Source License (?=.*nokia) http://opensource.org/licenses/Nokia nposl-3.0 nposl-3.0 Non-Profit Open Software License 3.0 (?=.*non-profit) http://opensource.org/licenses/NPOSL-3.0 oclc-2.0 oclc-2.0 OCLC Research Public License 2.0 (?=.*oclc) http://opensource.org/licenses/OCLC-2.0 ofl-1.1 ofl-1.1 Open Font License 1.1 (?=.*open)(?=.*font) http://opensource.org/licenses/OFL-1.1 ofgtsl ofgtsl Open Group Test Suite License (?=.*open)(?=.*group) http://opensource.org/licenses/OGTSL osl-1.0 osl-1.0 Open Software License 1.0 (?=.*open)(?=.*software)(?=.*1)(?!.*2)(?!.*3) osl-2.1 osl-2.1 Open Software License 2.1 (?=.*open)(?=.*software)(?!.*1)(?=.*2)(?!.*3) osl-3.0 osl-3.0 Open Software License 3.0 (?=.*open)(?=.*software)(?!.*1)(?!.*2)(?=.*3) http://opensource.org/licenses/OSL-3.0 php-3.0 php-3.0 PHP License 3.0 (?=.*php)(?=.*3) http://opensource.org/licenses/PHP-3.0 postresql postresql The PostgreSQL License (?=.*postgres) http://opensource.org/licenses/PostgreSQL python-2.0 python-2.0 Python License (?=.*python) http://opensource.org/licenses/Python-2.0 qpl-1.0 qpl-1.0 Q Public License (?=.*q)(?=.*public) http://opensource.org/licenses/QPL-1.0 rpsl-1.0 rpsl-1.0 RealNetworks Public Source License V1.0 (?=.*realnetworks) http://opensource.org/licenses/RPSL-1.0 rpl-1.1 rpl-1.1 "Reciprocal Public License, version 1.1" (?=.*reciprocal)(?=.*1\.1) http://opensource.org/licenses/RPL-1.1 rpl-1.5 rpl-1.5 Reciprocal Public License 1.5 (?=.*reciprocal)(?=.*1\.5) http://opensource.org/licenses/RPL-1.5 rscpl rscpl Ricoh Source Code Public License (?=.*ricoh) http://opensource.org/licenses/RSCPL simpl-2.0 simpl-2.0 Simple Public License 2.0 (?=.*simple)(?=.*public)(?=.*2) http://opensource.org/licenses/SimPL-2.0 sleepycat sleepycat Sleepycat License (?=.*sleepycat) http://opensource.org/licenses/Sleepycat spl-1.0 spl-1.0 Sun Public License 1.0 (?=.*sun)(?=.*public) http://opensource.org/licenses/SPL-1.0 watcom-1.0 watcom-1.0 Sybase Open Watcom Public License 1.0 (?=.*sybase)(?=.*whatcom) http://opensource.org/licenses/Watcom-1.0 ncsa ncsa University of Illinois/NCSA Open Source License (?=.*illinois)(?=.*ncsa) http://opensource.org/licenses/NCSA vsl-1.0 vsl-1.0 Vovida Software License v. 1.0 (?=.*vovida) http://opensource.org/licenses/VSL-1.0 w3c w3c W3C License (?=.*w3c) http://opensource.org/licenses/W3C wxwindows wxwindows wxWindows Library License (?=.*wxwindows) http://opensource.org/licenses/Wxwindows xnet xnet X.Net License (?=.*x\.net) http://opensource.org/licenses/Xnet zpl-2.0 zpl-2.0 Zope Public License 2.0 (?=.*zope)(?=.*2) http://opensource.org/licenses/ZPL-2.0 zlib zlib zlib/libpng license (?=.*zlib) http://opensource.org/licenses/Zlib -------------------------------------------------------------------------------- /src/main/java/org/complykit/licensecheck/mojo/OpenSourceLicenseCheckMojo.java: -------------------------------------------------------------------------------- 1 | /* 2 | The MIT License (MIT) 3 | 4 | Copyright (c) 2013 Michael Rice 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in 14 | all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | THE SOFTWARE. 23 | */ 24 | package org.complykit.licensecheck.mojo; 25 | 26 | import java.io.BufferedReader; 27 | import java.io.File; 28 | import java.io.FileReader; 29 | import java.io.IOException; 30 | import java.io.InputStream; 31 | import java.io.InputStreamReader; 32 | import java.util.ArrayList; 33 | import java.util.HashMap; 34 | import java.util.HashSet; 35 | import java.util.List; 36 | import java.util.Locale; 37 | import java.util.Map; 38 | import java.util.Set; 39 | import java.util.regex.Matcher; 40 | import java.util.regex.Pattern; 41 | import java.util.regex.PatternSyntaxException; 42 | import org.apache.maven.RepositoryUtils; 43 | import org.apache.maven.artifact.Artifact; 44 | import org.apache.maven.plugin.AbstractMojo; 45 | import org.apache.maven.plugin.MojoExecutionException; 46 | import org.apache.maven.plugin.MojoFailureException; 47 | import org.apache.maven.plugins.annotations.Component; 48 | import org.apache.maven.plugins.annotations.Mojo; 49 | import org.apache.maven.plugins.annotations.Parameter; 50 | import org.apache.maven.project.MavenProject; 51 | import org.complykit.licensecheck.model.LicenseDescriptor; 52 | import org.eclipse.aether.RepositorySystem; 53 | import org.eclipse.aether.RepositorySystemSession; 54 | import org.eclipse.aether.artifact.DefaultArtifact; 55 | import org.eclipse.aether.repository.RemoteRepository; 56 | import org.eclipse.aether.resolution.ArtifactRequest; 57 | import org.eclipse.aether.resolution.ArtifactResolutionException; 58 | import org.eclipse.aether.resolution.ArtifactResult; 59 | 60 | /** 61 | * This plugin uses Aether to cruise through the dependencies in the project, find the related pom files, 62 | * extract the license information, and then convert the licenses into codes. 63 | * If a license cannot be verified or if the license appears on the user's blacklist, then the build will fail. 64 | * 65 | * For more, visit https://github.com/mrice/license-check 66 | * 67 | * @author michael.rice 68 | */ 69 | @Mojo(name = "os-check") 70 | public class OpenSourceLicenseCheckMojo extends AbstractMojo 71 | { 72 | 73 | private static final Locale LOCALE = Locale.ENGLISH; 74 | 75 | /** 76 | * This is the repo system required by Aether 77 | * 78 | * For more, visit http://blog.sonatype.com/people/2011/01/how-to-use-aether-in-maven-plugins/ 79 | */ 80 | @Component 81 | RepositorySystem repoSystem; 82 | 83 | @Component 84 | final MavenProject project = null; 85 | 86 | /** 87 | * The current repository and network configuration of Maven 88 | * 89 | * For more, visit http://blog.sonatype.com/people/2011/01/how-to-use-aether-in-maven-plugins/ 90 | * 91 | * @readonly 92 | */ 93 | @Parameter(defaultValue = "${repositorySystemSession}") 94 | RepositorySystemSession repoSession; 95 | 96 | /** 97 | * This is the project's remote repositories that can be used for resolving plugins and their dependencies 98 | */ 99 | @Parameter(defaultValue = "${project.remotePluginRepositories}") 100 | List remoteRepos; 101 | 102 | /** 103 | * This is the maximum number of parents to search through (in case there's a malformed pom). 104 | */ 105 | @Parameter(property = "os-check.recursion-limit", defaultValue = "12") 106 | int maxSearchDepth; 107 | 108 | /** 109 | * A list of artifacts that should be excluded from consideration. Example: <configuration> <excludes> 110 | * <param>full:artifact:coords</param>> 111 | * </excludes> </configuration> 112 | */ 113 | @Parameter(property = "os-check.excludes") 114 | String[] excludes; 115 | 116 | /** 117 | * A list of artifacts that should be excluded from consideration. Example: <configuration> 118 | * <excludesRegex> <param>full:artifact:coords</param>> 119 | * </excludesRegex> </configuration> 120 | */ 121 | @Parameter(property = "os-check.excludesRegex") 122 | String[] excludesRegex; 123 | 124 | @Parameter(property = "os-check.excludesNoLicense") 125 | boolean excludeNoLicense; 126 | 127 | /** 128 | * A list of blacklisted licenses. Example: <configuration> <blacklist> 129 | * <param>agpl-3.0</param> <param>gpl-2.0</param> 130 | * <param>gpl-3.0</param> </blacklist> </configuration> 131 | */ 132 | @Parameter(property = "os-check.blacklist") 133 | String[] blacklist; 134 | 135 | /** 136 | * A list of whitelisted licenses. Example: <configuration> <whitelist> 137 | * <param>agpl-3.0</param> <param>gpl-2.0</param> 138 | * <param>gpl-3.0</param> </blacklist> </configuration> 139 | */ 140 | @Parameter(property = "os-check.whitelist") 141 | String[] whitelist; 142 | 143 | /** 144 | * A list of scopes to exclude. May be used to exclude artifacts with test or provided scope from license check. 145 | * Example: <configuration> <excludedScopes> <param>test</param> 146 | * <param>provided</param> </excludedScopes> </configuration> 147 | */ 148 | @Parameter(property = "os-check.excludedScopes") 149 | String[] excludedScopes; 150 | 151 | /** 152 | * Used to hold the list of license descriptors. Generation is lazy on the first method call to use it. 153 | */ 154 | List descriptors = null; 155 | 156 | public void execute() throws MojoExecutionException, MojoFailureException 157 | { 158 | 159 | getLog().info("------------------------------------------------------------------------"); 160 | getLog().info("VALIDATING OPEN SOURCE LICENSES "); 161 | getLog().info("------------------------------------------------------------------------"); 162 | 163 | final Set excludeSet = getAsLowerCaseSet(excludes); 164 | final Set blacklistSet = getAsLowerCaseSet(blacklist); 165 | final Set whitelistSet = getAsLowerCaseSet(whitelist); 166 | final Set excludedScopesSet = getAsLowerCaseSet(excludedScopes); 167 | final List excludePatternList = getAsPatternList(excludesRegex); 168 | 169 | final Set artifacts = project.getDependencyArtifacts(); 170 | getLog().info("Validating licenses for " + artifacts.size() + " artifact(s)"); 171 | 172 | final Map licenses = new HashMap(); 173 | 174 | boolean buildFails = false; 175 | for (final Artifact artifact : artifacts) { 176 | if (!artifactIsOnExcludeList(excludeSet, excludePatternList, excludedScopesSet, artifact)) { 177 | final ArtifactRequest request = new ArtifactRequest(); 178 | request.setArtifact(RepositoryUtils.toArtifact(artifact)); 179 | request.setRepositories(remoteRepos); 180 | 181 | ArtifactResult result = null; 182 | try { 183 | result = repoSystem.resolveArtifact(repoSession, request); 184 | getLog().info(result.toString()); 185 | } catch (final ArtifactResolutionException e) { 186 | // TODO: figure out how to deal with this one 187 | } 188 | 189 | String licenseName = ""; 190 | try { 191 | licenseName = recurseForLicenseName(RepositoryUtils.toArtifact(result.getArtifact()), 0); 192 | } catch (IOException e) { 193 | getLog().error("Error reading license information", e); 194 | } 195 | String code = convertLicenseNameToCode(licenseName); 196 | if (code == null) { 197 | if (excludeNoLicense==false) { 198 | buildFails = true; 199 | getLog().warn("Build will fail because of artifact '" + toCoordinates(artifact) + "' and license'" + licenseName + "'."); 200 | } 201 | } else if (blacklistSet.isEmpty() == false && isContained(blacklistSet, code)) { 202 | buildFails = true; 203 | code += " IS ON YOUR BLACKLIST"; 204 | } else if (whitelistSet.isEmpty() == false && isContained(whitelistSet, code) == false) { 205 | buildFails = true; 206 | code += " IS NOT ON YOUR WHITELIST"; 207 | } 208 | licenses.put(artifact.getArtifactId(), code); 209 | } else { 210 | licenses.put(artifact.getArtifactId(), "SKIPPED because artifact is on your exclude list"); 211 | } 212 | 213 | } 214 | 215 | getLog().info(""); 216 | getLog().info("This plugin validates that the artifacts you're using have a"); 217 | getLog().info("license declared in the pom. It then tries to determine whether "); 218 | getLog().info("the license is one of the Open Source Initiative (OSI) approved "); 219 | getLog().info("licenses. If it can't find a match or if the license is on your "); 220 | getLog().info("declared blacklist or not on your declared whitelist, then the build will fail."); 221 | getLog().info(""); 222 | getLog().info("This plugin and its author are not associated with the OSI."); 223 | getLog().info("Please send me feedback: me@michaelrice.com. Thanks!"); 224 | getLog().info(""); 225 | final Set keys = licenses.keySet(); 226 | getLog().info("--[ Licenses found ]------ "); 227 | for (final String artifact : keys) { 228 | getLog().info("\t" + artifact + ": " + licenses.get(artifact)); 229 | } 230 | 231 | if (buildFails) { 232 | getLog().info(""); 233 | getLog().info("RESULT: At least one license could not be verified or appears on your blacklist or is not on your whitelist. Build fails."); 234 | getLog().info(""); 235 | throw new MojoFailureException("blacklist/whitelist of unverifiable license"); 236 | } 237 | getLog().info(""); 238 | getLog().info("RESULT: license check complete, no issues found."); 239 | getLog().info(""); 240 | 241 | } 242 | 243 | Set getAsLowerCaseSet(final String[] src) 244 | { 245 | final Set target = new HashSet(); 246 | if (src != null) { 247 | for (final String s : src) { 248 | target.add(s.toLowerCase(LOCALE)); 249 | } 250 | } 251 | return target; 252 | } 253 | 254 | List getAsPatternList(final String[] src) 255 | { 256 | final List target = new ArrayList(); 257 | if (src != null) { 258 | for (final String s : src) { 259 | try { 260 | final Pattern pattern = Pattern.compile(s); 261 | target.add(pattern); 262 | } catch (final PatternSyntaxException e) { 263 | getLog().warn("The regex " + s + " is invalid: " + e.getLocalizedMessage()); 264 | } 265 | 266 | } 267 | } 268 | return target; 269 | } 270 | 271 | String toCoordinates(Artifact artifact) 272 | { 273 | return artifact.getGroupId() + ":" + artifact.getArtifactId() + ":" + artifact.getVersion(); 274 | } 275 | 276 | String recurseForLicenseName(final Artifact artifact, final int currentDepth) throws IOException 277 | { 278 | 279 | final File artifactDirectory = artifact.getFile().getParentFile(); 280 | String directoryPath = artifactDirectory.getAbsolutePath(); 281 | directoryPath += "/" + artifact.getArtifactId() + "-" + artifact.getVersion() + ".pom"; 282 | 283 | final String pom = readPomContents(directoryPath); 284 | 285 | // first, look for a license 286 | String licenseName = extractLicenseName(pom); 287 | if (licenseName == null) { 288 | final String parentArtifactCoords = extractParentCoords(pom); 289 | if (parentArtifactCoords != null) { 290 | // search for the artifact 291 | final Artifact parent = retrieveArtifact(parentArtifactCoords); 292 | if (parent != null) { 293 | // check the recursion depth 294 | if (currentDepth >= maxSearchDepth) { 295 | return null; // TODO throw an exception 296 | } 297 | licenseName = recurseForLicenseName(parent, currentDepth + 1); 298 | } else { 299 | return null; 300 | } 301 | } else { 302 | return null; 303 | } 304 | } 305 | return licenseName; 306 | } 307 | 308 | String readPomContents(final String path) throws IOException 309 | { 310 | 311 | final StringBuffer buffer = new StringBuffer(); 312 | BufferedReader reader = null; 313 | 314 | reader = new BufferedReader(new FileReader(path)); 315 | 316 | String line = null; 317 | 318 | while ((line = reader.readLine()) != null) { 319 | buffer.append(line); 320 | } 321 | 322 | 323 | reader.close(); 324 | 325 | 326 | return buffer.toString(); 327 | } 328 | 329 | /** 330 | * This function looks for the license textual description. I didn't really want to parse the pom xml since we're 331 | * just looking for one little snippet of the content. 332 | * 333 | * @param raw 334 | * @return 335 | */ 336 | // TODO make this more elegant and less brittle 337 | String extractLicenseName(final String raw) 338 | { 339 | final String licenseTagStart = "", licenseTagStop = ""; 340 | final String nameTagStart = "", nameTagStop = ""; 341 | if (raw.indexOf(licenseTagStart) != -1) { 342 | final String licenseContents = raw.substring(raw.indexOf(licenseTagStart) + licenseTagStart.length(), raw.indexOf(licenseTagStop)); 343 | final String name = licenseContents.substring(licenseContents.indexOf(nameTagStart) + nameTagStart.length(), licenseContents.indexOf(nameTagStop)); 344 | return name; 345 | } 346 | return null; 347 | } 348 | 349 | /** 350 | * @param raw 351 | * @return 352 | */ 353 | // TODO obviously this code needs a lot of error protection and handling 354 | String extractParentCoords(final String raw) 355 | { 356 | final String parentTagStart = "", parentTagStop = ""; 357 | final String groupTagStart = "", groupTagStop = ""; 358 | final String artifactTagStart = "", artifactTagStop = ""; 359 | final String versionTagStart = "", versionTagStop = ""; 360 | 361 | if (raw.indexOf(parentTagStart) == -1) { 362 | return null; 363 | } 364 | final String contents = raw.substring(raw.indexOf(parentTagStart) + parentTagStart.length(), raw.indexOf(parentTagStop)); 365 | final String group = contents.substring(contents.indexOf(groupTagStart) + groupTagStart.length(), contents.indexOf(groupTagStop)); 366 | final String artifact = contents.substring(contents.indexOf(artifactTagStart) + artifactTagStart.length(), contents.indexOf(artifactTagStop)); 367 | final String version = contents.substring(contents.indexOf(versionTagStart) + versionTagStart.length(), contents.indexOf(versionTagStop)); 368 | return group + ":" + artifact + ":" + version; 369 | } 370 | 371 | /** 372 | * Uses Aether to retrieve an artifact from the repository. 373 | * 374 | * @param coordinates as in groupId:artifactId:version 375 | * @return the located artifact 376 | */ 377 | Artifact retrieveArtifact(final String coordinates) 378 | { 379 | 380 | final ArtifactRequest request = new ArtifactRequest(); 381 | request.setArtifact(new DefaultArtifact(coordinates)); 382 | request.setRepositories(remoteRepos); 383 | 384 | ArtifactResult result = null; 385 | try { 386 | result = repoSystem.resolveArtifact(repoSession, request); 387 | } catch (final ArtifactResolutionException e) { 388 | getLog().error("Could not resolve parent artifact (" + coordinates + "): " + e.getMessage()); 389 | } 390 | 391 | if (result != null) { 392 | return RepositoryUtils.toArtifact(result.getArtifact()); 393 | } 394 | return null; 395 | } 396 | 397 | /** 398 | * This is the method that looks at the textual description of the license and returns a code version by running 399 | * regex. It needs a lot of optimization. 400 | * 401 | * @param licenseName 402 | * @return 403 | */ 404 | String convertLicenseNameToCode(final String licenseName) 405 | { 406 | if (licenseName == null) { 407 | return null; 408 | } 409 | if (descriptors == null) { 410 | loadDescriptors(); 411 | } 412 | for (final LicenseDescriptor descriptor : descriptors) { 413 | // TODO there's gotta be a faster way to do this 414 | final Pattern pattern = Pattern.compile(descriptor.getRegex(), Pattern.CASE_INSENSITIVE); 415 | final Matcher matcher = pattern.matcher(licenseName); 416 | if (matcher.find()) { 417 | return descriptor.getCode(); 418 | } 419 | } 420 | return null; 421 | } 422 | 423 | /** 424 | * This method looks for a resource file bundled with the plugin that contains a tab delimited version of the regex 425 | * and the codes. Future versions of the plugin may retrieve fresh versions of the file from the server so we can 426 | * take advantage of improvements in what we know and what we learn. 427 | */ 428 | // TODO I know, I know... this is really raw... will make it prettier 429 | void loadDescriptors() 430 | { 431 | 432 | final String licensesPath = "/licenses.txt"; 433 | final InputStream is = getClass().getResourceAsStream(licensesPath); 434 | BufferedReader reader = null; 435 | descriptors = new ArrayList(); 436 | final StringBuffer buffer = new StringBuffer(); 437 | try { 438 | reader = new BufferedReader(new InputStreamReader(is)); 439 | String line = null; 440 | while ((line = reader.readLine()) != null) { 441 | buffer.append(line + "\n"); 442 | } 443 | } catch (final Exception e) { 444 | getLog().error(e); 445 | } finally { 446 | try { 447 | reader.close(); 448 | } catch (final IOException e) { 449 | // TODO 450 | e.printStackTrace(); 451 | } 452 | } 453 | 454 | final String lines[] = buffer.toString().split("\n"); 455 | for (final String line : lines) { 456 | final String columns[] = line.split("\\t"); 457 | final LicenseDescriptor descriptor = new LicenseDescriptor(); 458 | descriptor.setCode(columns[0]); 459 | descriptor.setLicenseName(columns[2]); 460 | descriptor.setRegex(columns[3]); 461 | descriptors.add(descriptor); 462 | } 463 | } 464 | 465 | /** 466 | * Checks to see if an artifact is on the user's exclude list 467 | * 468 | * @param excludeSet 469 | * @param patternList 470 | * @param excludedScopes 471 | * @param artifact 472 | * @return 473 | */ 474 | boolean artifactIsOnExcludeList(final Set excludeSet, final List patternList, 475 | final Set excludedScopes, final Artifact artifact) 476 | { 477 | return isExcludedTemplate(excludeSet, patternList, toCoordinates(artifact)) || excludedScopes.contains(artifact.getScope()); 478 | } 479 | 480 | boolean isExcludedTemplate(final Set excludeSet, final List patternList, 481 | final String template) 482 | { 483 | if (isContained(excludeSet, template)) { 484 | return true; 485 | } 486 | for (final Pattern pattern : patternList) { 487 | if (pattern.matcher(template).matches()) { 488 | return true; 489 | } 490 | } 491 | return false; 492 | } 493 | 494 | boolean isContained(final Set set, final String template) 495 | { 496 | if (set != null && template != null) { 497 | return set.contains(template.toLowerCase(LOCALE)); 498 | } 499 | return false; 500 | } 501 | 502 | } 503 | --------------------------------------------------------------------------------