KT-50033 Add missing public API packages to module-info and setup a test
The test checks that new packages are not accidentally non-exported, so each new stdlib package must be either exported or specified in that test's expected non-exported package list.
This commit is contained in:
@@ -46,6 +46,7 @@ class CodeConformanceTest : TestCase() {
|
|||||||
"libraries/kotlin.test/js/it/node_modules",
|
"libraries/kotlin.test/js/it/node_modules",
|
||||||
"libraries/reflect/api/src/java9/java/kotlin/reflect/jvm/internal/impl",
|
"libraries/reflect/api/src/java9/java/kotlin/reflect/jvm/internal/impl",
|
||||||
"libraries/reflect/build",
|
"libraries/reflect/build",
|
||||||
|
"libraries/stdlib/jdk8/moduleTest/NonExportedPackagesTest.kt",
|
||||||
"libraries/stdlib/js-ir/.gradle",
|
"libraries/stdlib/js-ir/.gradle",
|
||||||
"libraries/stdlib/js-ir/build",
|
"libraries/stdlib/js-ir/build",
|
||||||
"libraries/stdlib/js-ir-minimal-for-test/.gradle",
|
"libraries/stdlib/js-ir-minimal-for-test/.gradle",
|
||||||
|
|||||||
@@ -34,6 +34,19 @@ sourceSets {
|
|||||||
srcDir 'java9'
|
srcDir 'java9'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
moduleTest {
|
||||||
|
java {
|
||||||
|
srcDir 'moduleTest'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
moduleTestApi project(':kotlin-stdlib')
|
||||||
|
moduleTestApi project(':kotlin-stdlib-jdk7')
|
||||||
|
moduleTestCompileOnly project
|
||||||
|
moduleTestApi project(':kotlin-test:kotlin-test-junit')
|
||||||
|
moduleTestApi "org.ow2.asm:asm:9.0"
|
||||||
}
|
}
|
||||||
|
|
||||||
jar {
|
jar {
|
||||||
@@ -94,11 +107,23 @@ task testJdk6Tests(type: Test) { thisTask ->
|
|||||||
}
|
}
|
||||||
|
|
||||||
[JdkMajorVersion.JDK_9, JdkMajorVersion.JDK_10, JdkMajorVersion.JDK_11].forEach { jvmVersion ->
|
[JdkMajorVersion.JDK_9, JdkMajorVersion.JDK_10, JdkMajorVersion.JDK_11].forEach { jvmVersion ->
|
||||||
tasks.register("jdk${jvmVersion.majorVersion}Test", Test) { thisTask ->
|
check.dependsOn(tasks.register("jdk${jvmVersion.majorVersion}Test", Test) { thisTask ->
|
||||||
check.dependsOn(thisTask)
|
|
||||||
group = "verification"
|
group = "verification"
|
||||||
thisTask.javaLauncher.set(
|
thisTask.javaLauncher.set(JvmToolchain.getToolchainLauncherFor(project, jvmVersion))
|
||||||
JvmToolchain.getToolchainLauncherFor(project, jvmVersion)
|
})
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
compileModuleTestKotlin {
|
||||||
|
kotlinJavaToolchain.toolchain.use(JvmToolchain.getToolchainLauncherFor(project, JdkMajorVersion.JDK_9))
|
||||||
|
}
|
||||||
|
|
||||||
|
check.dependsOn(tasks.register("moduleInfoTest", Test) {test ->
|
||||||
|
test.dependsOn(moduleTestClasses)
|
||||||
|
test.group = "verification"
|
||||||
|
test.testClassesDirs = sourceSets.moduleTest.output.classesDirs
|
||||||
|
test.classpath = files(sourceSets.moduleTest.runtimeClasspath, tasks.jar)
|
||||||
|
test.javaLauncher.set(JvmToolchain.getToolchainLauncherFor(project, JdkMajorVersion.JDK_9))
|
||||||
|
doFirst {
|
||||||
|
test.systemProperty("stdlibJars", test.classpath.filter { it.name.contains('kotlin-stdlib') }.join(File.pathSeparator))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|||||||
@@ -3,9 +3,11 @@ module kotlin.stdlib.jdk8 {
|
|||||||
requires transitive kotlin.stdlib;
|
requires transitive kotlin.stdlib;
|
||||||
requires kotlin.stdlib.jdk7;
|
requires kotlin.stdlib.jdk7;
|
||||||
|
|
||||||
|
exports kotlin.jvm.jdk8;
|
||||||
exports kotlin.collections.jdk8;
|
exports kotlin.collections.jdk8;
|
||||||
exports kotlin.streams.jdk8;
|
exports kotlin.streams.jdk8;
|
||||||
exports kotlin.text.jdk8;
|
exports kotlin.text.jdk8;
|
||||||
|
exports kotlin.time.jdk8;
|
||||||
|
|
||||||
opens kotlin.internal.jdk8 to kotlin.stdlib;
|
opens kotlin.internal.jdk8 to kotlin.stdlib;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,82 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2010-2021 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||||
|
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||||
|
*/
|
||||||
|
package test.jdk9
|
||||||
|
|
||||||
|
import org.objectweb.asm.*
|
||||||
|
import java.io.File
|
||||||
|
import java.lang.module.ModuleDescriptor
|
||||||
|
import java.util.jar.JarFile
|
||||||
|
import kotlin.test.*
|
||||||
|
|
||||||
|
class NonExportedPackagesTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun stdlib() {
|
||||||
|
checkNonExportedPackages("kotlin-stdlib", setOf(
|
||||||
|
"kotlin.collections.builders",
|
||||||
|
"kotlin.js",
|
||||||
|
"kotlin.jvm.internal.unsafe",
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun stdlibJdk7() {
|
||||||
|
checkNonExportedPackages("kotlin-stdlib-jdk7", setOf(
|
||||||
|
"kotlin.internal.jdk7",
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun stdlibJdk8() {
|
||||||
|
checkNonExportedPackages("kotlin-stdlib-jdk8", setOf(
|
||||||
|
"kotlin.internal.jdk8",
|
||||||
|
"kotlin.random.jdk8",
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun checkNonExportedPackages(jarShortName: String, expectedPackages: Set<String>) {
|
||||||
|
val file = findJar(jarShortName)
|
||||||
|
JarFile(file).use { jar ->
|
||||||
|
val moduleInfoEntry = jar.getJarEntry("META-INF/versions/9/module-info.class") ?: error("module-info is not found in $file")
|
||||||
|
val descriptor = jar.getInputStream(moduleInfoEntry).use { infoStream -> ModuleDescriptor.read(infoStream) }
|
||||||
|
val packages = mutableSetOf<String>()
|
||||||
|
|
||||||
|
for (entry in jar.entries()) {
|
||||||
|
if (entry.isDirectory) continue
|
||||||
|
val name = entry.name
|
||||||
|
if (name == moduleInfoEntry.name) continue
|
||||||
|
if (name.endsWith(".class", ignoreCase = true) &&
|
||||||
|
(!name.startsWith("META-INF", ignoreCase = true)) ||
|
||||||
|
name.startsWith("META-INF/versions", ignoreCase = true)
|
||||||
|
) {
|
||||||
|
jar.getInputStream(entry).use { classStream ->
|
||||||
|
val visitor = ClassFqnVisitor()
|
||||||
|
ClassReader(classStream).accept(visitor, ClassReader.SKIP_CODE)
|
||||||
|
visitor.fqname?.run { substringBeforeLast('/').replace('/', '.') }?.let(packages::add)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val nonExported = packages - descriptor.exports().filter { it.targets().isEmpty() }.map { it.source() }
|
||||||
|
assertEquals(expectedPackages, nonExported)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
private fun findJar(shortName: String): File {
|
||||||
|
val jars = System.getProperty("stdlibJars").split(File.pathSeparator)
|
||||||
|
return jars.map(::File).single { it.name.matches(Regex("""${Regex.escape(shortName)}(?!-[a-z]).+\.jar""")) }
|
||||||
|
}
|
||||||
|
|
||||||
|
private class ClassFqnVisitor : ClassVisitor(Opcodes.ASM9) {
|
||||||
|
var fqname: String? = null
|
||||||
|
|
||||||
|
override fun visit(version: Int, access: Int, name: String?, signature: String?, superName: String?, interfaces: Array<out String>?) {
|
||||||
|
super.visit(version, access, name, signature, superName, interfaces)
|
||||||
|
fqname = name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -8,6 +8,7 @@ module kotlin.stdlib {
|
|||||||
exports kotlin.concurrent;
|
exports kotlin.concurrent;
|
||||||
exports kotlin.contracts;
|
exports kotlin.contracts;
|
||||||
exports kotlin.coroutines;
|
exports kotlin.coroutines;
|
||||||
|
exports kotlin.coroutines.cancellation;
|
||||||
exports kotlin.coroutines.intrinsics;
|
exports kotlin.coroutines.intrinsics;
|
||||||
exports kotlin.coroutines.jvm.internal;
|
exports kotlin.coroutines.jvm.internal;
|
||||||
exports kotlin.io;
|
exports kotlin.io;
|
||||||
|
|||||||
Reference in New Issue
Block a user