import aQute.bnd.gradle.BundleTaskExtension
import aQute.bnd.gradle.Resolve

plugins {
	`java-library`
}

val importAPIGuardian = "org.apiguardian.*;resolution:=\"optional\""

// This task enhances `jar` and `shadowJar` tasks with the bnd
// `BundleTaskExtension` extension which allows for generating OSGi
// metadata into the jar
tasks.withType<Jar>().matching {
	task: Jar -> task.name == "jar" || task.name == "shadowJar"
}.configureEach {
	val bte = extensions.create<BundleTaskExtension>(BundleTaskExtension.NAME, this)

	extra["importAPIGuardian"] = importAPIGuardian

	// These are bnd instructions necessary for generating OSGi metadata.
	// We've generalized these so that they are widely applicable limiting
	// module configurations to special cases.
	bte.setBnd("""
			# Set the Bundle-SymbolicName to the archiveBaseName.
			# We don't use the archiveClassifier which Bnd will use
			# in the default Bundle-SymbolicName value.
			Bundle-SymbolicName: ${'$'}{task.archiveBaseName}

			# Set the Bundle-Name from the project description
			Bundle-Name: ${'$'}{project.description}

			# These are the general rules for package imports.
			Import-Package: \
				${importAPIGuardian},\
				org.junit.platform.commons.logging;status=INTERNAL,\
				kotlin.*;resolution:="optional",\
				*

			# This tells bnd not to complain if a module doesn't actually import
			# the kotlin and apiguardian packages, but enough modules do to make it a default.
			-fixupmessages.kotlin.import: "Unused Import-Package instructions: \\[kotlin.*\\]";is:=ignore
			-fixupmessages.apiguardian.import: "Unused Import-Package instructions: \\[org.apiguardian.*\\]";is:=ignore

			# This tells bnd to ignore classes it finds in `META-INF/versions/`
			# because bnd doesn't yet support multi-release jars.
			-fixupmessages.wrong.dir: "Classes found in the wrong directory: \\{META-INF/versions/...";is:=ignore

			# Don't scan for Class.forName package imports.
			# See https://bnd.bndtools.org/instructions/noclassforname.html
			-noclassforname: true

			# Don't add all the extra headers bnd normally adds.
			# See https://bnd.bndtools.org/instructions/noextraheaders.html
			-noextraheaders: true

			# Don't add the Private-Package header.
			# See https://bnd.bndtools.org/instructions/removeheaders.html
			-removeheaders: Private-Package

			# Instruct the APIGuardianAnnotations how to operate.
			# See https://bnd.bndtools.org/instructions/export-apiguardian.html
			-export-apiguardian: *;version=${'$'}{versionmask;===;${'$'}{version_cleanup;${'$'}{task.archiveVersion}}}
		""")

	// Do the actual work putting OSGi stuff in the jar.
	doLast(bte.buildAction())
}

// Bnd's Resolve task uses a properties file for its configuration. This
// task writes out the properties necessary for it to verify the OSGi
// metadata.
val osgiProperties by tasks.registering(WriteProperties::class) {
	setOutputFile(layout.buildDirectory.file("verifyOSGiProperties.bndrun"))
	property("-standalone", true)
	project.extensions.getByType(JavaLibraryExtension::class.java).let { javaLibrary ->
		property("-runee", "JavaSE-${javaLibrary.mainJavaVersion}")
	}
	property("-runrequires", "osgi.identity;filter:='(osgi.identity=${project.name})'")
	property("-runsystempackages", "jdk.internal.misc,jdk.jfr,sun.misc")
	// API Guardian should be optional -> instruct resolver to ignore it
	// during resolution. Resolve should still pass.
	property("-runblacklist", "org.apiguardian.api")
}

val osgiVerification by configurations.creating {
	extendsFrom(configurations.runtimeClasspath.get())
}

// Bnd's Resolve task is what verifies that a jar can be used in OSGi and
// that its metadata is valid. If the metadata is invalid this task will
// fail.
val verifyOSGi by tasks.registering(Resolve::class) {
	bndrun.fileProvider(osgiProperties.map{ it.outputFile })
	outputBndrun.set(layout.buildDirectory.file("resolvedOSGiProperties.bndrun"))
	isReportOptional = false
	// By default bnd will use jars found in:
	// 1. project.sourceSets.main.runtimeClasspath
	// 2. project.configurations.archives.artifacts.files
	// to validate the metadata.
	// This adds jars defined in `osgiVerification` also so that bnd
	// can use them to validate the metadata without causing those to
	// end up in the dependencies of those projects.
	bundles(osgiVerification)
}

tasks.check {
	dependsOn(verifyOSGi)
}
