Last active
November 3, 2020 23:54
-
-
Save Zetten/8cf873195f1680ba7278dfd22257301d to your computer and use it in GitHub Desktop.
Bazel workspace generation script
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
plugins { | |
id "com.github.ben-manes.versions" version "0.17.0" | |
id "io.spring.dependency-management" version "1.0.5.RELEASE" | |
id "com.github.jk1.dependency-license-report" version '1.0' | |
} | |
apply plugin: 'base' | |
group = 'com.example' | |
version = '0.0.0' | |
description = """ | |
Dependency management and generation of java dependency configuration for Bazel project. | |
""" | |
repositories { | |
maven { | |
url "https://jcenter.bintray.com" | |
} | |
maven { | |
url "https://repo.spring.io/libs-milestone" | |
} | |
maven { | |
url "http://download.osgeo.org/webdav/geotools" | |
} | |
} | |
configurations { | |
generate | |
} | |
configurations.generate { | |
resolutionStrategy { | |
dependencySubstitution { | |
// Work around missing jar for jai_core in Maven Central | |
substitute module('javax.media:jai_core:1.1.3') with module('javax.media:jai-core:1.1.3') | |
} | |
} | |
} | |
ext['awaitility.version'] = '3.1.0' | |
ext['commons-net.version'] = '3.6' | |
ext['cucumber-jvm.version'] = '3.0.2' | |
ext['geotools.version'] = '19.1' | |
ext['grpc-spring-boot-starter.version'] = '2.3.2' | |
ext['guava.version'] = '25.1-jre' | |
ext['fabric8-kubernetes.version'] = '3.1.12' | |
ext['jfairy.version'] = '0.5.9' | |
ext['jackson.version'] = '2.9.5' | |
ext['jimfs.version'] = '1.1' | |
ext['jool.version'] = '0.9.13' | |
ext['lombok.version'] = '1.18.0' | |
ext['okhttp3.version'] = '3.10.0' | |
ext['MockFtpServer.version'] = '2.7.1' | |
ext['postgresql.version'] = '42.2.2' | |
ext['protobuf-java.version'] = '3.5.1' | |
ext['spring-boot.version'] = '2.0.2.RELEASE' | |
ext['spring-cloud.version'] = 'Finchley.RC2' | |
ext['truth.version'] = '0.40' | |
dependencyManagement { | |
imports { | |
mavenBom 'org.springframework.cloud:spring-cloud-dependencies:' + ext['spring-cloud.version'] | |
mavenBom 'org.springframework.boot:spring-boot-dependencies:' + ext['spring-boot.version'] | |
} | |
dependencies { | |
dependency group: 'com.google.guava', name: 'guava', version: ext['guava.version'] | |
dependency group: 'io.fabric8', name: 'kubernetes-client', version: ext['fabric8-kubernetes.version'] | |
dependency group: 'com.google.jimfs', name: 'jimfs', version: ext['jimfs.version'] | |
dependency group: 'com.google.protobuf', name: 'protobuf-java', version: ext['protobuf-java.version'] | |
dependency group: 'com.google.truth', name: 'truth', version: ext['truth.version'] | |
dependency group: 'com.google.truth.extensions', name: 'truth-java8-extension', version: ext['truth.version'] | |
dependencySet(group: 'com.squareup.okhttp3', version: ext['okhttp3.version']) { | |
entry 'logging-interceptor' | |
entry 'mockwebserver' | |
entry 'okhttp' | |
} | |
dependency group: 'commons-net', name: 'commons-net', version: ext['commons-net.version'] | |
dependency group: 'io.codearte.jfairy', name: 'jfairy', version: ext['jfairy.version'] | |
dependencySet(group: 'io.cucumber', version: ext['cucumber-jvm.version']) { | |
entry 'cucumber-java8' | |
entry 'cucumber-junit' | |
entry 'cucumber-picocontainer' | |
} | |
dependency group: 'org.awaitility', name: 'awaitility', version: ext['awaitility.version'] | |
dependencySet(group: 'org.geotools', version: ext['geotools.version']) { | |
entry 'gt-geojson' | |
entry 'gt-opengis' | |
} | |
dependencySet(group: 'org.jooq', version: ext['jool.version']) { | |
entry 'jool' | |
entry 'jool-java-8' | |
} | |
dependency group: 'org.lognet', name: 'grpc-spring-boot-starter', version: ext['grpc-spring-boot-starter.version'] | |
dependency group: 'org.mockftpserver', name: 'MockFtpServer', version: ext['MockFtpServer.version'] | |
dependencySet(group: 'org.springframework.boot', version: ext['spring-boot.version']) { | |
entry('spring-boot-starter') { | |
exclude group: 'org.springframework.boot', name: 'spring-boot-starter-logging' | |
} | |
entry 'spring-boot-loader-tools' | |
} | |
} | |
} | |
dependencies { | |
generate 'com.fasterxml.jackson.dataformat:jackson-dataformat-yaml' | |
generate 'com.fasterxml.jackson.datatype:jackson-datatype-guava' | |
generate 'com.fasterxml.jackson.datatype:jackson-datatype-hibernate5' | |
generate 'com.fasterxml.jackson.datatype:jackson-datatype-jdk8' | |
generate 'com.fasterxml.jackson.datatype:jackson-datatype-jsr310' | |
generate 'com.fasterxml.jackson.dataformat:jackson-dataformat-yaml' | |
generate 'com.google.guava:guava' | |
generate 'com.google.jimfs:jimfs' | |
generate 'com.google.protobuf:protobuf-java' | |
generate 'com.google.truth:truth' | |
generate 'com.google.truth.extensions:truth-java8-extension' | |
generate 'com.h2database:h2' | |
generate 'com.querydsl:querydsl-apt' | |
generate 'com.querydsl:querydsl-jpa' | |
generate 'com.squareup.okhttp3:logging-interceptor' | |
generate 'com.squareup.okhttp3:mockwebserver' | |
generate 'com.squareup.okhttp3:okhttp' | |
generate 'commons-net:commons-net' | |
generate 'io.codearte.jfairy:jfairy' | |
generate 'io.cucumber:cucumber-java8' | |
generate 'io.cucumber:cucumber-junit' | |
generate 'io.cucumber:cucumber-picocontainer' | |
generate 'io.fabric8:kubernetes-client' | |
generate 'javax.servlet:javax.servlet-api' | |
generate 'org.awaitility:awaitility' | |
generate 'org.geotools:gt-geojson' | |
generate 'org.geotools:gt-opengis' | |
generate 'org.jooq:jool-java-8' | |
generate 'org.lognet:grpc-spring-boot-starter' | |
generate 'org.mockftpserver:MockFtpServer' | |
generate 'org.postgresql:postgresql' | |
generate 'org.projectlombok:lombok' | |
generate 'org.springframework.batch:spring-batch-test' | |
generate 'org.springframework.boot:spring-boot-configuration-processor' | |
generate 'org.springframework.boot:spring-boot-loader' | |
generate 'org.springframework.boot:spring-boot-loader-tools' | |
generate 'org.springframework.boot:spring-boot-properties-migrator' | |
generate 'org.springframework.boot:spring-boot-starter' | |
generate 'org.springframework.boot:spring-boot-starter-actuator' | |
generate 'org.springframework.boot:spring-boot-starter-batch' | |
generate 'org.springframework.boot:spring-boot-starter-data-jpa' | |
generate 'org.springframework.boot:spring-boot-starter-data-rest' | |
generate 'org.springframework.boot:spring-boot-starter-hateoas' | |
generate 'org.springframework.boot:spring-boot-starter-log4j2' | |
generate 'org.springframework.boot:spring-boot-starter-security' | |
generate 'org.springframework.boot:spring-boot-starter-test' | |
generate 'org.springframework.boot:spring-boot-starter-web' | |
generate 'org.springframework.boot:spring-boot-starter-websocket' | |
generate 'org.springframework.cloud:spring-cloud-starter-netflix-eureka-client' | |
generate 'org.springframework.cloud:spring-cloud-starter-netflix-eureka-server' | |
generate 'org.springframework.cloud:spring-cloud-starter-gateway' | |
generate 'org.springframework.data:spring-data-rest-hal-browser' | |
} | |
Set<ProjectDependency> projectDependencies = new HashSet<>() | |
task generateWorkspace { | |
description 'Generates java_import_external Bazel targets for dependencies' | |
doLast { | |
com.github.jk1.license.ConfigurationData configurationData = new com.github.jk1.license.reader.ConfigurationReader().read(project, configurations.generate) | |
// Collect all dependency info | |
configurations.generate.resolvedConfiguration.firstLevelModuleDependencies.each { | |
parseDependencyTree(it, projectDependencies, configurationData) | |
} | |
generateJavaRepositoriesFile(projectDependencies.stream() | |
.flatMap { collectDependencies(it) } | |
.collect(java.util.stream.Collectors.toCollection { new TreeSet() })) | |
} | |
} | |
ProjectDependency parseDependencyTree(ResolvedDependency dep, Set<ProjectDependency> projectDependencies, com.github.jk1.license.ConfigurationData configurationData) { | |
ProjectDependency existing = projectDependencies.find { it.id == dep.module.id } | |
if (existing) return existing | |
ProjectDependency thisDep = new ProjectDependency() | |
thisDep.id = dep.module.id | |
thisDep.dependencies = dep.children.collect { | |
parseDependencyTree(it, projectDependencies, configurationData) | |
}.toSet() | |
thisDep.jarSha256 = sha256(dep.moduleArtifacts.first().file) | |
thisDep.licenseTypes = getLicenseTypes(configurationData.dependencies.find { moduleData -> | |
(moduleData.group == dep.moduleGroup && moduleData.name == dep.moduleName && moduleData.version == dep.moduleVersion) | |
}) | |
thisDep.urls = findArtifactUrls(dep.module.id.toString()) | |
Set<ResolvedArtifactResult> sourcesArtifacts = dependencies.createArtifactResolutionQuery() | |
.forModule(dep.moduleGroup, dep.moduleName, dep.moduleVersion) | |
.withArtifacts(JvmLibrary, SourcesArtifact) | |
.execute() | |
.resolvedComponents | |
.collectMany { it.getArtifacts(SourcesArtifact) } | |
.toSet() | |
if (sourcesArtifacts.size() == 1) { | |
thisDep.srcjarSha256 = sha256(sourcesArtifacts.first().file) | |
thisDep.srcjarUrls = findArtifactUrls(dep.module.id.toString() + ":sources") | |
} else if (sourcesArtifacts.size() > 1) { | |
println("Artifact had multiple sources artifacts! ${it.moduleVersion.id}") | |
} | |
projectDependencies.add(thisDep) | |
return thisDep | |
} | |
private static List<String> getLicenseTypes(com.github.jk1.license.ModuleData moduleData) { | |
return moduleData.poms.collectMany { pom -> pom.licenses } | |
.collect { guessLicenseType([name: it.name, url: it.url]) } | |
} | |
private static String sha256(file) { | |
java.security.MessageDigest md = java.security.MessageDigest.getInstance("SHA-256"); | |
file.eachByte 4096, { bytes, size -> md.update(bytes, 0, size) } | |
return md.digest().collect { String.format "%02x", it }.join() | |
} | |
private List<String> findArtifactUrls(String artifactId) { | |
return repositories.findResults { | |
def url = getArtifactUrl(artifactId, it.url) + ".jar" | |
int code = new URL(url).openConnection().with { | |
requestMethod = 'HEAD' | |
connect() | |
responseCode | |
} | |
return (code >= 100 && code < 400) ? url : null | |
} | |
} | |
private static String getArtifactUrl(artifactId, repoUrl) { | |
def group, artifact, version, classifier, file_version | |
String[] parts = artifactId.split(':') | |
if (parts.length == 4) { | |
(group, artifact, version, classifier) = parts | |
file_version = version + '-' + classifier | |
} else { | |
(group, artifact, version) = parts | |
file_version = version | |
} | |
return [repoUrl, | |
group.replace('.', '/'), | |
artifact, | |
version, | |
artifact + '-' + file_version].join('/') | |
} | |
// Ref: https://bazel.build/versions/master/docs/be/functions.html#licenses_args | |
// Implementation from https://github.com/jart/bazel/blob/ef0328910fd61a7e77f4c56443d87365eeac7174/tools/build_defs/repo/maven_config_generator/index.html | |
private static String guessLicenseType(license) { | |
if (license['url'].indexOf('gnu.org/licenses/agpl') != -1 || | |
license['url'].indexOf('wtfpl.net') != -1 || | |
license['name'].indexOf('WTFPL') != -1 || | |
license['name'].indexOf('AGPL') != -1 || | |
license['name'].indexOf('Affero') != -1 || | |
license['name'].indexOf('affero') != -1 || | |
license['name'].indexOf('Fuck') != -1 || | |
license['name'].indexOf('fuck') != -1) { | |
return 'by_exception_only'; | |
} | |
if (license['url'].indexOf('apache.org/licenses/LICENSE-2.0') != -1 || | |
license['url'].indexOf('opensource.org/licenses/mit') != -1 || | |
license['url'].indexOf('opensource.org/licenses/bsd') != -1 || | |
license['url'].indexOf('opensource.org/licenses/BSD') != -1 || | |
license['url'].indexOf('opensource.org/licenses/ISC') != -1 || | |
license['url'].indexOf('opensource.org/licenses/zpl') != -1 || | |
license['url'].indexOf('opensource.org/licenses/zlib') != -1 || | |
license['url'].indexOf('unicode.org/copyright') != -1 || | |
license['url'].indexOf('json.org/license') != -1 || | |
license['url'].indexOf('gwtproject.org/terms') != -1) { | |
return 'notice'; | |
} | |
if (license['url'].indexOf('www.gnu.org') != -1 || | |
license['url'].indexOf('www.oracle.com/technetwork/java/javase/terms/license/index.html') != -1 || | |
license['url'].indexOf('opensource.org/licenses/sleepycat.php') != -1 || | |
license['url'].indexOf('opensource.org/licenses/osl') != -1 || | |
license['url'].indexOf('opensource.org/licenses/qtpl') != -1 || | |
license['url'].indexOf('opensource.org/licenses/sleepycat') != -1 || | |
license['url'].indexOf('cr.yp.to/qmail') != -1 || | |
license['url'].indexOf('MPL/NPL/') != -1) { | |
return 'restricted'; | |
} | |
if (license['url'].indexOf('opensource.org/licenses/cpl') != -1 || | |
license['url'].indexOf('opensource.org/licenses/eclipse') != -1 || | |
license['url'].indexOf('opensource.org/licenses/apsl') != -1 || | |
license['url'].indexOf('opensource.org/licenses/ibmpl') != -1 || | |
license['url'].indexOf('eclipse.org/org/documents/epl') != -1 || | |
license['url'].indexOf('eclipse.org/legal/epl') != -1) { | |
return 'reciprocal'; | |
} | |
if (license['url'].indexOf('creativecommons.org/licenses/publicdomain') != -1 || | |
license['url'].indexOf('unlicense.org') != -1) { | |
return 'unencumbered'; | |
} | |
if (license['name'] == 'gpl' || | |
license['name'] == 'lgpl' || | |
license['name'].indexOf('GPL') != -1 || | |
license['name'].indexOf('BCL') != -1 || | |
license['name'].indexOf('Oracle Binary Code License') != -1 || | |
license['name'].indexOf('Attribution-ShareAlike') != -1 || | |
license['name'].indexOf('CC BY-SA') != -1 || | |
license['name'].indexOf('Attribution-NoDerivs') != -1 || | |
license['name'].indexOf('CC BY-ND') != -1 || | |
license['name'].indexOf('Sleepycat') != -1) { | |
return 'restricted'; | |
} | |
if (license['name'].indexOf('MPL') != -1 || | |
license['name'].indexOf('Mozilla Public License') != -1 || | |
license['name'].indexOf('Common Public License') != -1 || | |
license['name'].indexOf('CDDL') != -1 || | |
license['name'].indexOf('Common Development and Distribution License') != -1 || | |
license['name'].indexOf('EPL') != -1 || | |
license['name'].indexOf('Eclipse Public License') != -1 || | |
license['name'].indexOf('APSL') != -1 || | |
license['name'].indexOf('Apple Public Source License') != -1) { | |
return 'reciprocal'; | |
} | |
if (license['name'].indexOf('BSD') != -1 || | |
license['name'].indexOf('MIT') != -1 || | |
license['name'].indexOf('X11') != -1 || | |
license['name'].indexOf('Apache') != -1 || | |
license['name'].indexOf('Artistic') != -1 || | |
license['name'].indexOf('ISC') != -1 || | |
license['name'].indexOf('ICU') != -1 || | |
license['name'].indexOf('JSON License') != -1) { | |
return 'notice'; | |
} | |
if (license['name'].indexOf('Beerware') != -1 || | |
license['name'].indexOf('beerware') != -1 || | |
license['name'].indexOf('Google App Engine Terms of Service') != -1) { | |
return 'permissive'; | |
} | |
if (license['name'] == 'Public Domain' && license['url'] == '') { | |
return 'unencumbered'; // e.g. aopalliance | |
} | |
return 'TODO'; | |
} | |
java.util.stream.Stream<ProjectDependency> collectDependencies(ProjectDependency projectDependency) { | |
return java.util.stream.Stream.concat(java.util.stream.Stream.of(projectDependency), | |
projectDependency.dependencies.stream().flatMap { collectDependencies(it) }) | |
} | |
void generateJavaRepositoriesFile(SortedSet<ProjectDependency> projectDependencies) { | |
def javaRepositories = file("java_repositories.bzl") | |
List<String> javaRepositoriesContent = new ArrayList<>() | |
javaRepositoriesContent.add('load("@bazel_tools//tools/build_defs/repo:java.bzl", "java_import_external")') | |
javaRepositoriesContent.add("") | |
javaRepositoriesContent.add("") | |
javaRepositoriesContent.add('def java_repositories():') | |
projectDependencies.each { dep -> | |
javaRepositoriesContent.add(" java_import_external(") | |
javaRepositoriesContent.add(" name = \"${dep.bazelIdentifier}\",") | |
javaRepositoriesContent.add(" licenses = [\"${getMostRestrictiveLicense(dep)}\"],") | |
javaRepositoriesContent.add(" jar_urls = [") | |
dep.urls.each { | |
javaRepositoriesContent.add(" \"${it}\",") | |
} | |
javaRepositoriesContent.add(" ],") | |
javaRepositoriesContent.add(" jar_sha256 = \"${dep.jarSha256}\",") | |
if (dep.srcjarSha256) { | |
javaRepositoriesContent.add(" srcjar_urls = [") | |
dep.srcjarUrls.each { | |
javaRepositoriesContent.add(" \"${it}\",") | |
} | |
javaRepositoriesContent.add(" ],") | |
javaRepositoriesContent.add(" srcjar_sha256 = \"${dep.srcjarSha256}\",") | |
} | |
if (!dep.dependencies.isEmpty()) { | |
javaRepositoriesContent.add(" runtime_deps = [") | |
dep.dependencies.toSorted().each { | |
javaRepositoriesContent.add(" \"@${it.bazelIdentifier}\",") | |
} | |
javaRepositoriesContent.add(" ],") | |
} | |
javaRepositoriesContent.add(" )") | |
javaRepositoriesContent.add("") | |
} | |
javaRepositories.withWriter{ out -> | |
javaRepositoriesContent.each { out.println it } | |
} | |
} | |
String getMostRestrictiveLicense(ProjectDependency dep) { | |
for (LicenseType type : LicenseType.values()) { | |
for (String license : dep.licenseTypes) { | |
if (type.toString().equals(license.toUpperCase())) { | |
return license; | |
} | |
} | |
} | |
println "WARNING: No Bazel-registered license detected for ${dep.id}" | |
return "none" | |
} | |
enum LicenseType { | |
BY_EXCEPTION_ONLY, | |
RESTRICTED, | |
RESTRICTED_IF_STATICALLY_LINKED, | |
RECIPROCAL, | |
NOTICE, | |
PERMISSIVE, | |
UNENCUMBERED, | |
NONE | |
} | |
class ProjectDependency implements Comparable<ProjectDependency> { | |
ModuleVersionIdentifier id | |
Set<ProjectDependency> dependencies | |
String jarSha256 | |
List<String> licenseTypes | |
List<String> urls | |
String srcjarSha256 | |
List<String> srcjarUrls | |
@Override | |
int compareTo(ProjectDependency o) { | |
return getBazelIdentifier() <=> o.getBazelIdentifier() | |
} | |
String getBazelIdentifier() { | |
id.group.replaceAll("[^\\w]", "_") + "_" + id.name.replaceAll("[^\\w]", "_") | |
} | |
@Override | |
String toString() { | |
return id.toString() | |
} | |
boolean equals(o) { | |
if (this.is(o)) return true | |
if (getClass() != o.class) return false | |
ProjectDependency that = (ProjectDependency) o | |
if (dependencies != that.dependencies) return false | |
if (id != that.id) return false | |
if (jarSha256 != that.jarSha256) return false | |
if (licenseTypes != that.licenseTypes) return false | |
if (srcjarSha256 != that.srcjarSha256) return false | |
if (srcjarUrls != that.srcjarUrls) return false | |
if (urls != that.urls) return false | |
return true | |
} | |
int hashCode() { | |
int result | |
result = (id != null ? id.hashCode() : 0) | |
result = 31 * result + (dependencies != null ? dependencies.hashCode() : 0) | |
result = 31 * result + (jarSha256 != null ? jarSha256.hashCode() : 0) | |
result = 31 * result + (licenseTypes != null ? licenseTypes.hashCode() : 0) | |
result = 31 * result + (urls != null ? urls.hashCode() : 0) | |
result = 31 * result + (srcjarSha256 != null ? srcjarSha256.hashCode() : 0) | |
result = 31 * result + (srcjarUrls != null ? srcjarUrls.hashCode() : 0) | |
return result | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment