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

Add license check #74

Merged
merged 3 commits into from
Jul 26, 2023
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
9 changes: 9 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,15 @@ If you happen to be using a multi project sbt build, you can instead use `dumpLi
from `aggregate` on the root project) or `dumpLicenseReportAnyProject` (which collects the results for all projects in the sbt build).
In either case the results will be merged into a single report file in a format that mirrors `dumpLicenseReport`.

### Check licenses

If you want to check that only certain licenses are used in a project, you can use

> licenseCheck

This ensures all licenses fall into one of the categories given by `licenseCheckAllow` which defaults
to a set of commonly allowed [OSS licenses](./src/main/scala/sbtlicensereport/SbtLicenseReport.scala#L173).

## Configuration

The license report plugin can be configured to dump any number of reports, but the default report
Expand Down
2 changes: 1 addition & 1 deletion project/build.properties
Original file line number Diff line number Diff line change
@@ -1 +1 @@
sbt.version=1.9.0-M1
sbt.version=1.9.3
20 changes: 19 additions & 1 deletion src/main/scala/sbtlicensereport/SbtLicenseReport.scala
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ object SbtLicenseReport extends AutoPlugin {
val dumpLicenseReportAnyProject = taskKey[File](
"Dumps a report file against all projects of the license report (using the target language) and combines it into a single file."
)
val licenseCheck = taskKey[Unit]("Checks that all licenses are allowed. Fails if other licenses are found.")
val licenseReportColumns =
settingKey[Seq[Column]]("Additional columns to be added to the final report")
val licenseReportDir = settingKey[File]("The location where we'll write the license reports.")
Expand All @@ -59,6 +60,7 @@ object SbtLicenseReport extends AutoPlugin {
)
val licenseFilter =
settingKey[LicenseCategory => Boolean]("Configuration for what licenses to include in the report, by default.")
val licenseCheckAllow = settingKey[Seq[LicenseCategory]]("Licenses that are allowed to pass in checkLicenses.")
}
// Workaround for broken autoImport in sbt 0.13.5
val autoImport = autoImportImpl
Expand Down Expand Up @@ -132,6 +134,12 @@ object SbtLicenseReport extends AutoPlugin {
for (config <- licenseReportConfigurations.value)
LicenseReport.dumpLicenseReport(reports.flatMap(_.licenses), config)
dir
},
licenseCheck := {
val log = streams.value.log
val report = updateLicenses.value
val allowed = licenseCheckAllow.value
LicenseReport.checkLicenses(report.licenses, allowed, log)
}
)

Expand All @@ -145,6 +153,16 @@ object SbtLicenseReport extends AutoPlugin {
licenseFilter := TypeFunctions.const(true),
licenseReportStyleRules := None,
licenseReportTypes := Seq(MarkDown, Html, Csv),
licenseReportColumns := Seq(Column.Category, Column.License, Column.Dependency)
licenseReportColumns := Seq(Column.Category, Column.License, Column.Dependency),
licenseCheckAllow := Seq(
LicenseCategory.Apache,
LicenseCategory.BouncyCastle,
LicenseCategory.BSD,
LicenseCategory.CC0,
LicenseCategory.EPL,
LicenseCategory.MIT,
LicenseCategory.Mozilla,
LicenseCategory.PublicDomain
)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why was the LGPL category not added to this list? AFAIK, it is allowed to ship libraries licensed under the LGPL with proprietary software.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

?

)
}
40 changes: 31 additions & 9 deletions src/main/scala/sbtlicensereport/license/LicenseReport.scala
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,18 @@ case class DepLicense(
s"$module ${homepage.map(url => s" from $url")} on $license in ${configs.mkString("(", ",", ")")}"
}

object DepLicense {
implicit val ordering: Ordering[DepLicense] = Ordering.fromLessThan { case (l, r) =>
if (l.license.category != r.license.category) l.license.category.name < r.license.category.name
else {
if (l.license.name != r.license.name) l.license.name < r.license.name
else {
l.module.toString < r.module.toString
}
}
}
}

case class LicenseReport(licenses: Seq[DepLicense], orig: ResolveReport) {
override def toString = s"""|## License Report ##
|${licenses.mkString("\t", "\n\t", "\n")}
Expand All @@ -45,15 +57,7 @@ object LicenseReport {
config: LicenseReportConfiguration
): Unit = {
import config._
val ordered = reportLicenses.filter(l => licenseFilter(l.license.category)) sortWith { case (l, r) =>
if (l.license.category != r.license.category) l.license.category.name < r.license.category.name
else {
if (l.license.name != r.license.name) l.license.name < r.license.name
else {
l.module.toString < r.module.toString
}
}
}
val ordered = reportLicenses.filter(l => licenseFilter(l.license.category)).sorted
// TODO - Make one of these for every configuration?
for (language <- languages) {
val reportFile = new File(config.reportDir, s"${title}.${language.ext}")
Expand All @@ -78,6 +82,24 @@ object LicenseReport {
}
}

def checkLicenses(reportLicenses: Seq[DepLicense], allowed: Seq[LicenseCategory], log: Logger): Unit = {
val violators = reportLicenses.collect {
case dep if !allowed.contains(dep.license.category) => dep
}
if (violators.nonEmpty) {
log.error(
violators.sorted
.map(v => (v.license, v.module))
.distinct
.map { case (license, module) => s"${license.category.name}: ${module.toString}" }
.mkString("Found non-allowed licenses among the dependencies:\n", "\n", "")
)
throw new sbt.MessageOnlyException(s"Found non-allowed licenses!")
} else {
log.info("Found only allowed licenses among the dependencies!")
}
}

private def getModuleInfo(dep: IvyNode): DepModuleInfo = {
// TODO - null handling...
DepModuleInfo(dep.getModuleId.getOrganisation, dep.getModuleId.getName, dep.getModuleRevision.getId.getRevision)
Expand Down