diff --git a/bom/build.gradle b/bom/build.gradle new file mode 100644 index 00000000000..7b3d454be6a --- /dev/null +++ b/bom/build.gradle @@ -0,0 +1,17 @@ +plugins { + id 'io.deephaven.project.register' +} + +description = 'Deephaven Bill of Materials' + +dependencies { + constraints { constraint -> + project.rootProject + .subprojects + .findAll { p -> io.deephaven.project.ProjectType.isPublic(p) } + .each { p -> + // TODO(deephaven-core#2050): Annotate some public dependencies with "runtime" constraint + constraint.api p + } + } +} diff --git a/bom/gradle.properties b/bom/gradle.properties new file mode 100644 index 00000000000..314ee538445 --- /dev/null +++ b/bom/gradle.properties @@ -0,0 +1 @@ +io.deephaven.project.ProjectType=BOM_PUBLIC diff --git a/buildSrc/src/main/groovy/io.deephaven.java-publishing-conventions.gradle b/buildSrc/src/main/groovy/io.deephaven.java-publishing-conventions.gradle index 7fe1985df4f..b4d58695ca9 100644 --- a/buildSrc/src/main/groovy/io.deephaven.java-publishing-conventions.gradle +++ b/buildSrc/src/main/groovy/io.deephaven.java-publishing-conventions.gradle @@ -1,3 +1,5 @@ +import io.deephaven.project.util.PublishingConstants + plugins { id 'java' id 'signing' @@ -17,119 +19,25 @@ tasks.withType(Javadoc) { options.addStringOption('sourcepath', sourceSets.main.allJava.getSourceDirectories().getAsPath()) } -def developerId = 'deephaven' -def developerName = 'Deephaven Developers' -def developerEmail = 'developers@deephaven.io' - -def projectUrl = 'https://github.com/deephaven/deephaven-core' -def orgName = 'Deephaven Data Labs' -def orgUrl = 'https://deephaven.io/' - def licenseName = ext.license.name def licenseUrl = ext.license.url -def issuesSystem = 'GitHub Issues' -def issuesUrl = 'https://github.com/deephaven/deephaven-core/issues' - -def scmUrl = 'https://github.com/deephaven/deephaven-core' -def scmConnection = 'scm:git:git://github.com/deephaven/deephaven-core.git' -def scmDevConnection = 'scm:git:ssh://github.com/deephaven/deephaven-core.git' - publishing { publications { mavenJava(MavenPublication) { from components.java pom { - url = projectUrl - organization { - name = orgName - url = orgUrl - } licenses { license { name = licenseName url = licenseUrl } } - scm { - url = scmUrl - connection = scmConnection - developerConnection = scmDevConnection - } - issueManagement { - system = issuesSystem - url = issuesUrl - } - developers { - developer { - id = developerId - name = developerName - email = developerEmail - organization = orgName - organizationUrl = orgUrl - } - } } } } - repositories { - maven { - name = 'ossrh' - // ossrhUsername, ossrhPassword - credentials(PasswordCredentials) - def releasesRepoUrl = "https://s01.oss.sonatype.org/service/local/staging/deploy/maven2/" - def snapshotsRepoUrl = "https://s01.oss.sonatype.org/content/repositories/snapshots/" - url = version.endsWith('SNAPSHOT') ? snapshotsRepoUrl : releasesRepoUrl - } - } -} - -signing { - sign publishing.publications.mavenJava - String signingKey = findProperty("signingKey") - String signingPassword = findProperty("signingPassword") - if (signingKey != null && signingPassword != null) { - // In CI, it's harder to pass a file; so if specified, we use the in-memory version. - useInMemoryPgpKeys(signingKey, signingPassword) - } -} - -def assertIsRelease = tasks.register('assertIsRelease') { - it.doLast { - if (System.getenv('CI') != 'true') { - throw new IllegalStateException('Release error: env CI must be true') - } - def actualGithubRef = System.getenv('GITHUB_REF') - def expectedGithubRef = "refs/heads/release/v${project.version}" - if (actualGithubRef != expectedGithubRef) { - throw new IllegalStateException("Release error: env GITHUB_REF '${actualGithubRef}' does not match expected '${expectedGithubRef}'. Bad tag? Bump version?") - } - } } -// This is an extra safety checks to make sure we don't publish incorrectly -publishMavenJavaPublicationToOssrhRepository.dependsOn(assertIsRelease) - -afterEvaluate { - // https://central.sonatype.org/publish/requirements/ - if (project.description == null) { - throw new IllegalStateException("Project '${project.name}' is missing a description, which is required for publishing to maven central") - } - - // The common-conventions plugin should take care of this, but we'll double-check here - if (!project.archivesBaseName.contains('deephaven')) { - throw new IllegalStateException("Project '${project.name}' archiveBaseName '${project.archivesBaseName}' does not contain 'deephaven'") - } - - publishing { - publications { - mavenJava(MavenPublication) { - artifactId = archivesBaseName - pom { - name = archivesBaseName - description = project.description - } - } - } - } -} +PublishingConstants.setupRepositories(project) +PublishingConstants.setupMavenPublication(project, publishing.publications.mavenJava) +PublishingConstants.setupSigning(project, publishing.publications.mavenJava) diff --git a/buildSrc/src/main/groovy/io.deephaven.project.bom-public.gradle b/buildSrc/src/main/groovy/io.deephaven.project.bom-public.gradle new file mode 100644 index 00000000000..0b1bbcdce64 --- /dev/null +++ b/buildSrc/src/main/groovy/io.deephaven.project.bom-public.gradle @@ -0,0 +1,33 @@ +import io.deephaven.project.util.JavaDependencies +import io.deephaven.project.util.PublishingConstants + +plugins { + id 'io.deephaven.common-conventions' + id 'java-platform' + id 'maven-publish' + id 'signing' +} + +publishing { + publications { + myPlatform(MavenPublication) { + from components.javaPlatform + pom { + licenses { + license { + name = 'The Apache License, Version 2.0' + url = 'https://www.apache.org/licenses/LICENSE-2.0.txt' + } + } + } + } + } +} + +PublishingConstants.setupRepositories(project) +PublishingConstants.setupMavenPublication(project, publishing.publications.myPlatform) +PublishingConstants.setupSigning(project, publishing.publications.myPlatform) + +project.tasks + .getByName('quick') + .dependsOn JavaDependencies.verifyAllConfigurationsArePublicTask(project) diff --git a/buildSrc/src/main/groovy/io.deephaven.project.java-external.gradle b/buildSrc/src/main/groovy/io.deephaven.project.java-external.gradle index df7e4da29a2..644fa115a1f 100644 --- a/buildSrc/src/main/groovy/io.deephaven.project.java-external.gradle +++ b/buildSrc/src/main/groovy/io.deephaven.project.java-external.gradle @@ -1,3 +1,5 @@ +import io.deephaven.project.util.JavaDependencies + plugins { id 'io.deephaven.common-conventions' id 'io.deephaven.java-common-conventions' @@ -6,3 +8,7 @@ plugins { id 'io.deephaven.java-publishing-conventions' id 'io.deephaven.default-description' } + +project.tasks + .getByName('quick') + .dependsOn JavaDependencies.verifyRuntimeClasspathIsPublicTask(project) diff --git a/buildSrc/src/main/groovy/io.deephaven.project.java-public.gradle b/buildSrc/src/main/groovy/io.deephaven.project.java-public.gradle index fba48fa3674..0ac0e9e6546 100644 --- a/buildSrc/src/main/groovy/io.deephaven.project.java-public.gradle +++ b/buildSrc/src/main/groovy/io.deephaven.project.java-public.gradle @@ -1,3 +1,5 @@ +import io.deephaven.project.util.JavaDependencies + plugins { id 'io.deephaven.common-conventions' id 'io.deephaven.java-common-conventions' @@ -6,3 +8,7 @@ plugins { id 'io.deephaven.java-publishing-conventions' id 'io.deephaven.default-description' } + +project.tasks + .getByName('quick') + .dependsOn JavaDependencies.verifyRuntimeClasspathIsPublicTask(project) diff --git a/buildSrc/src/main/groovy/io.deephaven.project.root.gradle b/buildSrc/src/main/groovy/io.deephaven.project.root.gradle index 127ddeb66c1..3978d224bf9 100644 --- a/buildSrc/src/main/groovy/io.deephaven.project.root.gradle +++ b/buildSrc/src/main/groovy/io.deephaven.project.root.gradle @@ -1,3 +1,19 @@ +import io.deephaven.project.ProjectType + plugins { id 'io.deephaven.common-conventions' } + +def verifyAllProjectsRegistered = project.tasks.register('verifyAllProjectsRegistered') { task -> + task.doLast { + project.allprojects { Project p -> + if (!ProjectType.isRegistered(p)) { + throw new IllegalStateException("Project '${project.name}' has not registered. Please apply the plugin 'io.deephaven.project.register'.") + } + } + } +} + +project.tasks + .getByName('quick') + .dependsOn verifyAllProjectsRegistered diff --git a/buildSrc/src/main/groovy/io/deephaven/project/ProjectType.groovy b/buildSrc/src/main/groovy/io/deephaven/project/ProjectType.groovy index 2b8e73dbd73..db1938ac996 100644 --- a/buildSrc/src/main/groovy/io/deephaven/project/ProjectType.groovy +++ b/buildSrc/src/main/groovy/io/deephaven/project/ProjectType.groovy @@ -1,11 +1,10 @@ package io.deephaven.project import groovy.transform.CompileStatic +import io.deephaven.project.util.JavaDependencies import org.gradle.api.Project -import org.gradle.api.Task -import org.gradle.api.artifacts.ProjectDependency +import org.gradle.api.plugins.JavaPlatformPlugin import org.gradle.api.plugins.JavaPlugin -import org.gradle.api.tasks.TaskProvider @CompileStatic enum ProjectType { @@ -15,25 +14,16 @@ enum ProjectType { JAVA_EXTERNAL(true, 'io.deephaven.project.java-external'), JAVA_LOCAL(false, 'io.deephaven.project.java-local'), JAVA_PUBLIC(true, 'io.deephaven.project.java-public'), + BOM_PUBLIC(true, 'io.deephaven.project.bom-public'), ROOT(false, 'io.deephaven.project.root'); - public static final String VERIFY_ALL_PROJECTS_REGISTERED_TASK_NAME = 'verifyAllProjectsRegistered' - - public static final String VERIFY_RUNTIME_CLASSPATH_IS_PUBLIC_TASK_NAME = 'verifyRuntimeClasspathIsPublic' - static void register(Project project) { ProjectType type = getType(project) if (type == ROOT && project.rootProject != project) { throw new IllegalStateException("Project '${project.name}' is likely inheriting the 'ROOT' type - please set the property 'io.deephaven.project.ProjectType' as appropriate.") } - - project.pluginManager.apply(type.pluginId) registerInternal(project, type) - if (type == ROOT) { - project.tasks - .getByName('quick') - .dependsOn verifyAllRegisteredTask(project) - } + project.pluginManager.apply(type.pluginId) } private static void registerInternal(Project project, ProjectType projectType) { @@ -44,51 +34,8 @@ enum ProjectType { " - is already registered with the type '${ext.get(key)}'") } ext.set(key, projectType) - if (projectType.isPublic) { - project.tasks - .getByName('quick') - .dependsOn verifyRuntimeClasspathIsPublicTask(project) - } } - private static TaskProvider verifyRuntimeClasspathIsPublicTask(Project project) { - return project.tasks.register(VERIFY_RUNTIME_CLASSPATH_IS_PUBLIC_TASK_NAME) { task -> - task.doLast { - verifyRuntimeClasspathIsPublic(project) - } - } - } - - private static void verifyRuntimeClasspathIsPublic(Project project) { - project - .configurations - .getByName(JavaPlugin.RUNTIME_CLASSPATH_CONFIGURATION_NAME) - .getAllDependencies() - .findAll { it instanceof ProjectDependency } - .collect { ((ProjectDependency)it).dependencyProject } - .each { - if (!isPublic(it)) { - throw new IllegalStateException("Public project '${project.name}' has a dependency on a non-public project '${it.name}'") - } - } - } - - private static TaskProvider verifyAllRegisteredTask(Project project) { - return project.tasks.register(VERIFY_ALL_PROJECTS_REGISTERED_TASK_NAME) { task -> - task.doLast { - project.allprojects { Project p -> - verifyRegistered(p) - } - } - } - } - - private static void verifyRegistered(Project project) { - def ext = project.extensions.extraProperties - if (!ext.has("${ProjectType.class.name}.isRegistered")) { - throw new IllegalStateException("Project '${project.name}' has not registered. Please apply the plugin 'io.deephaven.project.register'.") - } - } static ProjectType getType(Project project) { def typeString = project.findProperty('io.deephaven.project.ProjectType') as String @@ -98,6 +45,11 @@ enum ProjectType { return valueOf(typeString) } + static boolean isRegistered(Project project) { + def ext = project.extensions.extraProperties + return ext.has("${ProjectType.class.name}.isRegistered") + } + static boolean isPublic(Project project) { return getType(project).isPublic } diff --git a/buildSrc/src/main/groovy/io/deephaven/project/util/CombinedJavadoc.groovy b/buildSrc/src/main/groovy/io/deephaven/project/util/CombinedJavadoc.groovy new file mode 100644 index 00000000000..2ce37ba2681 --- /dev/null +++ b/buildSrc/src/main/groovy/io/deephaven/project/util/CombinedJavadoc.groovy @@ -0,0 +1,25 @@ +package io.deephaven.project.util + +import groovy.transform.CompileStatic +import io.deephaven.project.ProjectType +import org.gradle.api.Project + +@CompileStatic +class CombinedJavadoc { + + static boolean includeProject(Project p) { + ProjectType type = ProjectType.getType(p) + if (!type.isPublic) { + return false + } + switch (type) { + case ProjectType.BOM_PUBLIC: + return false + case ProjectType.JAVA_EXTERNAL: + case ProjectType.JAVA_PUBLIC: + return true + default: + throw new IllegalStateException("Unsure if public project type '${type}' is supposed to be included in combined-javadoc.") + } + } +} diff --git a/buildSrc/src/main/groovy/io/deephaven/project/util/JavaDependencies.groovy b/buildSrc/src/main/groovy/io/deephaven/project/util/JavaDependencies.groovy new file mode 100644 index 00000000000..44cc6fe3957 --- /dev/null +++ b/buildSrc/src/main/groovy/io/deephaven/project/util/JavaDependencies.groovy @@ -0,0 +1,67 @@ +package io.deephaven.project.util + +import groovy.transform.CompileStatic +import io.deephaven.project.ProjectType +import org.gradle.api.Project +import org.gradle.api.Task +import org.gradle.api.artifacts.Configuration +import org.gradle.api.artifacts.ProjectDependency +import org.gradle.api.internal.artifacts.dependencies.DefaultProjectDependencyConstraint +import org.gradle.api.plugins.JavaPlugin +import org.gradle.api.tasks.TaskProvider + +@CompileStatic +class JavaDependencies { + public static final String VERIFY_RUNTIME_CLASSPATH_IS_PUBLIC_TASK_NAME = 'verifyRuntimeClasspathIsPublic' + + public static final String VERIFY_ALL_CONFIGURATIONS_ARE_PUBLIC_TASK_NAME = 'verifyAllConfigurationsArePublic' + + static TaskProvider verifyRuntimeClasspathIsPublicTask(Project project) { + return project.tasks.register(VERIFY_RUNTIME_CLASSPATH_IS_PUBLIC_TASK_NAME) { task -> + task.doLast { + verifyRuntimeClasspathIsPublic(project) + } + } + } + + static TaskProvider verifyAllConfigurationsArePublicTask(Project project) { + return project.tasks.register(VERIFY_ALL_CONFIGURATIONS_ARE_PUBLIC_TASK_NAME) { task -> + task.doLast { + verifyAllConfigurationsArePublic(project) + } + } + } + + private static void verifyRuntimeClasspathIsPublic(Project project) { + def runtimeClasspath = project.configurations.getByName(JavaPlugin.RUNTIME_CLASSPATH_CONFIGURATION_NAME) + verifyConfigurationHasPublicDependencies(project, runtimeClasspath) + } + + + private static void verifyAllConfigurationsArePublic(Project project) { + for (Configuration configuration : project.configurations) { + verifyConfigurationHasPublicDependencies(project, configuration) + } + } + + private static void verifyConfigurationHasPublicDependencies(Project project, Configuration configuration) { + configuration + .getAllDependencies() + .findAll { it instanceof ProjectDependency } + .collect { ((ProjectDependency)it).dependencyProject } + .each { + if (!ProjectType.isPublic(it)) { + throw new IllegalStateException("Project '${project.name}' [${ProjectType.getType(project)}] has a dependency on a non-public project '${it.name}' [${ProjectType.getType(it)}]") + } + } + configuration + .getAllDependencyConstraints() + .findAll { it instanceof DefaultProjectDependencyConstraint } + .collect {((DefaultProjectDependencyConstraint)it).projectDependency.dependencyProject } + .each { + if (!ProjectType.isPublic(it)) { + throw new IllegalStateException("Project '${project.name}' [${ProjectType.getType(project)}] has a dependency constraint on a non-public project '${it.name}' [${ProjectType.getType(it)}]") + } + } + } +} diff --git a/buildSrc/src/main/groovy/io/deephaven/project/util/PublishingConstants.groovy b/buildSrc/src/main/groovy/io/deephaven/project/util/PublishingConstants.groovy new file mode 100644 index 00000000000..3d0f7b5346a --- /dev/null +++ b/buildSrc/src/main/groovy/io/deephaven/project/util/PublishingConstants.groovy @@ -0,0 +1,123 @@ +package io.deephaven.project.util + +import groovy.transform.CompileStatic +import org.gradle.api.Project +import org.gradle.api.Task +import org.gradle.api.artifacts.repositories.MavenArtifactRepository +import org.gradle.api.artifacts.repositories.PasswordCredentials +import org.gradle.api.plugins.BasePluginConvention +import org.gradle.api.publish.Publication +import org.gradle.api.publish.PublishingExtension +import org.gradle.api.publish.maven.MavenPublication +import org.gradle.api.tasks.TaskProvider +import org.gradle.plugins.signing.SigningExtension + +@CompileStatic +class PublishingConstants { + static final String DEVELOPER_ID = 'deephaven' + static final String DEVELOPER_NAME = 'Deephaven Developers' + static final String DEVELOPER_EMAIL = 'developers@deephaven.io' + + static final String PROJECT_URL = 'https://github.com/deephaven/deephaven-core' + static final String ORG_NAME = 'Deephaven Data Labs' + static final String ORG_URL = 'https://deephaven.io/' + + static final String ISSUES_SYSTEM = 'GitHub Issues' + static final String ISSUES_URL = 'https://github.com/deephaven/deephaven-core/issues' + + static final String SCM_URL = 'https://github.com/deephaven/deephaven-core' + static final String SCM_CONNECTION = 'scm:git:git://github.com/deephaven/deephaven-core.git' + static final String SCM_DEV_CONNECTION = 'scm:git:ssh://github.com/deephaven/deephaven-core.git' + + static final String REPO_NAME = 'ossrh' + static final String SNAPSHOT_REPO = 'https://s01.oss.sonatype.org/content/repositories/snapshots/' + static final String RELEASE_REPO = 'https://s01.oss.sonatype.org/service/local/staging/deploy/maven2/' + + static void setupRepositories(Project project) { + PublishingExtension publishingExtension = project.extensions.getByType(PublishingExtension) + publishingExtension.repositories { repoHandler -> + repoHandler.maven { MavenArtifactRepository repo -> + repo.name = REPO_NAME + repo.url = ((String)project.version).endsWith('SNAPSHOT') ? SNAPSHOT_REPO : RELEASE_REPO + // ossrhUsername, ossrhPassword + repo.credentials(PasswordCredentials) + } + } + } + + static void setupMavenPublication(Project project, MavenPublication mavenPublication) { + mavenPublication.pom {pom -> + pom.url.set PROJECT_URL + pom.organization {org -> + org.name.set ORG_NAME + org.url.set ORG_URL + } + pom.scm { scm -> + scm.url.set SCM_URL + scm.connection.set SCM_CONNECTION + scm.developerConnection.set SCM_DEV_CONNECTION + } + pom.issueManagement { im -> + im.system.set ISSUES_SYSTEM + im.url.set ISSUES_URL + } + pom.developers { devs -> + devs.developer { dev -> + dev.id.set DEVELOPER_ID + dev.name.set DEVELOPER_NAME + dev.email.set DEVELOPER_EMAIL + dev.organization.set ORG_NAME + dev.organizationUrl.set ORG_URL + } + } + } + + def publishToOssrhTask = project.tasks.getByName("publish${mavenPublication.getName().capitalize()}PublicationToOssrhRepository") + + publishToOssrhTask.dependsOn assertIsReleaseTask(project) + + project.afterEvaluate { Project p -> + // https://central.sonatype.org/publish/requirements/ + if (p.description == null) { + throw new IllegalStateException("Project '${project.name}' is missing a description, which is required for publishing to maven central") + } + BasePluginConvention base = p.convention.getPlugin(BasePluginConvention) + // The common-conventions plugin should take care of this, but we'll double-check here + if (!base.archivesBaseName.contains('deephaven')) { + throw new IllegalStateException("Project '${project.name}' archiveBaseName '${base.archivesBaseName}' does not contain 'deephaven'") + } + mavenPublication.artifactId = base.archivesBaseName + mavenPublication.pom { pom -> + pom.name.set base.archivesBaseName + pom.description.set p.description + } + } + } + + static void setupSigning(Project project, Publication publication) { + SigningExtension publishingExtension = project.extensions.getByType(SigningExtension) + publishingExtension.sign(publication) + String signingKey = project.findProperty('signingKey') + String signingPassword = project.findProperty('signingPassword') + if (signingKey != null && signingPassword != null) { + // In CI, it's harder to pass a file; so if specified, we use the in-memory version. + publishingExtension.useInMemoryPgpKeys(signingKey, signingPassword) + } + } + + static TaskProvider assertIsReleaseTask(Project p) { + // todo: can we register this task once globally instead? + return p.tasks.register('assertIsRelease') {task -> + task.doLast { + if (System.getenv('CI') != 'true') { + throw new IllegalStateException('Release error: env CI must be true') + } + def actualGithubRef = System.getenv('GITHUB_REF') + def expectedGithubRef = "refs/heads/release/v${p.version}" + if (actualGithubRef != expectedGithubRef) { + throw new IllegalStateException("Release error: env GITHUB_REF '${actualGithubRef}' does not match expected '${expectedGithubRef}'. Bad tag? Bump version?") + } + } + } + } +} diff --git a/combined-javadoc/build.gradle b/combined-javadoc/build.gradle index f53d9f57a9b..86812bce08a 100644 --- a/combined-javadoc/build.gradle +++ b/combined-javadoc/build.gradle @@ -40,19 +40,19 @@ def allJavadoc = tasks.register 'allJavadoc', Javadoc, { jdoc.options.links = ['https://docs.oracle.com/en/java/javase/11/docs/api/'] jdoc.options.addStringOption('Xdoclint:none', '-quiet') - def isPublic = { Project p -> return io.deephaven.project.ProjectType.isPublic(p) } + def isForJavadocs = { Project p -> return io.deephaven.project.util.CombinedJavadoc.includeProject(p) } jdoc.source = rootProject.subprojects - .findAll { it -> isPublic(it) } + .findAll { it -> isForJavadocs(it) } .collect { it.sourceSets.main.allJava } jdoc.classpath = files(rootProject.subprojects - .findAll { it -> isPublic(it) } + .findAll { it -> isForJavadocs(it) } .collect { it.sourceSets.main.compileClasspath }) // https://github.com/gradle/gradle/issues/19869 def sourcepath = files() - rootProject.subprojects.findAll{ it -> isPublic(it) } .each { + rootProject.subprojects.findAll{ it -> isForJavadocs(it) } .each { sourcepath = sourcepath + (FileCollection) it.sourceSets.main.allJava.getSourceDirectories() } options.addStringOption('sourcepath', sourcepath.getAsPath()) diff --git a/settings.gradle b/settings.gradle index 6d29b87d412..e3402712a63 100644 --- a/settings.gradle +++ b/settings.gradle @@ -57,6 +57,8 @@ pyMods.each { project(":$name").projectDir = file(dir) } +include 'bom' + include(':configs') project(':configs').projectDir = file('props/configs')