/* * Copyright (c) 2024. JetBrains s.r.o. * Use of this source code is governed by the MIT license that can be found in the LICENSE file. */ // okhttp3 added for publishing to the Sonatype Central Repository: import okhttp3.MultipartBody import okhttp3.OkHttpClient import okhttp3.Request import okhttp3.RequestBody.Companion.asRequestBody import org.gradle.internal.os.OperatingSystem import org.gradle.jvm.tasks.Jar import org.jetbrains.kotlin.gradle.dsl.JvmTarget import org.jetbrains.kotlin.gradle.dsl.KotlinCommonCompilerOptions import org.jetbrains.kotlin.gradle.dsl.KotlinJvmProjectExtension import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension import org.jetbrains.kotlin.gradle.tasks.KotlinJvmCompile import java.io.FileNotFoundException import java.util.* buildscript { dependencies { classpath("com.squareup.okhttp3:okhttp:4.12.0") } } plugins { kotlin("multiplatform") apply false kotlin("js") apply false } fun ExtraPropertiesExtension.getOrNull(name: String): Any? = if (has(name)) { get(name) } else { null } val os: OperatingSystem = OperatingSystem.current() val letsPlotTaskGroup by extra { "lets-plot" } allprojects { group = "org.jetbrains.lets-plot" version = "4.8.3-SNAPSHOT" // see also: python-package/lets_plot/_version.py // version = "0.0.0-SNAPSHOT" // for local publishing only // Generate JVM 1.8 bytecode tasks.withType().configureEach { compilerOptions.jvmTarget.set(JvmTarget.JVM_1_8) } tasks.withType().configureEach { sourceCompatibility = "1.8" targetCompatibility = "1.8" } repositories { mavenCentral() } } // Read build settings from commandline parameters (for build_release.py script): fun readPropertiesFromParameters() { val properties = Properties() if (project.hasProperty("enable_python_package")) { properties["enable_python_package"] = project.property("enable_python_package") } if (properties.getProperty("enable_python_package").toBoolean()) { properties["python.bin_path"] = project.property("python.bin_path") properties["python.include_path"] = project.property("python.include_path") } if (!os.isWindows) { properties["architecture"] = project.property("architecture") } for (property in properties) { extra[property.key as String] = property.value } } // Read build settings from local.properties: fun readPropertiesFromFile() { val properties = Properties() val localPropsFileName = "local.properties" if (project.file(localPropsFileName).exists()) { properties.load(project.file(localPropsFileName).inputStream()) } else { throw FileNotFoundException( "$localPropsFileName file not found!\n" + "Check ${localPropsFileName}_template file for the template." ) } if (!os.isWindows) { // Only 64bit version can be built for Windows, so the arch parameter is not needed and may not be set. assert(properties["architecture"] != null) } if (properties.getProperty("enable_python_package").toBoolean()) { val pythonBinPath = properties["python.bin_path"] val pythonIncludePath = properties["python.include_path"] assert(pythonBinPath != null) assert(pythonIncludePath != null) if (!os.isWindows) { val execResult = providers.exec { commandLine( "${pythonBinPath}/python", "-c", "import platform; print(platform.machine())" ) } val getArchOutput = execResult.standardOutput.asText.get() val currentPythonArch = getArchOutput.toString().trim() if (currentPythonArch != properties["architecture"]) { throw IllegalArgumentException( "Project and Python architectures don't match!\n" + " - Value, from your '${localPropsFileName}' file: ${properties["architecture"]}\n" + " - Your Python architecture: ${currentPythonArch}\n" + "Check your '${localPropsFileName}' file." ) } } } for (property in properties) { extra[property.key as String] = property.value } } // For build_release.py settings will be read from commandline parameters. // In other cases, settings will be read from local.properties. if (project.hasProperty("build_release")) { readPropertiesFromParameters() } else { readPropertiesFromFile() } // Maven publication settings: // define local Maven Repository path: val localMavenRepository by extra { "$rootDir/.maven-publish-dev-repo" } // define the Maven Repository URL. Currently set to a local path for uploading // artifacts to the Sonatype Central Repository. val mavenReleasePublishUrl by extra { layout.buildDirectory.dir("maven/artifacts").get().toString() } // define Maven Snapshot repository URL. val mavenSnapshotPublishUrl by extra { "https://central.sonatype.com/repository/maven-snapshots/" } // define Sonatype Central Repository settings: val sonatypeUsername by extra { extra.getOrNull("sonatype.username") ?: "" } val sonatypePassword by extra { extra.getOrNull("sonatype.password") ?: "" } // Publish some sub-projects as Kotlin Multi-project libraries. val publishLetsPlotCoreModulesToMavenLocalRepository by tasks.registering { group = letsPlotTaskGroup } val publishLetsPlotCoreModulesToMavenRepository by tasks.registering { group = letsPlotTaskGroup } // Configure a workaround tasks for publishing to the Sonatype Central Repository, // as there is currently no official Gradle plugin support. // Refer to documentation: https://central.sonatype.org/publish/publish-portal-gradle/ val packageMavenArtifacts by tasks.registering(Zip::class) { group = letsPlotTaskGroup from(mavenReleasePublishUrl) archiveFileName.set("${project.name}-artifacts.zip") destinationDirectory.set(layout.buildDirectory) } val uploadMavenArtifacts by tasks.registering { group = letsPlotTaskGroup dependsOn(packageMavenArtifacts) doLast { val uriBase = "https://central.sonatype.com/api/v1/publisher/upload" val publishingType = "USER_MANAGED" val deploymentName = "${project.name}-$version" val uri = "$uriBase?name=$deploymentName&publishingType=$publishingType" val userName = sonatypeUsername as String val password = sonatypePassword as String val base64Auth = Base64.getEncoder().encode("$userName:$password".toByteArray()).toString(Charsets.UTF_8) val bundleFile = packageMavenArtifacts.get().archiveFile.get().asFile println("Sending request to $uri...") val client = OkHttpClient() val request = Request.Builder() .url(uri) .header("Authorization", "Bearer $base64Auth") .post( MultipartBody.Builder() .setType(MultipartBody.FORM) .addFormDataPart("bundle", bundleFile.name, bundleFile.asRequestBody()) .build() ) .build() client.newCall(request).execute().use { response -> val statusCode = response.code println("Upload status code: $statusCode") println("Upload result: ${response.body!!.string()}") if (statusCode != 201) { error("Upload error to Central repository. Status code $statusCode.") } } } } val defaultImageMagickLibPath = rootDir.path + "/platf-imagick/deps" val imageMagickLibPath = project.findProperty("imagemagick_lib_path") as? String ?: System.getenv("IMAGICK_LIB_PATH") ?: defaultImageMagickLibPath extra.set("imagemagick_lib_path", imageMagickLibPath) val initImageMagick by tasks.registering { group = letsPlotTaskGroup doLast { exec { this.workingDir = File(rootDir.path + "/platf-imagick") commandLine( "./init_imagemagick.sh", imageMagickLibPath ) } } } // Generating JavaDoc task for each publication task. // Fixes "Task ':canvas:publishJsPublicationToMavenRepository' uses this output of task ':canvas:signJvmPublication' // without declaring an explicit or implicit dependency" error. // Issues: // - https://github.com/gradle-nexus/publish-plugin/issues/208 // - https://github.com/gradle/gradle/issues/26091 // fun getJarJavaDocsTask(distributeName: String): TaskProvider { return tasks.register("${distributeName}JarJavaDoc") { archiveClassifier.set("javadoc") from("$rootDir/README.md") archiveBaseName.set(distributeName) } } // Configure native targets for python-extension dependencies. subprojects { val pythonExtensionModules = listOf( "commons", "canvas", "datamodel", "plot-base", "plot-builder", "plot-stem", "plot-raster", "demo-and-test-shared", "demo-common-svg", "demo-svg-native", ) val projectArchitecture = rootProject.extra.getOrNull("architecture") if (name in pythonExtensionModules) { apply(plugin = "org.jetbrains.kotlin.multiplatform") configure { if (os.isMacOsX && projectArchitecture == "x86_64") { macosX64() } else if (os.isMacOsX && projectArchitecture == "arm64") { if (project.hasProperty("build_release")) { macosX64() macosArm64() } else { macosArm64() } } else if (os.isLinux) { if (project.hasProperty("build_release")) { linuxX64() linuxArm64() } else if (projectArchitecture == "x86_64") { linuxX64() } } else if (os.isWindows) { mingwX64() } else { throw Exception("Unsupported platform! Check project settings.") } } } } // Configure Lets-Plot Core multiplatform modules. val multiPlatformCoreModulesForPublish = listOf( "commons", "datamodel", "canvas", "gis", "livemap", "plot-base", "plot-builder", "plot-raster", "plot-stem", "plot-livemap" ) subprojects { if (name in multiPlatformCoreModulesForPublish) { apply(plugin = "org.jetbrains.kotlin.multiplatform") // For `jvmSourcesJar` task: configure { jvm() } } } // Configure Lets-Plot Core JVM modules. val jvmCoreModulesForPublish = listOf( "platf-awt", "platf-batik", ) subprojects { if (name in jvmCoreModulesForPublish) { apply(plugin = "org.jetbrains.kotlin.jvm") apply(plugin = "maven-publish") configure { tasks.register("${name}-sources") { archiveClassifier.set("sources") from(sourceSets.getByName("main").kotlin.srcDirs) } } configure { publications { register(name, MavenPublication::class) { groupId = project.group as String artifactId = name version = project.version as String artifact(tasks["jar"]) artifact(tasks["${name}-sources"]) } } } } } // Configure Maven publication for Lets-Plot Core modules. subprojects { if (name in multiPlatformCoreModulesForPublish + jvmCoreModulesForPublish) { apply(plugin = "maven-publish") apply(plugin = "signing") // Do not publish 'native' targets: val targetsToPublish = listOf( "platf-awt", "platf-batik", "jvm", "js", "kotlinMultiplatform", "metadata" ) configure { publications { withType(MavenPublication::class) { if (name in targetsToPublish) { // Configure this publication. artifact(getJarJavaDocsTask("${name}-${project.name}")) pom { name = "Lets-Plot core artifact" description = "A part of the Lets-Plot library." url = "https://github.com/JetBrains/lets-plot" licenses { license { name = "MIT" url = "https://raw.githubusercontent.com/JetBrains/lets-plot/master/LICENSE" } } developers { developer { id = "jetbrains" name = "JetBrains" email = "lets-plot@jetbrains.com" } } scm { url = "https://github.com/JetBrains/lets-plot" } } } } } repositories { mavenLocal { url = uri(localMavenRepository) } maven { if (version.toString().endsWith("-SNAPSHOT")) { url = uri(mavenSnapshotPublishUrl) credentials { username = sonatypeUsername.toString() password = sonatypePassword.toString() } } else { url = uri(mavenReleasePublishUrl) } } } } afterEvaluate { // Add LICENSE file to the META-INF folder inside published JAR files. tasks.filterIsInstance() .forEach { if (it.name == "jvmJar" || it.name == "jar") { // "jar" for 'org.jetbrains.kotlin.jvm' plugin it.metaInf { from("$rootDir") { include("LICENSE") } } } } // Configure artifacts signing process for release versions. val publicationsToSign = mutableListOf() for (task in tasks.withType(PublishToMavenRepository::class)) { if (task.publication.name in targetsToPublish) { val repoName = task.repository.name if (repoName == "MavenLocal") { publishLetsPlotCoreModulesToMavenLocalRepository.configure { dependsOn += task } } else if (repoName == "maven") { publishLetsPlotCoreModulesToMavenRepository.configure { dependsOn += task } publicationsToSign.add(task.publication) } else { throw IllegalStateException("Repository expected: 'MavenLocal' or 'maven' but was: '$repoName'.") } } } // Sign artifacts. publicationsToSign.forEach { if (!project.version.toString().contains("SNAPSHOT")) { configure { sign(it) } } } } } } // Fix warnings in all projects. subprojects { fun KotlinCommonCompilerOptions.configCompilerWarnings() { freeCompilerArgs.addAll( // Suppress expect/actual classes are in Beta warning. "-Xexpect-actual-classes", // Non-public primary constructor is exposed via the generated 'copy()' method of the 'data' class. // Kotlin 2.0 feature. //"-Xconsistent-data-class-copy-visibility", // Enable all warnings as errors. // Disabled because even with the Suppress("unused") the warnings may still happen: // (https://github.com/JetBrains/lets-plot/blob/f5af69befdd2fa963672d3b1d9992f3635f64840/plot-base/src/commonMain/kotlin/org/jetbrains/letsPlot/core/interact/mouse/MouseDragInteraction.kt#L70) //"-Werror" ) } plugins.withId("org.jetbrains.kotlin.multiplatform") { extensions.configure { targets.configureEach { compilations.configureEach { compileTaskProvider.get().compilerOptions { configCompilerWarnings() } } } } } // Koltin 2.0 //plugins.withId("org.jetbrains.kotlin.jvm") { // extensions.configure { // compilerOptions { // configCompilerWarnings() // jvmTarget.set(JvmTarget.JVM_1_8) // } // } //} plugins.withId("org.jetbrains.kotlin.jvm") { extensions.configure { compilerOptions { configCompilerWarnings() jvmTarget.set(JvmTarget.JVM_1_8) } } } }