feat(gradle): exclude dependsOn tasks (#30913)
<!-- Please make sure you have read the submission guidelines before posting an PR --> <!-- https://github.com/nrwl/nx/blob/master/CONTRIBUTING.md#-submitting-a-pr --> <!-- Please make sure that your commit message follows our format --> <!-- Example: `fix(nx): must begin with lowercase` --> <!-- If this is a particularly complex change or feature addition, you can request a dedicated Nx release for this pull request branch. Mention someone from the Nx team or the `@nrwl/nx-pipelines-reviewers` and they will confirm if the PR warrants its own release for testing purposes, and generate it for you if appropriate. --> ## Current Behavior <!-- This is the behavior we have today --> current gradle task executor will run gradle task as it is. by default, gradle command will run the tasks itself and its all depends on tasks. ## Expected Behavior <!-- This is the behavior we should expect with the changes in this PR --> - add excludeDependsOn in gradle executor schema with default value to true: this allows gradle command to run tasks without its dependsOn tasks. this improves performance time - change project graph plugin (dev.nx.gradle.project-graph) to accept option atomizer: ``` nxProjectReport { atomized = false } ``` this will disabled atomized targets to be created. check-ci will not have dependsOn task ci, it will be test instead. it will not created any ci and ci--* targets, but check-ci will be created, but dependsOn test: <img width="605" alt="Screenshot 2025-05-20 at 3 00 39 PM" src="https://github.com/user-attachments/assets/a2e0ae20-78a1-4848-a063-5825b169c219" /> this is what check-ci target looks like with atomized as true: <img width="917" alt="Screenshot 2025-05-20 at 2 59 34 PM" src="https://github.com/user-attachments/assets/33c6af0b-3e45-498d-96d0-4f46c54a8159" /> - change dependsOn targets to include both project name and task name. e.g. `spring-boot:checkFormat `, so when excludeDependsOn is true, it will exclude exact task - in batch runner, run test runner and build runner as same time ## Related Issue(s) <!-- Please link the issue being fixed so it gets closed when this is merged. --> Fixes #
This commit is contained in:
parent
cb25df1c98
commit
5537df6411
@ -24,6 +24,12 @@
|
|||||||
],
|
],
|
||||||
"description": "The arguments to pass to the Gradle task.",
|
"description": "The arguments to pass to the Gradle task.",
|
||||||
"examples": [["--warning-mode", "all"], "--stracktrace"]
|
"examples": [["--warning-mode", "all"], "--stracktrace"]
|
||||||
|
},
|
||||||
|
"excludeDependsOn": {
|
||||||
|
"type": "boolean",
|
||||||
|
"description": "If true, the tasks will not execute its dependsOn tasks (e.g. pass --exclude-task args to gradle command). If false, the task will execute its dependsOn tasks.",
|
||||||
|
"default": true,
|
||||||
|
"x-priority": "internal"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"required": ["taskName"],
|
"required": ["taskName"],
|
||||||
|
|||||||
@ -9,6 +9,7 @@ import {
|
|||||||
e2eCwd,
|
e2eCwd,
|
||||||
readJson,
|
readJson,
|
||||||
runCommand,
|
runCommand,
|
||||||
|
createFile,
|
||||||
} from '@nx/e2e/utils';
|
} from '@nx/e2e/utils';
|
||||||
import { mkdirSync, rmdirSync, writeFileSync } from 'fs';
|
import { mkdirSync, rmdirSync, writeFileSync } from 'fs';
|
||||||
import { execSync } from 'node:child_process';
|
import { execSync } from 'node:child_process';
|
||||||
@ -37,6 +38,8 @@ describe('Nx Import Gradle', () => {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
createFile('.gitignore', '.kotlin/');
|
||||||
|
|
||||||
try {
|
try {
|
||||||
rmdirSync(tempImportE2ERoot);
|
rmdirSync(tempImportE2ERoot);
|
||||||
} catch {}
|
} catch {}
|
||||||
|
|||||||
@ -21,6 +21,7 @@ dependencies {
|
|||||||
val toolingApiVersion = "8.13" // Match the Gradle version you're working with
|
val toolingApiVersion = "8.13" // Match the Gradle version you're working with
|
||||||
|
|
||||||
implementation("org.gradle:gradle-tooling-api:$toolingApiVersion")
|
implementation("org.gradle:gradle-tooling-api:$toolingApiVersion")
|
||||||
|
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.3")
|
||||||
runtimeOnly("org.slf4j:slf4j-simple:1.7.10")
|
runtimeOnly("org.slf4j:slf4j-simple:1.7.10")
|
||||||
implementation("com.google.code.gson:gson:2.10.1")
|
implementation("com.google.code.gson:gson:2.10.1")
|
||||||
}
|
}
|
||||||
|
|||||||
@ -7,17 +7,20 @@ import dev.nx.gradle.runner.runTasksInParallel
|
|||||||
import dev.nx.gradle.util.logger
|
import dev.nx.gradle.util.logger
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import kotlin.system.exitProcess
|
import kotlin.system.exitProcess
|
||||||
|
import kotlinx.coroutines.runBlocking
|
||||||
import org.gradle.tooling.GradleConnector
|
import org.gradle.tooling.GradleConnector
|
||||||
import org.gradle.tooling.ProjectConnection
|
import org.gradle.tooling.ProjectConnection
|
||||||
|
|
||||||
fun main(args: Array<String>) {
|
fun main(args: Array<String>) {
|
||||||
val options = parseArgs(args)
|
val options = parseArgs(args)
|
||||||
configureLogger(options.quiet)
|
configureLogger(options.quiet)
|
||||||
|
logger.info("NxBatchOptions: $options")
|
||||||
|
|
||||||
if (options.workspaceRoot.isBlank()) {
|
if (options.workspaceRoot.isBlank()) {
|
||||||
logger.severe("❌ Missing required arguments --workspaceRoot")
|
logger.severe("❌ Missing required arguments --workspaceRoot")
|
||||||
exitProcess(1)
|
exitProcess(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (options.tasks.isEmpty()) {
|
if (options.tasks.isEmpty()) {
|
||||||
logger.severe("❌ Missing required arguments --tasks")
|
logger.severe("❌ Missing required arguments --tasks")
|
||||||
exitProcess(1)
|
exitProcess(1)
|
||||||
@ -29,7 +32,9 @@ fun main(args: Array<String>) {
|
|||||||
connection =
|
connection =
|
||||||
GradleConnector.newConnector().forProjectDirectory(File(options.workspaceRoot)).connect()
|
GradleConnector.newConnector().forProjectDirectory(File(options.workspaceRoot)).connect()
|
||||||
|
|
||||||
val results = runTasksInParallel(connection, options.tasks, options.args)
|
val results = runBlocking {
|
||||||
|
runTasksInParallel(connection, options.tasks, options.args, options.excludeTasks)
|
||||||
|
}
|
||||||
|
|
||||||
val reportJson = Gson().toJson(results)
|
val reportJson = Gson().toJson(results)
|
||||||
println(reportJson)
|
println(reportJson)
|
||||||
|
|||||||
@ -29,11 +29,16 @@ fun parseArgs(args: Array<String>): NxBatchOptions {
|
|||||||
gson.fromJson(tasksJson, taskType)
|
gson.fromJson(tasksJson, taskType)
|
||||||
} else emptyMap()
|
} else emptyMap()
|
||||||
|
|
||||||
|
val excludeTasks =
|
||||||
|
argMap["--excludeTasks"]?.split(",")?.map { it.trim() }?.filter { it.isNotEmpty() }
|
||||||
|
?: emptyList()
|
||||||
|
|
||||||
return NxBatchOptions(
|
return NxBatchOptions(
|
||||||
workspaceRoot = argMap["--workspaceRoot"] ?: "",
|
workspaceRoot = argMap["--workspaceRoot"] ?: "",
|
||||||
tasks = tasksMap,
|
tasks = tasksMap,
|
||||||
args = argMap["--args"] ?: "",
|
args = argMap["--args"] ?: "",
|
||||||
quiet = argMap["--quiet"]?.toBoolean() ?: false)
|
quiet = argMap["--quiet"]?.toBoolean() ?: false,
|
||||||
|
excludeTasks = excludeTasks)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun configureLogger(quiet: Boolean) {
|
fun configureLogger(quiet: Boolean) {
|
||||||
|
|||||||
@ -4,5 +4,6 @@ data class NxBatchOptions(
|
|||||||
val workspaceRoot: String,
|
val workspaceRoot: String,
|
||||||
val tasks: Map<String, GradleTask>,
|
val tasks: Map<String, GradleTask>,
|
||||||
val args: String,
|
val args: String,
|
||||||
val quiet: Boolean
|
val quiet: Boolean,
|
||||||
|
val excludeTasks: List<String>
|
||||||
)
|
)
|
||||||
|
|||||||
@ -25,6 +25,7 @@ fun buildListener(
|
|||||||
taskStartTimes[nxTaskId] = min(System.currentTimeMillis(), event.eventTime)
|
taskStartTimes[nxTaskId] = min(System.currentTimeMillis(), event.eventTime)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
is TaskFinishEvent -> {
|
is TaskFinishEvent -> {
|
||||||
val taskPath = event.descriptor.taskPath
|
val taskPath = event.descriptor.taskPath
|
||||||
val success =
|
val success =
|
||||||
@ -33,10 +34,12 @@ fun buildListener(
|
|||||||
logger.info("✅ Task finished successfully: $taskPath")
|
logger.info("✅ Task finished successfully: $taskPath")
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
is TaskFailureResult -> {
|
is TaskFailureResult -> {
|
||||||
logger.warning("❌ Task failed: $taskPath")
|
logger.warning("❌ Task failed: $taskPath")
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
else -> true
|
else -> true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -3,17 +3,20 @@ package dev.nx.gradle.runner
|
|||||||
import dev.nx.gradle.data.GradleTask
|
import dev.nx.gradle.data.GradleTask
|
||||||
import dev.nx.gradle.data.TaskResult
|
import dev.nx.gradle.data.TaskResult
|
||||||
import dev.nx.gradle.runner.OutputProcessor.buildTerminalOutput
|
import dev.nx.gradle.runner.OutputProcessor.buildTerminalOutput
|
||||||
import dev.nx.gradle.runner.OutputProcessor.splitOutputPerTask
|
import dev.nx.gradle.runner.OutputProcessor.finalizeTaskResults
|
||||||
import dev.nx.gradle.util.logger
|
import dev.nx.gradle.util.logger
|
||||||
import java.io.ByteArrayOutputStream
|
import java.io.ByteArrayOutputStream
|
||||||
|
import kotlinx.coroutines.async
|
||||||
|
import kotlinx.coroutines.coroutineScope
|
||||||
import org.gradle.tooling.ProjectConnection
|
import org.gradle.tooling.ProjectConnection
|
||||||
import org.gradle.tooling.events.OperationType
|
import org.gradle.tooling.events.OperationType
|
||||||
|
|
||||||
fun runTasksInParallel(
|
suspend fun runTasksInParallel(
|
||||||
connection: ProjectConnection,
|
connection: ProjectConnection,
|
||||||
tasks: Map<String, GradleTask>,
|
tasks: Map<String, GradleTask>,
|
||||||
additionalArgs: String,
|
additionalArgs: String,
|
||||||
): Map<String, TaskResult> {
|
excludeTasks: List<String>
|
||||||
|
): Map<String, TaskResult> = coroutineScope {
|
||||||
logger.info("▶️ Running all tasks in a single Gradle run: ${tasks.keys.joinToString(", ")}")
|
logger.info("▶️ Running all tasks in a single Gradle run: ${tasks.keys.joinToString(", ")}")
|
||||||
|
|
||||||
val (testClassTasks, buildTasks) = tasks.entries.partition { it.value.testClassName != null }
|
val (testClassTasks, buildTasks) = tasks.entries.partition { it.value.testClassName != null }
|
||||||
@ -21,72 +24,76 @@ fun runTasksInParallel(
|
|||||||
logger.info("🧪 Test launcher tasks: ${testClassTasks.joinToString(", ") { it.key }}")
|
logger.info("🧪 Test launcher tasks: ${testClassTasks.joinToString(", ") { it.key }}")
|
||||||
logger.info("🛠️ Build launcher tasks: ${buildTasks.joinToString(", ") { it.key }}")
|
logger.info("🛠️ Build launcher tasks: ${buildTasks.joinToString(", ") { it.key }}")
|
||||||
|
|
||||||
val allResults = mutableMapOf<String, TaskResult>()
|
val outputStream1 = ByteArrayOutputStream()
|
||||||
|
val errorStream1 = ByteArrayOutputStream()
|
||||||
val outputStream = ByteArrayOutputStream()
|
val outputStream2 = ByteArrayOutputStream()
|
||||||
val errorStream = ByteArrayOutputStream()
|
val errorStream2 = ByteArrayOutputStream()
|
||||||
|
|
||||||
val args = buildList {
|
val args = buildList {
|
||||||
// --info is for terminal per task
|
// --info is for terminal per task
|
||||||
// --continue is for continue running tasks if one failed in a batch
|
// --continue is for continue running tasks if one failed in a batch
|
||||||
// --parallel and --build-cache are for performance
|
// --parallel is for performance
|
||||||
// -Dorg.gradle.daemon.idletimeout=10000 is to kill daemon after 10 seconds
|
// -Dorg.gradle.daemon.idletimeout=10000 is to kill daemon after 10 seconds
|
||||||
addAll(
|
addAll(listOf("--info", "--continue", "-Dorg.gradle.daemon.idletimeout=10000"))
|
||||||
listOf(
|
|
||||||
"--info",
|
|
||||||
"--continue",
|
|
||||||
"--parallel",
|
|
||||||
"--build-cache",
|
|
||||||
"-Dorg.gradle.daemon.idletimeout=10000"))
|
|
||||||
addAll(additionalArgs.split(" ").filter { it.isNotBlank() })
|
addAll(additionalArgs.split(" ").filter { it.isNotBlank() })
|
||||||
|
excludeTasks.forEach {
|
||||||
|
add("--exclude-task")
|
||||||
|
add(it)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.info("🏳️ Args: ${args.joinToString(", ")}")
|
logger.info("🏳️ Args: ${args.joinToString(", ")}")
|
||||||
|
|
||||||
val taskNames = tasks.values.map { it.taskName }.distinct()
|
val buildJob = async {
|
||||||
|
if (buildTasks.isNotEmpty()) {
|
||||||
if (buildTasks.isNotEmpty()) {
|
runBuildLauncher(
|
||||||
allResults.putAll(
|
connection,
|
||||||
runBuildLauncher(
|
buildTasks.associate { it.key to it.value },
|
||||||
connection,
|
args,
|
||||||
buildTasks.associate { it.key to it.value },
|
outputStream1,
|
||||||
taskNames,
|
errorStream1)
|
||||||
args,
|
} else emptyMap()
|
||||||
outputStream,
|
|
||||||
errorStream))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (testClassTasks.isNotEmpty()) {
|
val testJob = async {
|
||||||
allResults.putAll(
|
if (testClassTasks.isNotEmpty()) {
|
||||||
runTestLauncher(
|
runTestLauncher(
|
||||||
connection,
|
connection,
|
||||||
testClassTasks.associate { it.key to it.value },
|
testClassTasks.associate { it.key to it.value },
|
||||||
taskNames,
|
args,
|
||||||
args,
|
outputStream2,
|
||||||
outputStream,
|
errorStream2)
|
||||||
errorStream))
|
} else emptyMap()
|
||||||
}
|
}
|
||||||
|
|
||||||
return allResults
|
val allResults = mutableMapOf<String, TaskResult>()
|
||||||
|
allResults.putAll(buildJob.await())
|
||||||
|
allResults.putAll(testJob.await())
|
||||||
|
|
||||||
|
return@coroutineScope allResults
|
||||||
}
|
}
|
||||||
|
|
||||||
fun runBuildLauncher(
|
fun runBuildLauncher(
|
||||||
connection: ProjectConnection,
|
connection: ProjectConnection,
|
||||||
tasks: Map<String, GradleTask>,
|
tasks: Map<String, GradleTask>,
|
||||||
taskNames: List<String>,
|
|
||||||
args: List<String>,
|
args: List<String>,
|
||||||
outputStream: ByteArrayOutputStream,
|
outputStream: ByteArrayOutputStream,
|
||||||
errorStream: ByteArrayOutputStream
|
errorStream: ByteArrayOutputStream
|
||||||
): Map<String, TaskResult> {
|
): Map<String, TaskResult> {
|
||||||
|
val taskNames = tasks.values.map { it.taskName }.distinct().toTypedArray()
|
||||||
|
logger.info("📋 Collected ${taskNames.size} unique task names: ${taskNames.joinToString(", ")}")
|
||||||
|
|
||||||
val taskStartTimes = mutableMapOf<String, Long>()
|
val taskStartTimes = mutableMapOf<String, Long>()
|
||||||
val taskResults = mutableMapOf<String, TaskResult>()
|
val taskResults = mutableMapOf<String, TaskResult>()
|
||||||
|
|
||||||
|
val globalStart = System.currentTimeMillis()
|
||||||
var globalOutput: String
|
var globalOutput: String
|
||||||
|
|
||||||
try {
|
try {
|
||||||
connection
|
connection
|
||||||
.newBuild()
|
.newBuild()
|
||||||
.apply {
|
.apply {
|
||||||
forTasks(*taskNames.toTypedArray())
|
forTasks(*taskNames)
|
||||||
withArguments(*args.toTypedArray())
|
withArguments(*args.toTypedArray())
|
||||||
setStandardOutput(outputStream)
|
setStandardOutput(outputStream)
|
||||||
setStandardError(errorStream)
|
setStandardError(errorStream)
|
||||||
@ -97,17 +104,20 @@ fun runBuildLauncher(
|
|||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
globalOutput =
|
globalOutput =
|
||||||
buildTerminalOutput(outputStream, errorStream) + "\nException occurred: ${e.message}"
|
buildTerminalOutput(outputStream, errorStream) + "\nException occurred: ${e.message}"
|
||||||
logger.warning("\ud83d\udca5 Gradle run failed: ${e.message}")
|
logger.warning("\ud83d\udca5 Gradle run failed: ${e.message} $errorStream")
|
||||||
} finally {
|
} finally {
|
||||||
outputStream.close()
|
outputStream.close()
|
||||||
errorStream.close()
|
errorStream.close()
|
||||||
}
|
}
|
||||||
|
|
||||||
val perTaskOutput = splitOutputPerTask(globalOutput)
|
val globalEnd = System.currentTimeMillis()
|
||||||
tasks.forEach { (taskId, taskConfig) ->
|
finalizeTaskResults(
|
||||||
val taskOutput = perTaskOutput[taskConfig.taskName] ?: globalOutput
|
tasks = tasks,
|
||||||
taskResults[taskId]?.let { taskResults[taskId] = it.copy(terminalOutput = taskOutput) }
|
taskResults = taskResults,
|
||||||
}
|
globalOutput = globalOutput,
|
||||||
|
errorStream = errorStream,
|
||||||
|
globalStart = globalStart,
|
||||||
|
globalEnd = globalEnd)
|
||||||
|
|
||||||
logger.info("\u2705 Finished build tasks")
|
logger.info("\u2705 Finished build tasks")
|
||||||
return taskResults
|
return taskResults
|
||||||
@ -116,11 +126,13 @@ fun runBuildLauncher(
|
|||||||
fun runTestLauncher(
|
fun runTestLauncher(
|
||||||
connection: ProjectConnection,
|
connection: ProjectConnection,
|
||||||
tasks: Map<String, GradleTask>,
|
tasks: Map<String, GradleTask>,
|
||||||
taskNames: List<String>,
|
|
||||||
args: List<String>,
|
args: List<String>,
|
||||||
outputStream: ByteArrayOutputStream,
|
outputStream: ByteArrayOutputStream,
|
||||||
errorStream: ByteArrayOutputStream
|
errorStream: ByteArrayOutputStream
|
||||||
): Map<String, TaskResult> {
|
): Map<String, TaskResult> {
|
||||||
|
val taskNames = tasks.values.map { it.taskName }.distinct().toTypedArray()
|
||||||
|
logger.info("📋 Collected ${taskNames.size} unique task names: ${taskNames.joinToString(", ")}")
|
||||||
|
|
||||||
val taskStartTimes = mutableMapOf<String, Long>()
|
val taskStartTimes = mutableMapOf<String, Long>()
|
||||||
val taskResults = mutableMapOf<String, TaskResult>()
|
val taskResults = mutableMapOf<String, TaskResult>()
|
||||||
val testTaskStatus = mutableMapOf<String, Boolean>()
|
val testTaskStatus = mutableMapOf<String, Boolean>()
|
||||||
@ -140,8 +152,14 @@ fun runTestLauncher(
|
|||||||
connection
|
connection
|
||||||
.newTestLauncher()
|
.newTestLauncher()
|
||||||
.apply {
|
.apply {
|
||||||
forTasks(*taskNames.toTypedArray())
|
forTasks(*taskNames)
|
||||||
tasks.values.mapNotNull { it.testClassName }.forEach { withJvmTestClasses(it) }
|
tasks.values
|
||||||
|
.mapNotNull { it.testClassName }
|
||||||
|
.forEach {
|
||||||
|
logger.info("Registering test class: $it")
|
||||||
|
withArguments("--tests", it)
|
||||||
|
withJvmTestClasses(it)
|
||||||
|
}
|
||||||
withArguments(*args.toTypedArray())
|
withArguments(*args.toTypedArray())
|
||||||
setStandardOutput(outputStream)
|
setStandardOutput(outputStream)
|
||||||
setStandardError(errorStream)
|
setStandardError(errorStream)
|
||||||
@ -153,9 +171,10 @@ fun runTestLauncher(
|
|||||||
.run()
|
.run()
|
||||||
globalOutput = buildTerminalOutput(outputStream, errorStream)
|
globalOutput = buildTerminalOutput(outputStream, errorStream)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
|
logger.warning(errorStream.toString())
|
||||||
globalOutput =
|
globalOutput =
|
||||||
buildTerminalOutput(outputStream, errorStream) + "\nException occurred: ${e.message}"
|
buildTerminalOutput(outputStream, errorStream) + "\nException occurred: ${e.message}"
|
||||||
logger.warning("\ud83d\udca5 Gradle test run failed: ${e.message}")
|
logger.warning("\ud83d\udca5 Gradle test run failed: ${e.message} $errorStream")
|
||||||
} finally {
|
} finally {
|
||||||
outputStream.close()
|
outputStream.close()
|
||||||
errorStream.close()
|
errorStream.close()
|
||||||
@ -175,11 +194,13 @@ fun runTestLauncher(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val perTaskOutput = splitOutputPerTask(globalOutput)
|
finalizeTaskResults(
|
||||||
tasks.forEach { (taskId, taskConfig) ->
|
tasks = tasks,
|
||||||
val taskOutput = perTaskOutput[taskConfig.taskName] ?: globalOutput
|
taskResults = taskResults,
|
||||||
taskResults[taskId]?.let { taskResults[taskId] = it.copy(terminalOutput = taskOutput) }
|
globalOutput = globalOutput,
|
||||||
}
|
errorStream = errorStream,
|
||||||
|
globalStart = globalStart,
|
||||||
|
globalEnd = globalEnd)
|
||||||
|
|
||||||
logger.info("\u2705 Finished test tasks")
|
logger.info("\u2705 Finished test tasks")
|
||||||
return taskResults
|
return taskResults
|
||||||
|
|||||||
@ -1,5 +1,7 @@
|
|||||||
package dev.nx.gradle.runner
|
package dev.nx.gradle.runner
|
||||||
|
|
||||||
|
import dev.nx.gradle.data.GradleTask
|
||||||
|
import dev.nx.gradle.data.TaskResult
|
||||||
import java.io.ByteArrayOutputStream
|
import java.io.ByteArrayOutputStream
|
||||||
|
|
||||||
object OutputProcessor {
|
object OutputProcessor {
|
||||||
@ -12,7 +14,42 @@ object OutputProcessor {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun splitOutputPerTask(globalOutput: String): Map<String, String> {
|
fun finalizeTaskResults(
|
||||||
|
tasks: Map<String, GradleTask>,
|
||||||
|
taskResults: MutableMap<String, TaskResult>,
|
||||||
|
globalOutput: String,
|
||||||
|
errorStream: ByteArrayOutputStream,
|
||||||
|
globalStart: Long,
|
||||||
|
globalEnd: Long
|
||||||
|
): Map<String, TaskResult> {
|
||||||
|
val perTaskOutput = splitOutputPerTask(globalOutput)
|
||||||
|
|
||||||
|
tasks.forEach { (taskId, taskConfig) ->
|
||||||
|
val baseOutput = perTaskOutput[taskConfig.taskName] ?: ""
|
||||||
|
val existingResult = taskResults[taskId]
|
||||||
|
|
||||||
|
val outputWithErrors =
|
||||||
|
if (existingResult?.success == false) {
|
||||||
|
baseOutput + "\n" + errorStream.toString()
|
||||||
|
} else {
|
||||||
|
baseOutput
|
||||||
|
}
|
||||||
|
|
||||||
|
val finalOutput = outputWithErrors.ifBlank { globalOutput }
|
||||||
|
|
||||||
|
taskResults[taskId] =
|
||||||
|
existingResult?.copy(terminalOutput = finalOutput)
|
||||||
|
?: TaskResult(
|
||||||
|
success = false,
|
||||||
|
startTime = globalStart,
|
||||||
|
endTime = globalEnd,
|
||||||
|
terminalOutput = finalOutput)
|
||||||
|
}
|
||||||
|
|
||||||
|
return taskResults
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun splitOutputPerTask(globalOutput: String): Map<String, String> {
|
||||||
val unescapedOutput = globalOutput.replace("\\u003e", ">").replace("\\n", "\n")
|
val unescapedOutput = globalOutput.replace("\\u003e", ">").replace("\\n", "\n")
|
||||||
val taskHeaderRegex = Regex("(?=> Task (:[^\\s]+))")
|
val taskHeaderRegex = Regex("(?=> Task (:[^\\s]+))")
|
||||||
val sections = unescapedOutput.split(taskHeaderRegex)
|
val sections = unescapedOutput.split(taskHeaderRegex)
|
||||||
|
|||||||
@ -3,8 +3,6 @@ package dev.nx.gradle.runner
|
|||||||
import dev.nx.gradle.data.GradleTask
|
import dev.nx.gradle.data.GradleTask
|
||||||
import dev.nx.gradle.data.TaskResult
|
import dev.nx.gradle.data.TaskResult
|
||||||
import dev.nx.gradle.util.logger
|
import dev.nx.gradle.util.logger
|
||||||
import kotlin.math.max
|
|
||||||
import kotlin.math.min
|
|
||||||
import org.gradle.tooling.events.ProgressEvent
|
import org.gradle.tooling.events.ProgressEvent
|
||||||
import org.gradle.tooling.events.task.TaskFinishEvent
|
import org.gradle.tooling.events.task.TaskFinishEvent
|
||||||
import org.gradle.tooling.events.task.TaskStartEvent
|
import org.gradle.tooling.events.task.TaskStartEvent
|
||||||
@ -22,38 +20,42 @@ fun testListener(
|
|||||||
is TaskStartEvent,
|
is TaskStartEvent,
|
||||||
is TaskFinishEvent -> buildListener(tasks, taskStartTimes, taskResults)(event)
|
is TaskFinishEvent -> buildListener(tasks, taskStartTimes, taskResults)(event)
|
||||||
is TestStartEvent -> {
|
is TestStartEvent -> {
|
||||||
(event.descriptor as? JvmTestOperationDescriptor)?.className?.let { className ->
|
((event.descriptor as? JvmTestOperationDescriptor)?.className?.substringAfterLast('.')?.let {
|
||||||
|
simpleClassName ->
|
||||||
tasks.entries
|
tasks.entries
|
||||||
.find { entry -> entry.value.testClassName?.let { className.endsWith(it) } ?: false }
|
.find { entry -> entry.value.testClassName?.let { simpleClassName == it } ?: false }
|
||||||
?.key
|
?.key
|
||||||
?.let { nxTaskId ->
|
?.let { nxTaskId ->
|
||||||
testStartTimes.compute(nxTaskId) { _, old ->
|
testStartTimes.computeIfAbsent(nxTaskId) { event.eventTime }
|
||||||
min(old ?: event.eventTime, event.eventTime)
|
logger.info("🏁 Test start at ${event.eventTime}: $nxTaskId $simpleClassName")
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
})
|
||||||
}
|
}
|
||||||
is TestFinishEvent -> {
|
is TestFinishEvent -> {
|
||||||
(event.descriptor as? JvmTestOperationDescriptor)?.className?.let { className ->
|
((event.descriptor as? JvmTestOperationDescriptor)?.className?.substringAfterLast('.')?.let {
|
||||||
|
simpleClassName ->
|
||||||
tasks.entries
|
tasks.entries
|
||||||
.find { entry -> entry.value.testClassName?.let { className.endsWith(it) } ?: false }
|
.find { entry -> entry.value.testClassName?.let { simpleClassName == it } ?: false }
|
||||||
?.key
|
?.key
|
||||||
?.let { nxTaskId ->
|
?.let { nxTaskId ->
|
||||||
testEndTimes.compute(nxTaskId) { _, old ->
|
testEndTimes.compute(nxTaskId) { _, _ -> event.result.endTime }
|
||||||
max(old ?: event.eventTime, event.eventTime)
|
|
||||||
}
|
|
||||||
when (event.result) {
|
when (event.result) {
|
||||||
is TestSuccessResult -> logger.info("\u2705 Test passed: $nxTaskId $className")
|
is TestSuccessResult ->
|
||||||
|
logger.info(
|
||||||
|
"\u2705 Test passed at ${event.result.endTime}: $nxTaskId $simpleClassName")
|
||||||
is TestFailureResult -> {
|
is TestFailureResult -> {
|
||||||
testTaskStatus[nxTaskId] = false
|
testTaskStatus[nxTaskId] = false
|
||||||
logger.warning("\u274C Test failed: $nxTaskId $className")
|
logger.warning("\u274C Test failed: $nxTaskId $simpleClassName")
|
||||||
}
|
}
|
||||||
|
|
||||||
is TestSkippedResult ->
|
is TestSkippedResult ->
|
||||||
logger.warning("\u26A0\uFE0F Test skipped: $nxTaskId $className")
|
logger.warning("\u26A0\uFE0F Test skipped: $nxTaskId $simpleClassName")
|
||||||
else -> logger.warning("\u26A0\uFE0F Unknown test result: $nxTaskId $className")
|
|
||||||
|
else ->
|
||||||
|
logger.warning("\u26A0\uFE0F Unknown test result: $nxTaskId $simpleClassName")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -19,14 +19,6 @@ class NxProjectGraphReportPlugin : Plugin<Project> {
|
|||||||
"default-hash"
|
"default-hash"
|
||||||
}
|
}
|
||||||
|
|
||||||
val cwdProperty =
|
|
||||||
project.findProperty("cwd")?.toString()
|
|
||||||
?: run {
|
|
||||||
project.logger.warn(
|
|
||||||
"No 'cwd' property was provided for $project. Using default hash value: ${System.getProperty("user.dir")}")
|
|
||||||
System.getProperty("user.dir")
|
|
||||||
}
|
|
||||||
|
|
||||||
val workspaceRootProperty =
|
val workspaceRootProperty =
|
||||||
project.findProperty("workspaceRoot")?.toString()
|
project.findProperty("workspaceRoot")?.toString()
|
||||||
?: run {
|
?: run {
|
||||||
@ -43,7 +35,6 @@ class NxProjectGraphReportPlugin : Plugin<Project> {
|
|||||||
task.projectRef.set(project)
|
task.projectRef.set(project)
|
||||||
task.hash.set(hashProperty)
|
task.hash.set(hashProperty)
|
||||||
task.targetNameOverrides.set(targetNameOverrides)
|
task.targetNameOverrides.set(targetNameOverrides)
|
||||||
task.cwd.set(cwdProperty)
|
|
||||||
task.workspaceRoot.set(workspaceRootProperty)
|
task.workspaceRoot.set(workspaceRootProperty)
|
||||||
|
|
||||||
task.description = "Create Nx project report for ${project.name}"
|
task.description = "Create Nx project report for ${project.name}"
|
||||||
|
|||||||
@ -24,16 +24,20 @@ abstract class NxProjectReportTask @Inject constructor(private val projectLayout
|
|||||||
|
|
||||||
@get:Input abstract val hash: Property<String>
|
@get:Input abstract val hash: Property<String>
|
||||||
|
|
||||||
@get:Input abstract val cwd: Property<String>
|
|
||||||
|
|
||||||
@get:Input abstract val workspaceRoot: Property<String>
|
@get:Input abstract val workspaceRoot: Property<String>
|
||||||
|
|
||||||
|
@get:Input abstract val atomized: Property<Boolean>
|
||||||
|
|
||||||
@get:Input abstract val targetNameOverrides: MapProperty<String, String>
|
@get:Input abstract val targetNameOverrides: MapProperty<String, String>
|
||||||
|
|
||||||
// Don't compute report at configuration time, move it to execution time
|
// Don't compute report at configuration time, move it to execution time
|
||||||
@get:Internal // Prevent Gradle from caching this reference
|
@get:Internal // Prevent Gradle from caching this reference
|
||||||
abstract val projectRef: Property<Project>
|
abstract val projectRef: Property<Project>
|
||||||
|
|
||||||
|
init {
|
||||||
|
atomized.convention(true)
|
||||||
|
}
|
||||||
|
|
||||||
@get:OutputFile
|
@get:OutputFile
|
||||||
val outputFile: File
|
val outputFile: File
|
||||||
get() = projectLayout.buildDirectory.file("nx/${projectName.get()}.json").get().asFile
|
get() = projectLayout.buildDirectory.file("nx/${projectName.get()}.json").get().asFile
|
||||||
@ -43,13 +47,14 @@ abstract class NxProjectReportTask @Inject constructor(private val projectLayout
|
|||||||
logger.info("${Date()} Apply task action NxProjectReportTask for ${projectName.get()}")
|
logger.info("${Date()} Apply task action NxProjectReportTask for ${projectName.get()}")
|
||||||
logger.info("${Date()} Hash input: ${hash.get()}")
|
logger.info("${Date()} Hash input: ${hash.get()}")
|
||||||
logger.info("${Date()} Target Name Overrides ${targetNameOverrides.get()}")
|
logger.info("${Date()} Target Name Overrides ${targetNameOverrides.get()}")
|
||||||
|
logger.info("${Date()} Atomized: ${atomized.get()}")
|
||||||
val project = projectRef.get() // Get project reference at execution time
|
val project = projectRef.get() // Get project reference at execution time
|
||||||
val report =
|
val report =
|
||||||
createNodeForProject(
|
createNodeForProject(
|
||||||
project,
|
project,
|
||||||
targetNameOverrides.get(),
|
targetNameOverrides.get(),
|
||||||
workspaceRoot.get(),
|
workspaceRoot.get(),
|
||||||
cwd.get()) // Compute report at execution time
|
atomized.get()) // Compute report at execution time
|
||||||
val reportJson = gson.toJson(report)
|
val reportJson = gson.toJson(report)
|
||||||
|
|
||||||
if (outputFile.exists() && outputFile.readText() == reportJson) {
|
if (outputFile.exists() && outputFile.readText() == reportJson) {
|
||||||
|
|||||||
@ -9,7 +9,7 @@ fun createNodeForProject(
|
|||||||
project: Project,
|
project: Project,
|
||||||
targetNameOverrides: Map<String, String>,
|
targetNameOverrides: Map<String, String>,
|
||||||
workspaceRoot: String,
|
workspaceRoot: String,
|
||||||
cwd: String
|
atomized: Boolean
|
||||||
): GradleNodeReport {
|
): GradleNodeReport {
|
||||||
val logger = project.logger
|
val logger = project.logger
|
||||||
logger.info("${Date()} ${project.name} createNodeForProject: get nodes and dependencies")
|
logger.info("${Date()} ${project.name} createNodeForProject: get nodes and dependencies")
|
||||||
@ -31,7 +31,8 @@ fun createNodeForProject(
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
val gradleTargets: GradleTargets =
|
val gradleTargets: GradleTargets =
|
||||||
processTargetsForProject(project, dependencies, targetNameOverrides, workspaceRoot, cwd)
|
processTargetsForProject(
|
||||||
|
project, dependencies, targetNameOverrides, workspaceRoot, atomized)
|
||||||
val projectRoot = project.projectDir.path
|
val projectRoot = project.projectDir.path
|
||||||
val projectNode =
|
val projectNode =
|
||||||
ProjectNode(
|
ProjectNode(
|
||||||
@ -61,7 +62,7 @@ fun processTargetsForProject(
|
|||||||
dependencies: MutableSet<Dependency>,
|
dependencies: MutableSet<Dependency>,
|
||||||
targetNameOverrides: Map<String, String>,
|
targetNameOverrides: Map<String, String>,
|
||||||
workspaceRoot: String,
|
workspaceRoot: String,
|
||||||
cwd: String
|
atomized: Boolean
|
||||||
): GradleTargets {
|
): GradleTargets {
|
||||||
val targets: NxTargets = mutableMapOf()
|
val targets: NxTargets = mutableMapOf()
|
||||||
val targetGroups: TargetGroups = mutableMapOf()
|
val targetGroups: TargetGroups = mutableMapOf()
|
||||||
@ -111,7 +112,7 @@ fun processTargetsForProject(
|
|||||||
|
|
||||||
targets[taskName] = target
|
targets[taskName] = target
|
||||||
|
|
||||||
if (hasCiTestTarget && task.name.startsWith("compileTest")) {
|
if (hasCiTestTarget && task.name.startsWith("compileTest") && atomized) {
|
||||||
addTestCiTargets(
|
addTestCiTargets(
|
||||||
task.inputs.sourceFiles,
|
task.inputs.sourceFiles,
|
||||||
projectBuildPath,
|
projectBuildPath,
|
||||||
@ -124,7 +125,7 @@ fun processTargetsForProject(
|
|||||||
ciTestTargetName!!)
|
ciTestTargetName!!)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (hasCiIntTestTarget && task.name.startsWith("compileIntTest")) {
|
if (hasCiIntTestTarget && task.name.startsWith("compileIntTest") && atomized) {
|
||||||
addTestCiTargets(
|
addTestCiTargets(
|
||||||
task.inputs.sourceFiles,
|
task.inputs.sourceFiles,
|
||||||
projectBuildPath,
|
projectBuildPath,
|
||||||
@ -137,14 +138,19 @@ fun processTargetsForProject(
|
|||||||
ciIntTestTargetName!!)
|
ciIntTestTargetName!!)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (task.name == "check" && (hasCiTestTarget || hasCiIntTestTarget)) {
|
if (task.name == "check") {
|
||||||
val replacedDependencies =
|
val replacedDependencies =
|
||||||
(target["dependsOn"] as? List<*>)?.map { dep ->
|
(target["dependsOn"] as? List<*>)?.map { dep ->
|
||||||
when (dep.toString()) {
|
val dependsOn = dep.toString()
|
||||||
testTargetName -> ciTestTargetName ?: dep
|
if (hasCiTestTarget && dependsOn == "${project.name}:$testTargetName" && atomized) {
|
||||||
intTestTargetName -> ciIntTestTargetName ?: dep
|
"${project.name}:$ciTestTargetName"
|
||||||
else -> dep
|
} else if (hasCiIntTestTarget &&
|
||||||
}.toString()
|
dependsOn == "${project.name}:$intTestTargetName" &&
|
||||||
|
atomized) {
|
||||||
|
"${project.name}:$ciIntTestTargetName"
|
||||||
|
} else {
|
||||||
|
dep
|
||||||
|
}
|
||||||
} ?: emptyList()
|
} ?: emptyList()
|
||||||
|
|
||||||
val newTarget: MutableMap<String, Any?> =
|
val newTarget: MutableMap<String, Any?> =
|
||||||
|
|||||||
@ -180,12 +180,7 @@ fun getDependsOnForTask(
|
|||||||
|
|
||||||
// Check if this task name needs to be overridden
|
// Check if this task name needs to be overridden
|
||||||
val taskName = targetNameOverrides.getOrDefault(depTask.name + "TargetName", depTask.name)
|
val taskName = targetNameOverrides.getOrDefault(depTask.name + "TargetName", depTask.name)
|
||||||
val overriddenTaskName =
|
val overriddenTaskName = "${depProject.name}:${taskName}"
|
||||||
if (depProject == taskProject) {
|
|
||||||
taskName
|
|
||||||
} else {
|
|
||||||
"${depProject.name}:${taskName}"
|
|
||||||
}
|
|
||||||
|
|
||||||
overriddenTaskName
|
overriddenTaskName
|
||||||
}
|
}
|
||||||
|
|||||||
@ -32,7 +32,7 @@ class CreateNodeForProjectTest {
|
|||||||
project = project,
|
project = project,
|
||||||
targetNameOverrides = targetNameOverrides,
|
targetNameOverrides = targetNameOverrides,
|
||||||
workspaceRoot = workspaceRoot,
|
workspaceRoot = workspaceRoot,
|
||||||
cwd = "{projectRoot}")
|
atomized = true)
|
||||||
|
|
||||||
// Assert
|
// Assert
|
||||||
val projectRoot = project.projectDir.absolutePath
|
val projectRoot = project.projectDir.absolutePath
|
||||||
|
|||||||
@ -57,7 +57,7 @@ class ProcessTaskUtilsTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `test getDependsOnForTask with direct dependsOn`() {
|
fun `test getDependsOnForTask with direct dependsOn`() {
|
||||||
val project = ProjectBuilder.builder().build()
|
val project = ProjectBuilder.builder().withName("myApp").build()
|
||||||
val taskA = project.tasks.register("taskA").get()
|
val taskA = project.tasks.register("taskA").get()
|
||||||
val taskB = project.tasks.register("taskB").get()
|
val taskB = project.tasks.register("taskB").get()
|
||||||
|
|
||||||
@ -67,7 +67,7 @@ class ProcessTaskUtilsTest {
|
|||||||
val dependsOn = getDependsOnForTask(taskA, dependencies)
|
val dependsOn = getDependsOnForTask(taskA, dependencies)
|
||||||
|
|
||||||
assertNotNull(dependsOn)
|
assertNotNull(dependsOn)
|
||||||
assertTrue(dependsOn!!.contains("taskB"))
|
assertTrue(dependsOn!!.contains("myApp:taskB"))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|||||||
67
packages/gradle/src/executors/gradle/get-exclude-task.ts
Normal file
67
packages/gradle/src/executors/gradle/get-exclude-task.ts
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
import { ProjectGraph } from 'nx/src/config/project-graph';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns Gradle CLI arguments to exclude dependent tasks
|
||||||
|
* that are not part of the current execution set.
|
||||||
|
*
|
||||||
|
* For example, if a project defines `dependsOn: ['lint']` for the `test` target,
|
||||||
|
* and only `test` is running, this will return: ['lint']
|
||||||
|
*/
|
||||||
|
export function getExcludeTasks(
|
||||||
|
projectGraph: ProjectGraph,
|
||||||
|
targets: { project: string; target: string; excludeDependsOn: boolean }[],
|
||||||
|
runningTaskIds: Set<string> = new Set()
|
||||||
|
): Set<string> {
|
||||||
|
const excludes = new Set<string>();
|
||||||
|
|
||||||
|
for (const { project, target, excludeDependsOn } of targets) {
|
||||||
|
if (!excludeDependsOn) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const taskDeps =
|
||||||
|
projectGraph.nodes[project]?.data?.targets?.[target]?.dependsOn ?? [];
|
||||||
|
|
||||||
|
for (const dep of taskDeps) {
|
||||||
|
const taskId = typeof dep === 'string' ? dep : dep?.target;
|
||||||
|
if (taskId && !runningTaskIds.has(taskId)) {
|
||||||
|
const [projectName, targetName] = taskId.split(':');
|
||||||
|
const taskName =
|
||||||
|
projectGraph.nodes[projectName]?.data?.targets?.[targetName]?.options
|
||||||
|
?.taskName;
|
||||||
|
if (taskName) {
|
||||||
|
excludes.add(taskName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return excludes;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getAllDependsOn(
|
||||||
|
projectGraph: ProjectGraph,
|
||||||
|
projectName: string,
|
||||||
|
targetName: string,
|
||||||
|
visited: Set<string> = new Set()
|
||||||
|
): string[] {
|
||||||
|
const dependsOn =
|
||||||
|
projectGraph[projectName]?.data?.targets?.[targetName]?.dependsOn ?? [];
|
||||||
|
|
||||||
|
const allDependsOn: string[] = [];
|
||||||
|
|
||||||
|
for (const dependency of dependsOn) {
|
||||||
|
if (!visited.has(dependency)) {
|
||||||
|
visited.add(dependency);
|
||||||
|
|
||||||
|
const [depProjectName, depTargetName] = dependency.split(':');
|
||||||
|
allDependsOn.push(dependency);
|
||||||
|
|
||||||
|
// Recursively get dependencies of the current dependency
|
||||||
|
allDependsOn.push(
|
||||||
|
...getAllDependsOn(projectGraph, depProjectName, depTargetName, visited)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return allDependsOn;
|
||||||
|
}
|
||||||
@ -1,10 +1,10 @@
|
|||||||
import { ExecutorContext, output, TaskGraph, workspaceRoot } from '@nx/devkit';
|
import { ExecutorContext, output, TaskGraph, workspaceRoot } from '@nx/devkit';
|
||||||
import {
|
import runCommandsImpl, {
|
||||||
LARGE_BUFFER,
|
LARGE_BUFFER,
|
||||||
RunCommandsOptions,
|
RunCommandsOptions,
|
||||||
} from 'nx/src/executors/run-commands/run-commands.impl';
|
} from 'nx/src/executors/run-commands/run-commands.impl';
|
||||||
import { BatchResults } from 'nx/src/tasks-runner/batch/batch-messages';
|
import { BatchResults } from 'nx/src/tasks-runner/batch/batch-messages';
|
||||||
import { gradleExecutorSchema } from './schema';
|
import { GradleExecutorSchema } from './schema';
|
||||||
import { findGradlewFile } from '../../utils/exec-gradle';
|
import { findGradlewFile } from '../../utils/exec-gradle';
|
||||||
import { dirname, join } from 'path';
|
import { dirname, join } from 'path';
|
||||||
import { execSync } from 'child_process';
|
import { execSync } from 'child_process';
|
||||||
@ -12,6 +12,7 @@ import {
|
|||||||
createPseudoTerminal,
|
createPseudoTerminal,
|
||||||
PseudoTerminal,
|
PseudoTerminal,
|
||||||
} from 'nx/src/tasks-runner/pseudo-terminal';
|
} from 'nx/src/tasks-runner/pseudo-terminal';
|
||||||
|
import { getAllDependsOn, getExcludeTasks } from './get-exclude-task';
|
||||||
|
|
||||||
export const batchRunnerPath = join(
|
export const batchRunnerPath = join(
|
||||||
__dirname,
|
__dirname,
|
||||||
@ -25,15 +26,16 @@ interface GradleTask {
|
|||||||
|
|
||||||
export default async function gradleBatch(
|
export default async function gradleBatch(
|
||||||
taskGraph: TaskGraph,
|
taskGraph: TaskGraph,
|
||||||
inputs: Record<string, gradleExecutorSchema>,
|
inputs: Record<string, GradleExecutorSchema>,
|
||||||
overrides: RunCommandsOptions,
|
overrides: RunCommandsOptions,
|
||||||
context: ExecutorContext
|
context: ExecutorContext
|
||||||
): Promise<BatchResults> {
|
): Promise<BatchResults> {
|
||||||
try {
|
try {
|
||||||
const projectName = taskGraph.tasks[taskGraph.roots[0]]?.target?.project;
|
const projectName = taskGraph.tasks[taskGraph.roots[0]]?.target?.project;
|
||||||
let projectRoot = context.projectGraph.nodes[projectName]?.data?.root ?? '';
|
let projectRoot = context.projectGraph.nodes[projectName]?.data?.root ?? '';
|
||||||
const gradlewPath = findGradlewFile(join(projectRoot, 'project.json')); // find gradlew near project root
|
let gradlewPath = findGradlewFile(join(projectRoot, 'project.json')); // find gradlew near project root
|
||||||
const root = join(context.root, dirname(gradlewPath));
|
gradlewPath = join(context.root, gradlewPath);
|
||||||
|
const root = dirname(gradlewPath);
|
||||||
|
|
||||||
// set args with passed in args and overrides in command line
|
// set args with passed in args and overrides in command line
|
||||||
const input = inputs[taskGraph.roots[0]];
|
const input = inputs[taskGraph.roots[0]];
|
||||||
@ -48,74 +50,73 @@ export default async function gradleBatch(
|
|||||||
args.push(...overrides.__overrides_unparsed__);
|
args.push(...overrides.__overrides_unparsed__);
|
||||||
}
|
}
|
||||||
|
|
||||||
const gradlewTasksToRun: Record<string, GradleTask> = Object.entries(
|
const taskIdsWithExclude = [];
|
||||||
taskGraph.tasks
|
const taskIdsWithoutExclude = [];
|
||||||
).reduce((gradlewTasksToRun, [taskId, task]) => {
|
|
||||||
const gradlewTaskName = inputs[task.id].taskName;
|
|
||||||
const testClassName = inputs[task.id].testClassName;
|
|
||||||
gradlewTasksToRun[taskId] = {
|
|
||||||
taskName: gradlewTaskName,
|
|
||||||
testClassName: testClassName,
|
|
||||||
};
|
|
||||||
return gradlewTasksToRun;
|
|
||||||
}, {});
|
|
||||||
const gradlewBatchStart = performance.mark(`gradlew-batch:start`);
|
|
||||||
|
|
||||||
const usePseudoTerminal =
|
const taskIds = Object.keys(taskGraph.tasks);
|
||||||
process.env.NX_NATIVE_COMMAND_RUNNER !== 'false' &&
|
for (const taskId of taskIds) {
|
||||||
PseudoTerminal.isSupported();
|
if (inputs[taskId].excludeDependsOn) {
|
||||||
const command = `java -jar ${batchRunnerPath} --tasks='${JSON.stringify(
|
taskIdsWithExclude.push(taskId);
|
||||||
gradlewTasksToRun
|
} else {
|
||||||
)}' --workspaceRoot=${root} --args='${args
|
taskIdsWithoutExclude.push(taskId);
|
||||||
.join(' ')
|
}
|
||||||
.replaceAll("'", '"')}' ${
|
|
||||||
process.env.NX_VERBOSE_LOGGING === 'true' ? '' : '--quiet'
|
|
||||||
}`;
|
|
||||||
let batchResults;
|
|
||||||
if (usePseudoTerminal) {
|
|
||||||
const terminal = createPseudoTerminal();
|
|
||||||
await terminal.init();
|
|
||||||
|
|
||||||
const cp = terminal.runCommand(command, {
|
|
||||||
cwd: workspaceRoot,
|
|
||||||
jsEnv: process.env,
|
|
||||||
quiet: process.env.NX_VERBOSE_LOGGING !== 'true',
|
|
||||||
});
|
|
||||||
const results = await cp.getResults();
|
|
||||||
batchResults = results.terminalOutput;
|
|
||||||
|
|
||||||
batchResults = batchResults.replace(command, '');
|
|
||||||
const startIndex = batchResults.indexOf('{');
|
|
||||||
const endIndex = batchResults.lastIndexOf('}');
|
|
||||||
batchResults = batchResults.substring(startIndex, endIndex + 1);
|
|
||||||
} else {
|
|
||||||
batchResults = execSync(command, {
|
|
||||||
cwd: workspaceRoot,
|
|
||||||
windowsHide: true,
|
|
||||||
env: process.env,
|
|
||||||
maxBuffer: LARGE_BUFFER,
|
|
||||||
}).toString();
|
|
||||||
}
|
}
|
||||||
const gradlewBatchEnd = performance.mark(`gradlew-batch:end`);
|
|
||||||
performance.measure(
|
|
||||||
`gradlew-batch`,
|
|
||||||
gradlewBatchStart.name,
|
|
||||||
gradlewBatchEnd.name
|
|
||||||
);
|
|
||||||
const gradlewBatchResults = JSON.parse(
|
|
||||||
batchResults.toString()
|
|
||||||
) as BatchResults;
|
|
||||||
|
|
||||||
Object.keys(taskGraph.tasks).forEach((taskId) => {
|
const allDependsOn = new Set<string>(taskIds);
|
||||||
if (!gradlewBatchResults[taskId]) {
|
taskIdsWithoutExclude.forEach((taskId) => {
|
||||||
gradlewBatchResults[taskId] = {
|
const [projectName, targetName] = taskId.split(':');
|
||||||
|
const dependencies = getAllDependsOn(
|
||||||
|
context.projectGraph,
|
||||||
|
projectName,
|
||||||
|
targetName
|
||||||
|
);
|
||||||
|
dependencies.forEach((dep) => allDependsOn.add(dep));
|
||||||
|
});
|
||||||
|
|
||||||
|
const gradlewTasksToRun: Record<string, GradleTask> = taskIds.reduce(
|
||||||
|
(gradlewTasksToRun, taskId) => {
|
||||||
|
const task = taskGraph.tasks[taskId];
|
||||||
|
const gradlewTaskName = inputs[task.id].taskName;
|
||||||
|
const testClassName = inputs[task.id].testClassName;
|
||||||
|
gradlewTasksToRun[taskId] = {
|
||||||
|
taskName: gradlewTaskName,
|
||||||
|
testClassName: testClassName,
|
||||||
|
};
|
||||||
|
return gradlewTasksToRun;
|
||||||
|
},
|
||||||
|
{}
|
||||||
|
);
|
||||||
|
|
||||||
|
const excludeTasks = getExcludeTasks(
|
||||||
|
context.projectGraph,
|
||||||
|
taskIdsWithExclude.map((taskId) => {
|
||||||
|
const task = taskGraph.tasks[taskId];
|
||||||
|
return {
|
||||||
|
project: task?.target?.project,
|
||||||
|
target: task?.target?.target,
|
||||||
|
excludeDependsOn: inputs[taskId]?.excludeDependsOn,
|
||||||
|
};
|
||||||
|
}),
|
||||||
|
allDependsOn
|
||||||
|
);
|
||||||
|
|
||||||
|
const batchResults = await runTasksInBatch(
|
||||||
|
gradlewTasksToRun,
|
||||||
|
excludeTasks,
|
||||||
|
args,
|
||||||
|
root
|
||||||
|
);
|
||||||
|
|
||||||
|
taskIds.forEach((taskId) => {
|
||||||
|
if (!batchResults[taskId]) {
|
||||||
|
batchResults[taskId] = {
|
||||||
success: false,
|
success: false,
|
||||||
terminalOutput: `Gradlew batch failed`,
|
terminalOutput: `Gradlew batch failed`,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
return gradlewBatchResults;
|
return batchResults;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
output.error({
|
output.error({
|
||||||
title: `Gradlew batch failed`,
|
title: `Gradlew batch failed`,
|
||||||
@ -127,3 +128,59 @@ export default async function gradleBatch(
|
|||||||
}, {} as BatchResults);
|
}, {} as BatchResults);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function runTasksInBatch(
|
||||||
|
gradlewTasksToRun: Record<string, GradleTask>,
|
||||||
|
excludeTasks: Set<string>,
|
||||||
|
args: string[],
|
||||||
|
root: string
|
||||||
|
): Promise<BatchResults> {
|
||||||
|
const gradlewBatchStart = performance.mark(`gradlew-batch:start`);
|
||||||
|
|
||||||
|
const usePseudoTerminal =
|
||||||
|
process.env.NX_NATIVE_COMMAND_RUNNER !== 'false' &&
|
||||||
|
PseudoTerminal.isSupported();
|
||||||
|
const command = `java -jar ${batchRunnerPath} --tasks='${JSON.stringify(
|
||||||
|
gradlewTasksToRun
|
||||||
|
)}' --workspaceRoot=${root} --args='${args
|
||||||
|
.join(' ')
|
||||||
|
.replaceAll("'", '"')}' --excludeTasks='${Array.from(excludeTasks).join(
|
||||||
|
','
|
||||||
|
)}' ${process.env.NX_VERBOSE_LOGGING === 'true' ? '' : '--quiet'}`;
|
||||||
|
let batchResults;
|
||||||
|
if (usePseudoTerminal && process.env.NX_VERBOSE_LOGGING !== 'true') {
|
||||||
|
const terminal = createPseudoTerminal();
|
||||||
|
await terminal.init();
|
||||||
|
|
||||||
|
const cp = terminal.runCommand(command, {
|
||||||
|
cwd: workspaceRoot,
|
||||||
|
jsEnv: process.env,
|
||||||
|
quiet: true,
|
||||||
|
});
|
||||||
|
const results = await cp.getResults();
|
||||||
|
terminal.shutdown(0);
|
||||||
|
batchResults = results.terminalOutput;
|
||||||
|
|
||||||
|
batchResults = batchResults.replace(command, '');
|
||||||
|
const startIndex = batchResults.indexOf('{');
|
||||||
|
const endIndex = batchResults.lastIndexOf('}');
|
||||||
|
// only keep the json part
|
||||||
|
batchResults = batchResults.substring(startIndex, endIndex + 1);
|
||||||
|
} else {
|
||||||
|
batchResults = execSync(command, {
|
||||||
|
cwd: workspaceRoot,
|
||||||
|
windowsHide: true,
|
||||||
|
env: process.env,
|
||||||
|
maxBuffer: LARGE_BUFFER,
|
||||||
|
}).toString();
|
||||||
|
}
|
||||||
|
const gradlewBatchEnd = performance.mark(`gradlew-batch:end`);
|
||||||
|
performance.measure(
|
||||||
|
`gradlew-batch`,
|
||||||
|
gradlewBatchStart.name,
|
||||||
|
gradlewBatchEnd.name
|
||||||
|
);
|
||||||
|
const gradlewBatchResults = JSON.parse(batchResults) as BatchResults;
|
||||||
|
|
||||||
|
return gradlewBatchResults;
|
||||||
|
}
|
||||||
|
|||||||
@ -1,11 +1,12 @@
|
|||||||
import { ExecutorContext } from '@nx/devkit';
|
import { ExecutorContext } from '@nx/devkit';
|
||||||
import { gradleExecutorSchema } from './schema';
|
import { GradleExecutorSchema } from './schema';
|
||||||
import { findGradlewFile } from '../../utils/exec-gradle';
|
import { findGradlewFile } from '../../utils/exec-gradle';
|
||||||
import { dirname, join } from 'node:path';
|
import { dirname, join } from 'node:path';
|
||||||
import runCommandsImpl from 'nx/src/executors/run-commands/run-commands.impl';
|
import runCommandsImpl from 'nx/src/executors/run-commands/run-commands.impl';
|
||||||
|
import { getExcludeTasks } from './get-exclude-task';
|
||||||
|
|
||||||
export default async function gradleExecutor(
|
export default async function gradleExecutor(
|
||||||
options: gradleExecutorSchema,
|
options: GradleExecutorSchema,
|
||||||
context: ExecutorContext
|
context: ExecutorContext
|
||||||
): Promise<{ success: boolean }> {
|
): Promise<{ success: boolean }> {
|
||||||
let projectRoot =
|
let projectRoot =
|
||||||
@ -22,6 +23,19 @@ export default async function gradleExecutor(
|
|||||||
if (options.testClassName) {
|
if (options.testClassName) {
|
||||||
args.push(`--tests`, options.testClassName);
|
args.push(`--tests`, options.testClassName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getExcludeTasks(context.projectGraph, [
|
||||||
|
{
|
||||||
|
project: context.projectName,
|
||||||
|
target: context.targetName,
|
||||||
|
excludeDependsOn: options.excludeDependsOn,
|
||||||
|
},
|
||||||
|
]).forEach((task) => {
|
||||||
|
if (task) {
|
||||||
|
args.push('--exclude-task', task);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const { success } = await runCommandsImpl(
|
const { success } = await runCommandsImpl(
|
||||||
{
|
{
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
export interface gradleExecutorSchema {
|
export interface GradleExecutorSchema {
|
||||||
taskName: string;
|
taskName: string;
|
||||||
testClassName?: string;
|
testClassName?: string;
|
||||||
args?: string[] | string;
|
args?: string[] | string;
|
||||||
|
excludeDependsOn: boolean;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -27,6 +27,12 @@
|
|||||||
],
|
],
|
||||||
"description": "The arguments to pass to the Gradle task.",
|
"description": "The arguments to pass to the Gradle task.",
|
||||||
"examples": [["--warning-mode", "all"], "--stracktrace"]
|
"examples": [["--warning-mode", "all"], "--stracktrace"]
|
||||||
|
},
|
||||||
|
"excludeDependsOn": {
|
||||||
|
"type": "boolean",
|
||||||
|
"description": "If true, the tasks will not execute its dependsOn tasks (e.g. pass --exclude-task args to gradle command). If false, the task will execute its dependsOn tasks.",
|
||||||
|
"default": true,
|
||||||
|
"x-priority": "internal"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"required": ["taskName"]
|
"required": ["taskName"]
|
||||||
|
|||||||
@ -30,7 +30,6 @@ export async function getNxProjectGraphLines(
|
|||||||
'--warning-mode',
|
'--warning-mode',
|
||||||
'none',
|
'none',
|
||||||
...gradlePluginOptionsArgs,
|
...gradlePluginOptionsArgs,
|
||||||
`-Pcwd=${dirname(gradlewFile)}`,
|
|
||||||
`-PworkspaceRoot=${workspaceRoot}`,
|
`-PworkspaceRoot=${workspaceRoot}`,
|
||||||
process.env.NX_VERBOSE_LOGGING ? '--info' : '',
|
process.env.NX_VERBOSE_LOGGING ? '--info' : '',
|
||||||
]);
|
]);
|
||||||
@ -53,7 +52,7 @@ export async function getNxProjectGraphLines(
|
|||||||
[
|
[
|
||||||
gradlewFile,
|
gradlewFile,
|
||||||
new Error(
|
new Error(
|
||||||
`Could not run 'nxProjectGraph' task. Please run 'nx generate @nx/gradle:init' to generate the necessary tasks.\n\r${e.toString()}`
|
`Could not run 'nxProjectGraph' task. Please run 'nx generate @nx/gradle:init' to add the necessary plugin dev.nx.gradle.project-graph.\n\r${e.toString()}`
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
],
|
],
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user