Skip to content
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
2 changes: 0 additions & 2 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -105,8 +105,6 @@ subprojects {
cacheChangingModulesFor(cacheHours, 'hours')
}
}

apply from: rootProject.layout.projectDirectory.file('gradle/dependency-licenses.gradle')
}

apply {
Expand Down
5 changes: 1 addition & 4 deletions buildSrc/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -63,15 +63,12 @@ repositories {
dependencies {
implementation platform("org.apache.grails:grails-gradle-bom:${gradleProperties.projectVersion}")
implementation 'org.apache.grails.gradle:grails-publish'
implementation "gradle.plugin.com.hierynomus.gradle.plugins:license-gradle-plugin:${gradleProperties.gradleLicensePluginVersion}", {
// Due to https://github.com/hierynomus/license-gradle-plugin/issues/161, spring must be excluded
exclude group: 'org.springframework', module: 'spring-core'
}
implementation 'cloud.wondrify:asset-pipeline-gradle'
implementation 'org.apache.grails:grails-docs-core'
implementation 'org.apache.grails:grails-gradle-plugins'
implementation 'org.asciidoctor:asciidoctor-gradle-jvm'
implementation 'org.springframework.boot:spring-boot-gradle-plugin'
implementation "org.nosphere.apache.rat:org.nosphere.apache.rat.gradle.plugin:${gradleProperties.apacheRatVersion}"
implementation "org.cyclonedx.bom:org.cyclonedx.bom.gradle.plugin:${gradleProperties.gradleCycloneDxPluginVersion}"
implementation "org.gradle.crypto.checksum:org.gradle.crypto.checksum.gradle.plugin:${gradleProperties.gradleChecksumPluginVersion}"
}
3 changes: 2 additions & 1 deletion gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,8 @@ yakworksHibernateGroovyProxyVersion=1.1
# Build dependency versions not managed by BOMs
apacheRatVersion=0.8.1
gradleChecksumPluginVersion=1.4.0
gradleLicensePluginVersion=0.16.1
# note: the cyclonedx 3.0.0-alpha-1 still does not set the project correctly, so we must use the older version
gradleCycloneDxPluginVersion=2.4.0

# micronaut libraries not in the bom due to the potential for spring mismatches
micronautPlatformVersion=4.9.2
Expand Down
30 changes: 0 additions & 30 deletions gradle/dependency-licenses.gradle

This file was deleted.

5 changes: 5 additions & 0 deletions gradle/java-config.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,11 @@ tasks.withType(GroovyCompile).configureEach {
// Grails determines the grails version via the META-INF/MANIFEST.MF file
// Note: we exclude attributes such as Built-By, Build-Jdk, Created-By to ensure the build is reproducible.
tasks.withType(Jar).configureEach {
if (project.findProperty('skipJavaComponent')) {
it.enabled = false
return
}

manifest.attributes(
'Implementation-Title': 'Apache Grails',
'Implementation-Version': grailsVersion,
Expand Down
41 changes: 24 additions & 17 deletions gradle/publish-config.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -30,29 +30,36 @@ extensions.configure(GrailsPublishExtension) {
it.developers = findProperty('pomDevelopers') as Map<String, String> ?: [graemerocher: 'Graeme Rocher']
it.pomCustomization = findProperty('pomCustomization') as Closure
it.publishTestSources = findProperty('pomPublishTestSources') ?: false
it.testRepositoryPath = rootProject.layout.buildDirectory.dir('local-maven')
it.testRepositoryPath = findProperty('skipJavaComponent') ? null : rootProject.layout.projectDirectory.dir('../build/local-maven')
}

tasks.withType(Jar).configureEach {
if(it.archiveClassifier.getOrNull() != 'javadoc') {
from(rootProject.layout.projectDirectory.file('DISCLAIMER')) {
into('META-INF')
}
if (findProperty('skipJavaComponent')) {
// since the publish plugin won't register
tasks.register('publishAllPublicationsToTestCaseMavenRepoRepository')
}

def projectLicense = layout.projectDirectory.file('src/main/resources/META-INF/LICENSE')
if (!projectLicense.asFile.exists()) {
def basicLicense = rootProject.layout.projectDirectory.file('licenses/LICENSE-Apache-2.0.txt')
from(basicLicense) {
if (!findProperty('skipJavaComponent')) {
tasks.withType(Jar).configureEach {
if (it.archiveClassifier.getOrNull() != 'javadoc') {
from(rootProject.layout.projectDirectory.file('DISCLAIMER')) {
into('META-INF')
rename { 'LICENSE' }
}
}

def projectNotice = layout.projectDirectory.file('src/main/resources/META-INF/NOTICE')
if (!projectNotice.asFile.exists()) {
def basicNotice = rootProject.layout.projectDirectory.file('grails-core/src/main/resources/META-INF/NOTICE')
from(basicNotice) {
into('META-INF')
def projectLicense = layout.projectDirectory.file('src/main/resources/META-INF/LICENSE')
if (!projectLicense.asFile.exists()) {
def basicLicense = rootProject.layout.projectDirectory.file('licenses/LICENSE-Apache-2.0.txt')
from(basicLicense) {
into('META-INF')
rename { 'LICENSE' }
}
}

def projectNotice = layout.projectDirectory.file('src/main/resources/META-INF/NOTICE')
if (!projectNotice.asFile.exists()) {
def basicNotice = rootProject.layout.projectDirectory.file('grails-core/src/main/resources/META-INF/NOTICE')
from(basicNotice) {
into('META-INF')
}
}
}
}
Expand Down
250 changes: 250 additions & 0 deletions gradle/sbom-config.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,250 @@
/*
* 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
*
* https://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.
*/

import groovy.json.JsonOutput
import groovy.json.JsonSlurper
import org.cyclonedx.gradle.CycloneDxTask
import org.cyclonedx.model.ExternalReference
import org.cyclonedx.model.LicenseChoice
import org.cyclonedx.model.License
import org.cyclonedx.model.OrganizationalContact
import org.cyclonedx.model.OrganizationalEntity
import org.cyclonedx.model.Component

import java.nio.charset.StandardCharsets
import java.time.format.DateTimeFormatter
import java.time.temporal.ChronoUnit

apply plugin: 'org.cyclonedx.bom'

project.ext.setProperty('sbomOutputLocation', project.layout.buildDirectory.file("${findProperty('pomArtifactId') ?: project.name}-${projectVersion}-sbom.json"))

def sbomTask = tasks.named('cyclonedxBom', CycloneDxTask)
sbomTask.configure { CycloneDxTask it ->
// the 2.x version of Cyclonedx uses a legacy syntax & helpers for setting inputs so the syntax below
// is required until the 3.x version is GA
it.setProjectType(findProperty('sbomProjectType')?.toString() ?: Component.Type.FRAMEWORK.name())
[email protected](findProperty('pomArtifactId')?.toString() ?: project.name)
[email protected](new OrganizationalEntity().tap {
name = 'Apache Software Foundation'
urls = ['https://www.apache.org/', 'https://security.apache.org/']
addContact(new OrganizationalContact().tap {
name = 'Apache Grails Development Team'
email = '[email protected]'
})
})
[email protected](new LicenseChoice().tap {
addLicense(new License().tap {
name = 'Apache-2.0'
url = 'https://www.apache.org/licenses/LICENSE-2.0.txt'
})
})

[email protected]([
new ExternalReference().tap {
url = 'https://grails.apache.org/'
type = ExternalReference.Type.WEBSITE
}
])

// sboms are published for the purposes of vulnerability analysis so only include the runtime classpath
[email protected](['runtimeClasspath'])
[email protected](['compileClasspath', 'testRuntimeClasspath'])

// turn off license text since it's base64 encoded & will inflate the jar sizes
[email protected](false)

// disable xml output
it.xmlOutput.unsetConvention()

def sbomOutputLocation = findProperty('sbomOutputLocation')
it.jsonOutput.set(sbomOutputLocation.get())
it.outputs.file(sbomOutputLocation)

// cyclonedx does not support "choosing" the license placed in the sbom
// see: https://github.com/CycloneDX/cyclonedx-gradle-plugin/issues/16
it.doLast {
// ordered so that first value is the most preferred, this list is from https://www.apache.org/legal/resolved.html
def preferredLicenses = ['Apache-2.0', 'EPL-1.0', 'BSD-3-Clause', 'EPL-2.0', 'MIT', 'MIT-0', '0BSD', 'UPL-1.0',
'CC0-1.0', 'ICU', 'Xnet', 'NCSA', 'W3C', 'Zlib', 'AFL-3.0', 'MS-PL', 'PSF-2.0', 'APAFML',
'BSL-1.0', 'WTFPL', 'Unlicense', 'HPND', 'EPICS', 'TCL']

// licenses are standardized @ https://spdx.org/licenses/
def licenses = [
'Apache-2.0' : [
id : 'Apache-2.0',
url : 'https://www.apache.org/licenses/LICENSE-2.0'
],
'BSD-2-Clause': [
id : 'BSD-2-Clause',
url : 'https://opensource.org/license/bsd-3-clause/'
],
'BSD-3-Clause': [
id : 'BSD-3-Clause',
url : 'https://opensource.org/license/bsd-3-clause/'
],
// Variant of Apache 1.1 license. Approved by legal LEGAL-707
'OpenSymphony' : [
// id is optional and the opensymphony license doesn't have an SPDX id
name: 'The OpenSymphony Software License, Version 1.1',
url: 'https://raw.githubusercontent.com/sitemesh/sitemesh2/refs/heads/master/LICENSE.txt'
],
'UPL-1.0' : [
id: 'UPL-1.0',
url: 'https://oss.oracle.com/licenses/upl/'
],

]

def licenseMapping = [
'pkg:maven/org.antlr/[email protected]?type=jar' : 'BSD-3-Clause', // maps incorrectly because of https://github.com/CycloneDX/cyclonedx-core-java/issues/205
'pkg:maven/jline/[email protected]?type=jar' : 'BSD-2-Clause', // maps incorrectly because of https://github.com/CycloneDX/cyclonedx-core-java/issues/205
'pkg:maven/org.jline/[email protected]?type=jar' : 'BSD-2-Clause', // maps incorrectly because of https://github.com/CycloneDX/cyclonedx-core-java/issues/205
'pkg:maven/org.liquibase.ext/[email protected]?type=jar': 'Apache-2.0', // maps incorrectly because of https://github.com/liquibase/liquibase/issues/2445 & the base pom does not define a license
'pkg:maven/com.oracle.coherence.ce/[email protected]?type=pom': 'UPL-1.0', // does not have map based on license id
'pkg:maven/com.oracle.coherence.ce/[email protected]?type=pom': 'UPL-1.0', // does not have map based on license id
'pkg:maven/opensymphony/[email protected]?type=jar' : 'OpenSymphony', // custom license approved by legal LEGAL-707
'pkg:maven/org.jruby/[email protected]?type=jar' : 'BSD-3-Clause'// https://web.archive.org/web/20240822213507/http://www.jcraft.com/jzlib/LICENSE.txt shows it's a 3 clause
]

// we don't distribute these so these licenses are considered acceptable, but we still prefer ASF licenses.
// Require a whitelist of any case of category X licenses to prevent accidental inclusion in a distributed artifact
// this list will need to be updated anytime we change versions so we can revise the licenses
def licenseExceptions = [
'grails-data-hibernate5-core' : [
'pkg:maven/org.hibernate.common/[email protected]?type=jar': 'LGPL-2.1-only', // hibernate 5 is LGPL, we are migrating to ASF license in hibernate 7
'pkg:maven/org.hibernate/[email protected]?type=jar': 'LGPL-2.1-only', // hibernate 5 is LGPL, we are migrating to ASF license in hibernate 7
],
'grails-data-hibernate5' : [
'pkg:maven/org.hibernate.common/[email protected]?type=jar': 'LGPL-2.1-only', // hibernate 5 is LGPL, we are migrating to ASF license in hibernate 7
'pkg:maven/org.hibernate/[email protected]?type=jar': 'LGPL-2.1-only', // hibernate 5 is LGPL, we are migrating to ASF license in hibernate 7
],
'grails-data-hibernate5-spring-boot': [
'pkg:maven/org.hibernate.common/[email protected]?type=jar': 'LGPL-2.1-only', // hibernate 5 is LGPL, we are migrating to ASF license in hibernate 7
'pkg:maven/org.hibernate/[email protected]?type=jar': 'LGPL-2.1-only', // hibernate 5 is LGPL, we are migrating to ASF license in hibernate 7
],
'grails-data-hibernate5-dbmigration': [
'pkg:maven/javax.xml.bind/[email protected]?type=jar': 'CDDL-1.1', // api export
],
]

def pickLicense = { String bomRef, List licenseChoices ->
if (!bomRef) {
throw new GradleException("No bomRef found for a dependency of ${project.name}, cannot pick license")
}

logger.info('Picking license for {} from {} choices', bomRef, licenseChoices.size())
if (licenseMapping.containsKey(bomRef)) {
// There are several reasons that cyclone will get the license wrong, usually due to upstream not publishing information or publishing it incorrectly
// see the licenseMapping map above for details
def licenseId = licenseMapping[bomRef]
logger.lifecycle('Forcing license for {} to {}', bomRef, licenseId)

def licenseBlock = licenses[licenseId]
if (!licenseBlock) {
throw new GradleException("Cannot find license information for id ${licenseId} to use for bomRef ${bomRef} in project ${project.name}")
}

return licenseBlock
}

if (!(licenseChoices instanceof List) || licenseChoices.isEmpty()) {
throw new GradleException("No License was found for dependency: ${bomRef} in project ${project.name}")
}

def licenseIds = licenseChoices.findAll { it instanceof Map && it.license instanceof Map && it.license.id }
def foundLicense = preferredLicenses.find { p -> licenseIds.any { it.license.id == p } }
if (foundLicense) {
return licenseIds.find { it.license.id == foundLicense }
}

def defaultLicense = licenseChoices[0] // pick the first one found
def defaultLicenseId = defaultLicense.license.id as String
if (defaultLicenseId == null) {
throw new GradleException("Could not determine License id for dependency: ${bomRef} in project ${project.name} for value ${defaultLicense}")
}
if (!(defaultLicenseId in preferredLicenses)) {
def projectLicenseExemptions = licenseExceptions[project.name] ?: [:]
def permittedLicense = projectLicenseExemptions.get(bomRef) == defaultLicenseId
if (!permittedLicense) {
throw new GradleException("Unpermitted License found for bom dependency: ${bomRef} in project ${project.name} : ${defaultLicenseId}")
}
}

return defaultLicense
}

// json schema is documented here: https://cyclonedx.org/docs/1.6/json/
def rewriteSbom = { File f ->
def bom = new JsonSlurper().parse(f)

// timestamp is not reproducible: https://github.com/CycloneDX/cyclonedx-gradle-plugin/issues/292
bom.metadata.timestamp = DateTimeFormatter.ISO_INSTANT.format(buildDate.truncatedTo(ChronoUnit.SECONDS))

// components[*].licenses
def comps = (bom instanceof Map && bom.components instanceof List) ? bom.components : []
comps.each { c ->
if (c instanceof Map && c.licenses instanceof List && !c.licenses.isEmpty()) {
def chosen = pickLicense(c['bom-ref'] as String, c.licenses as List)
if (chosen != null) {
c.licenses = [chosen]
}
}
}

// force the serialNumber to be reproducible by removing it & recalculating
bom.serialNumber = ''
String withOutSerial = JsonOutput.prettyPrint(JsonOutput.toJson(bom))
def uuid = UUID.nameUUIDFromBytes(withOutSerial.getBytes(StandardCharsets.UTF_8.name()))
def urn = "urn:uuid:${uuid.toString()}" as String
bom.serialNumber = urn

f.setText(JsonOutput.prettyPrint(JsonOutput.toJson(bom)), StandardCharsets.UTF_8.name())

logger.info('Rewrote JSON SBOM ({}) to pick preferred license', project.relativePath(f))
}

sbomOutputLocation.get().with { rewriteSbom(it.asFile) }
}
}

// for now, we only publish the sbom inside of the binary jar (our bom projects won't have a jar file)
pluginManager.withPlugin('java') {
tasks.named('assemble').configure {
dependsOn('cyclonedxBom')
}

if (!project.findProperty('skipJavaComponent')) {
tasks.named('jar').configure { Jar jar ->
jar.dependsOn('cyclonedxBom')

jar.from(findProperty('sbomOutputLocation')) {
into('META-INF')
rename {
'sbom.json'
}
}

jar.manifest {
attributes('Sbom-Location': 'META-INF/sbom.json')
attributes('Sbom-Format': 'CycloneDX')
}
}
}
}
1 change: 1 addition & 0 deletions grails-async/core/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -50,5 +50,6 @@ apply {
from rootProject.layout.projectDirectory.file('gradle/code-style-config.gradle')
from rootProject.layout.projectDirectory.file('gradle/docs-config.gradle')
from rootProject.layout.projectDirectory.file('gradle/publish-config.gradle')
from rootProject.layout.projectDirectory.file('gradle/sbom-config.gradle')
from rootProject.layout.projectDirectory.file('gradle/test-config.gradle')
}
Loading
Loading