Build: Fix kotlin-native tasks input annotations
Required for Gradle 7+ #KTI-559
This commit is contained in:
@@ -13,7 +13,7 @@ open class CopyCommonSources : DefaultTask() {
|
|||||||
var zipSources: Boolean = false
|
var zipSources: Boolean = false
|
||||||
|
|
||||||
@InputFiles
|
@InputFiles
|
||||||
var sourcePaths: ConfigurableFileCollection = project.files()
|
val sourcePaths: ConfigurableFileCollection = project.files()
|
||||||
|
|
||||||
@OutputDirectory
|
@OutputDirectory
|
||||||
var outputDir: File = project.buildDir.resolve("sources")
|
var outputDir: File = project.buildDir.resolve("sources")
|
||||||
@@ -27,7 +27,7 @@ open class CopyCommonSources : DefaultTask() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun sourcePaths(paths: Any) {
|
fun sourcePaths(paths: Any) {
|
||||||
sourcePaths = project.files(paths)
|
sourcePaths.setFrom(paths)
|
||||||
}
|
}
|
||||||
|
|
||||||
@TaskAction
|
@TaskAction
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import org.gradle.api.DefaultTask
|
|||||||
import org.gradle.api.Task
|
import org.gradle.api.Task
|
||||||
import org.gradle.api.tasks.Input
|
import org.gradle.api.tasks.Input
|
||||||
import org.gradle.api.tasks.TaskAction
|
import org.gradle.api.tasks.TaskAction
|
||||||
|
import org.gradle.api.tasks.Internal
|
||||||
import org.jetbrains.kotlin.konan.target.AppleConfigurables
|
import org.jetbrains.kotlin.konan.target.AppleConfigurables
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -49,6 +50,7 @@ open class CoverageTest : DefaultTask() {
|
|||||||
@Input
|
@Input
|
||||||
var numberOfCoveredLines: Int? = null
|
var numberOfCoveredLines: Int? = null
|
||||||
|
|
||||||
|
@get:Internal
|
||||||
val profrawFile: String by lazy {
|
val profrawFile: String by lazy {
|
||||||
"${project.buildDir.absolutePath}/$binaryName.profraw"
|
"${project.buildDir.absolutePath}/$binaryName.profraw"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import org.gradle.api.DefaultTask
|
|||||||
import org.gradle.api.Task
|
import org.gradle.api.Task
|
||||||
import org.gradle.api.file.FileTree
|
import org.gradle.api.file.FileTree
|
||||||
import org.gradle.api.tasks.Input
|
import org.gradle.api.tasks.Input
|
||||||
|
import org.gradle.api.tasks.Internal
|
||||||
import org.gradle.api.tasks.Optional
|
import org.gradle.api.tasks.Optional
|
||||||
import org.gradle.api.tasks.TaskAction
|
import org.gradle.api.tasks.TaskAction
|
||||||
import org.gradle.language.base.plugins.LifecycleBasePlugin
|
import org.gradle.language.base.plugins.LifecycleBasePlugin
|
||||||
@@ -42,6 +43,7 @@ open class FrameworkTest : DefaultTask(), KonanTestExecutable {
|
|||||||
@Input
|
@Input
|
||||||
var codesign: Boolean = true
|
var codesign: Boolean = true
|
||||||
|
|
||||||
|
@Input
|
||||||
val testOutput: String = project.testOutputFramework
|
val testOutput: String = project.testOutputFramework
|
||||||
|
|
||||||
@Input @Optional
|
@Input @Optional
|
||||||
@@ -100,13 +102,17 @@ open class FrameworkTest : DefaultTask(), KonanTestExecutable {
|
|||||||
this.map { language.filesFrom(it) }
|
this.map { language.filesFrom(it) }
|
||||||
.flatMap { it.files }
|
.flatMap { it.files }
|
||||||
|
|
||||||
|
@get:Internal
|
||||||
override val executable: String
|
override val executable: String
|
||||||
get() = Paths.get(testOutput, name, "swiftTestExecutable").toString()
|
get() = Paths.get(testOutput, name, "swiftTestExecutable").toString()
|
||||||
|
|
||||||
|
@Internal
|
||||||
override var doBeforeRun: Action<in Task>? = null
|
override var doBeforeRun: Action<in Task>? = null
|
||||||
|
|
||||||
|
@Internal
|
||||||
override var doBeforeBuild: Action<in Task>? = null
|
override var doBeforeBuild: Action<in Task>? = null
|
||||||
|
|
||||||
|
@get:Internal
|
||||||
override val buildTasks: List<Task>
|
override val buildTasks: List<Task>
|
||||||
get() = frameworks.map { project.tasks.getByName("compileKonan${it.name}") }
|
get() = frameworks.map { project.tasks.getByName("compileKonan${it.name}") }
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import org.gradle.api.provider.Provider
|
|||||||
import org.gradle.api.tasks.Exec
|
import org.gradle.api.tasks.Exec
|
||||||
import org.gradle.api.tasks.Input
|
import org.gradle.api.tasks.Input
|
||||||
import org.gradle.api.tasks.InputFile
|
import org.gradle.api.tasks.InputFile
|
||||||
|
import org.gradle.api.tasks.Internal
|
||||||
import org.gradle.api.tasks.OutputDirectory
|
import org.gradle.api.tasks.OutputDirectory
|
||||||
import org.jetbrains.kotlin.konan.target.KonanTarget
|
import org.jetbrains.kotlin.konan.target.KonanTarget
|
||||||
import org.jetbrains.kotlin.konan.target.HostManager
|
import org.jetbrains.kotlin.konan.target.HostManager
|
||||||
@@ -16,9 +17,13 @@ open class KlibInstall: Exec() {
|
|||||||
@Input
|
@Input
|
||||||
lateinit var klib: Provider<File>
|
lateinit var klib: Provider<File>
|
||||||
|
|
||||||
@Input
|
@Internal
|
||||||
var repo: File = project.rootDir
|
var repo: File = project.rootDir
|
||||||
|
|
||||||
|
@get:Input
|
||||||
|
val repoPath
|
||||||
|
get() = repo.absolutePath
|
||||||
|
|
||||||
val installDir: Provider<File>
|
val installDir: Provider<File>
|
||||||
@OutputDirectory
|
@OutputDirectory
|
||||||
get() = project.provider {
|
get() = project.provider {
|
||||||
|
|||||||
+15
-14
@@ -31,6 +31,7 @@ abstract class KonanTest : DefaultTask(), KonanTestExecutable {
|
|||||||
SILENT // Prints no log of passed/failed tests
|
SILENT // Prints no log of passed/failed tests
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@get:Input
|
||||||
var disabled: Boolean
|
var disabled: Boolean
|
||||||
get() = !enabled
|
get() = !enabled
|
||||||
set(value) {
|
set(value) {
|
||||||
@@ -40,11 +41,13 @@ abstract class KonanTest : DefaultTask(), KonanTestExecutable {
|
|||||||
/**
|
/**
|
||||||
* Test output directory. Used to store processed sources and binary artifacts.
|
* Test output directory. Used to store processed sources and binary artifacts.
|
||||||
*/
|
*/
|
||||||
|
@get:OutputDirectory
|
||||||
abstract val outputDirectory: String
|
abstract val outputDirectory: String
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Test logger to be used for the test built with TestRunner (`-tr` option).
|
* Test logger to be used for the test built with TestRunner (`-tr` option).
|
||||||
*/
|
*/
|
||||||
|
@get:Internal
|
||||||
abstract var testLogger: Logger
|
abstract var testLogger: Logger
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -61,6 +64,7 @@ abstract class KonanTest : DefaultTask(), KonanTestExecutable {
|
|||||||
/**
|
/**
|
||||||
* Test source.
|
* Test source.
|
||||||
*/
|
*/
|
||||||
|
@Internal
|
||||||
lateinit var source: String
|
lateinit var source: String
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -74,14 +78,13 @@ abstract class KonanTest : DefaultTask(), KonanTestExecutable {
|
|||||||
* As this run task comes after the build task all actions for doFirst
|
* As this run task comes after the build task all actions for doFirst
|
||||||
* should be done before the build and not run.
|
* should be done before the build and not run.
|
||||||
*/
|
*/
|
||||||
@Input
|
@Internal
|
||||||
@Optional
|
|
||||||
override var doBeforeBuild: Action<in Task>? = null
|
override var doBeforeBuild: Action<in Task>? = null
|
||||||
|
|
||||||
@Input
|
@Internal
|
||||||
@Optional
|
|
||||||
override var doBeforeRun: Action<in Task>? = null
|
override var doBeforeRun: Action<in Task>? = null
|
||||||
|
|
||||||
|
@get:Internal
|
||||||
override val buildTasks: List<Task>
|
override val buildTasks: List<Task>
|
||||||
get() = listOf(project.findKonanBuildTask(name, project.testTarget))
|
get() = listOf(project.findKonanBuildTask(name, project.testTarget))
|
||||||
|
|
||||||
@@ -151,9 +154,11 @@ open class KonanGTest : KonanTest() {
|
|||||||
// Use GTEST logger to parse test results later
|
// Use GTEST logger to parse test results later
|
||||||
override var testLogger = Logger.GTEST
|
override var testLogger = Logger.GTEST
|
||||||
|
|
||||||
|
@get:Internal
|
||||||
override val executable: String
|
override val executable: String
|
||||||
get() = "$outputDirectory/${project.testTarget.name}/$name.${project.testTarget.family.exeSuffix}"
|
get() = "$outputDirectory/${project.testTarget.name}/$name.${project.testTarget.family.exeSuffix}"
|
||||||
|
|
||||||
|
@Internal
|
||||||
var statistics = Statistics()
|
var statistics = Statistics()
|
||||||
|
|
||||||
@TaskAction
|
@TaskAction
|
||||||
@@ -199,6 +204,7 @@ open class KonanLocalTest : KonanTest() {
|
|||||||
override val outputDirectory = project.testOutputLocal
|
override val outputDirectory = project.testOutputLocal
|
||||||
|
|
||||||
// local tests built into a single binary with the known name
|
// local tests built into a single binary with the known name
|
||||||
|
@get:Internal
|
||||||
override val executable: String
|
override val executable: String
|
||||||
get() = "$outputDirectory/${project.testTarget.name}/localTest.${project.testTarget.family.exeSuffix}"
|
get() = "$outputDirectory/${project.testTarget.name}/localTest.${project.testTarget.family.exeSuffix}"
|
||||||
|
|
||||||
@@ -208,15 +214,13 @@ open class KonanLocalTest : KonanTest() {
|
|||||||
@Optional
|
@Optional
|
||||||
var expectedExitStatus: Int? = null
|
var expectedExitStatus: Int? = null
|
||||||
|
|
||||||
@Input
|
@Internal
|
||||||
@Optional
|
|
||||||
var expectedExitStatusChecker: (Int) -> Boolean = { it == (expectedExitStatus ?: 0) }
|
var expectedExitStatusChecker: (Int) -> Boolean = { it == (expectedExitStatus ?: 0) }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Should this test fail or not.
|
* Should this test fail or not.
|
||||||
*/
|
*/
|
||||||
@Input
|
@Input
|
||||||
@Optional
|
|
||||||
var expectedFail = false
|
var expectedFail = false
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -252,8 +256,7 @@ open class KonanLocalTest : KonanTest() {
|
|||||||
/**
|
/**
|
||||||
* Checks test's output against gold value and returns true if the output matches the expectation.
|
* Checks test's output against gold value and returns true if the output matches the expectation.
|
||||||
*/
|
*/
|
||||||
@Input
|
@Internal
|
||||||
@Optional
|
|
||||||
var outputChecker: (String) -> Boolean = { output ->
|
var outputChecker: (String) -> Boolean = { output ->
|
||||||
if (useGoldenData) goldenData == output else true
|
if (useGoldenData) goldenData == output else true
|
||||||
}
|
}
|
||||||
@@ -287,11 +290,9 @@ open class KonanLocalTest : KonanTest() {
|
|||||||
* Should compiler message be read and validated with output checker or gold value.
|
* Should compiler message be read and validated with output checker or gold value.
|
||||||
*/
|
*/
|
||||||
@Input
|
@Input
|
||||||
@Optional
|
|
||||||
var compilerMessages = false
|
var compilerMessages = false
|
||||||
|
|
||||||
@Input
|
@Input
|
||||||
@Optional
|
|
||||||
var multiRuns = false
|
var multiRuns = false
|
||||||
|
|
||||||
@Input
|
@Input
|
||||||
@@ -388,16 +389,15 @@ open class KonanStandaloneTest : KonanLocalTest() {
|
|||||||
get() = "$outputDirectory/${project.testTarget.name}/$name.${project.testTarget.family.exeSuffix}"
|
get() = "$outputDirectory/${project.testTarget.name}/$name.${project.testTarget.family.exeSuffix}"
|
||||||
|
|
||||||
@Input
|
@Input
|
||||||
@Optional
|
|
||||||
var enableKonanAssertions = true
|
var enableKonanAssertions = true
|
||||||
|
|
||||||
@Input
|
@Input
|
||||||
@Optional
|
|
||||||
var verifyIr = true
|
var verifyIr = true
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Compiler flags used to build a test.
|
* Compiler flags used to build a test.
|
||||||
*/
|
*/
|
||||||
|
@Internal
|
||||||
var flags: List<String> = listOf()
|
var flags: List<String> = listOf()
|
||||||
get() {
|
get() {
|
||||||
val result = field.toMutableList()
|
val result = field.toMutableList()
|
||||||
@@ -408,6 +408,7 @@ open class KonanStandaloneTest : KonanLocalTest() {
|
|||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Internal
|
||||||
fun getSources(): Provider<List<String>> = project.provider {
|
fun getSources(): Provider<List<String>> = project.provider {
|
||||||
val sources = buildCompileList(project.file(source).toPath(), outputDirectory)
|
val sources = buildCompileList(project.file(source).toPath(), outputDirectory)
|
||||||
sources.forEach { it.writeTextToFile() }
|
sources.forEach { it.writeTextToFile() }
|
||||||
@@ -490,7 +491,7 @@ open class KonanDynamicTest : KonanStandaloneTest() {
|
|||||||
/**
|
/**
|
||||||
* File path to the C source.
|
* File path to the C source.
|
||||||
*/
|
*/
|
||||||
@Input
|
@get:Input
|
||||||
lateinit var cSource: String
|
lateinit var cSource: String
|
||||||
|
|
||||||
@Input
|
@Input
|
||||||
|
|||||||
+11
-5
@@ -17,10 +17,10 @@ import java.io.File
|
|||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
open class CompileToBitcode @Inject constructor(
|
open class CompileToBitcode @Inject constructor(
|
||||||
val srcRoot: File,
|
@Internal val srcRoot: File,
|
||||||
val folderName: String,
|
@Input val folderName: String,
|
||||||
val target: String,
|
@Input val target: String,
|
||||||
val outputGroup: String
|
@Input val outputGroup: String
|
||||||
) : DefaultTask() {
|
) : DefaultTask() {
|
||||||
|
|
||||||
enum class Language {
|
enum class Language {
|
||||||
@@ -28,22 +28,27 @@ open class CompileToBitcode @Inject constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Compiler args are part of compilerFlags so we don't register them as an input.
|
// Compiler args are part of compilerFlags so we don't register them as an input.
|
||||||
|
@Internal
|
||||||
val compilerArgs = mutableListOf<String>()
|
val compilerArgs = mutableListOf<String>()
|
||||||
@Input
|
@Input
|
||||||
val linkerArgs = mutableListOf<String>()
|
val linkerArgs = mutableListOf<String>()
|
||||||
|
@Input
|
||||||
var excludeFiles: List<String> = listOf(
|
var excludeFiles: List<String> = listOf(
|
||||||
"**/*Test.cpp",
|
"**/*Test.cpp",
|
||||||
"**/*TestSupport.cpp",
|
"**/*TestSupport.cpp",
|
||||||
"**/*Test.mm",
|
"**/*Test.mm",
|
||||||
"**/*TestSupport.mm"
|
"**/*TestSupport.mm"
|
||||||
)
|
)
|
||||||
|
@Input
|
||||||
var includeFiles: List<String> = listOf(
|
var includeFiles: List<String> = listOf(
|
||||||
"**/*.cpp",
|
"**/*.cpp",
|
||||||
"**/*.mm"
|
"**/*.mm"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Source files and headers are registered as inputs by the `inputFiles` and `headers` properties.
|
// Source files and headers are registered as inputs by the `inputFiles` and `headers` properties.
|
||||||
|
@Internal
|
||||||
var srcDirs: FileCollection = project.files(srcRoot.resolve("cpp"))
|
var srcDirs: FileCollection = project.files(srcRoot.resolve("cpp"))
|
||||||
|
@Internal
|
||||||
var headersDirs: FileCollection = srcDirs + project.files(srcRoot.resolve("headers"))
|
var headersDirs: FileCollection = srcDirs + project.files(srcRoot.resolve("headers"))
|
||||||
|
|
||||||
@Input
|
@Input
|
||||||
@@ -62,13 +67,14 @@ open class CompileToBitcode @Inject constructor(
|
|||||||
return project.buildDir.resolve("bitcode/$outputGroup/$target$sanitizerSuffix")
|
return project.buildDir.resolve("bitcode/$outputGroup/$target$sanitizerSuffix")
|
||||||
}
|
}
|
||||||
|
|
||||||
@get:Input
|
@get:Internal
|
||||||
val objDir
|
val objDir
|
||||||
get() = File(targetDir, folderName)
|
get() = File(targetDir, folderName)
|
||||||
|
|
||||||
private val KonanTarget.isMINGW
|
private val KonanTarget.isMINGW
|
||||||
get() = this.family == Family.MINGW
|
get() = this.family == Family.MINGW
|
||||||
|
|
||||||
|
@get:Internal
|
||||||
val executable
|
val executable
|
||||||
get() = when (language) {
|
get() = when (language) {
|
||||||
Language.C -> "clang"
|
Language.C -> "clang"
|
||||||
|
|||||||
+5
-3
@@ -8,6 +8,7 @@ package org.jetbrains.kotlin.testing.native
|
|||||||
import org.gradle.api.DefaultTask
|
import org.gradle.api.DefaultTask
|
||||||
import org.gradle.api.provider.Property
|
import org.gradle.api.provider.Property
|
||||||
import org.gradle.api.provider.Provider
|
import org.gradle.api.provider.Provider
|
||||||
|
import org.gradle.api.tasks.Input
|
||||||
import org.gradle.api.tasks.TaskAction
|
import org.gradle.api.tasks.TaskAction
|
||||||
import org.gradle.api.tasks.options.Option
|
import org.gradle.api.tasks.options.Option
|
||||||
import org.gradle.process.ExecResult
|
import org.gradle.process.ExecResult
|
||||||
@@ -23,9 +24,9 @@ import javax.inject.Inject
|
|||||||
*/
|
*/
|
||||||
@Suppress("UnstableApiUsage")
|
@Suppress("UnstableApiUsage")
|
||||||
open class GitDownloadTask @Inject constructor(
|
open class GitDownloadTask @Inject constructor(
|
||||||
val repositoryProvider: Provider<URL>,
|
private val repositoryProvider: Provider<URL>,
|
||||||
val revisionProvider: Provider<String>,
|
private val revisionProvider: Provider<String>,
|
||||||
val outputDirectoryProvider: Provider<File>
|
private val outputDirectoryProvider: Provider<File>
|
||||||
) : DefaultTask() {
|
) : DefaultTask() {
|
||||||
|
|
||||||
private val repository: URL
|
private val repository: URL
|
||||||
@@ -40,6 +41,7 @@ open class GitDownloadTask @Inject constructor(
|
|||||||
@Option(option = "refresh",
|
@Option(option = "refresh",
|
||||||
description = "Fetch and checkout the revision even if the output directory already contains it. " +
|
description = "Fetch and checkout the revision even if the output directory already contains it. " +
|
||||||
"All changes in the output directory will be overwritten")
|
"All changes in the output directory will be overwritten")
|
||||||
|
@Input
|
||||||
val refresh: Property<Boolean> = project.objects.property(Boolean::class.java).apply {
|
val refresh: Property<Boolean> = project.objects.property(Boolean::class.java).apply {
|
||||||
set(false)
|
set(false)
|
||||||
}
|
}
|
||||||
|
|||||||
+3
-4
@@ -29,7 +29,6 @@ open class CompileNativeTest @Inject constructor(
|
|||||||
@Input @Optional
|
@Input @Optional
|
||||||
var sanitizer: SanitizerKind? = null
|
var sanitizer: SanitizerKind? = null
|
||||||
|
|
||||||
@Input
|
|
||||||
private val sanitizerFlags = when (sanitizer) {
|
private val sanitizerFlags = when (sanitizer) {
|
||||||
null -> listOf()
|
null -> listOf()
|
||||||
SanitizerKind.ADDRESS -> listOf("-fsanitize=address")
|
SanitizerKind.ADDRESS -> listOf("-fsanitize=address")
|
||||||
@@ -55,14 +54,14 @@ open class CompileNativeTest @Inject constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
open class LlvmLinkNativeTest @Inject constructor(
|
open class LlvmLinkNativeTest @Inject constructor(
|
||||||
val baseName: String,
|
baseName: String,
|
||||||
@Input val target: String,
|
@Input val target: String,
|
||||||
@InputFile val mainFile: File
|
@InputFile val mainFile: File
|
||||||
) : DefaultTask() {
|
) : DefaultTask() {
|
||||||
|
|
||||||
@SkipWhenEmpty
|
@SkipWhenEmpty
|
||||||
@InputFiles
|
@InputFiles
|
||||||
var inputFiles: ConfigurableFileCollection = project.files()
|
val inputFiles: ConfigurableFileCollection = project.files()
|
||||||
|
|
||||||
@OutputFile
|
@OutputFile
|
||||||
var outputFile: File = project.buildDir.resolve("bitcode/test/$target/$baseName.bc")
|
var outputFile: File = project.buildDir.resolve("bitcode/test/$target/$baseName.bc")
|
||||||
@@ -265,7 +264,7 @@ private fun createTestTask(
|
|||||||
testName, target, testSupportTask.outFile
|
testName, target, testSupportTask.outFile
|
||||||
).apply {
|
).apply {
|
||||||
val tasksToLink = (compileToBitcodeTasks + testedTasks + testFrameworkTasks)
|
val tasksToLink = (compileToBitcodeTasks + testedTasks + testFrameworkTasks)
|
||||||
inputFiles = project.files(tasksToLink.map { it.outFile })
|
inputFiles.setFrom(tasksToLink.map { it.outFile })
|
||||||
dependsOn(testSupportTask)
|
dependsOn(testSupportTask)
|
||||||
dependsOn(tasksToLink)
|
dependsOn(tasksToLink)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,8 +30,10 @@ buildscript {
|
|||||||
class NativeDep extends DefaultTask {
|
class NativeDep extends DefaultTask {
|
||||||
static final String baseUrl = "https://cache-redirector.jetbrains.com/download.jetbrains.com/kotlin/native"
|
static final String baseUrl = "https://cache-redirector.jetbrains.com/download.jetbrains.com/kotlin/native"
|
||||||
|
|
||||||
|
@Internal
|
||||||
KonanPropertiesLoader konanPropertiesLoader = null
|
KonanPropertiesLoader konanPropertiesLoader = null
|
||||||
|
|
||||||
|
@Internal
|
||||||
final File getBaseOutDir() {
|
final File getBaseOutDir() {
|
||||||
final File res = project.rootProject.project(":kotlin-native").ext.dependenciesDir
|
final File res = project.rootProject.project(":kotlin-native").ext.dependenciesDir
|
||||||
res.mkdirs()
|
res.mkdirs()
|
||||||
|
|||||||
Reference in New Issue
Block a user