Incremental build task for variable generated output files

I have a gradle task which generated Java files from an input file. The files generated vary according to the contents of the input file. I’ve tried using inputs and outputs to have gradle skip running the task contents when nothing has changed, but the task is always marked UP-TO-DATE.

task generateJavaUiProp(type: JavaExec) {
	inputs.file file("$mcEcoreFile")
	outputs.files fileTree("$generatedSrcDir").include('**/*.java')

	main = "$rootProject.codeGenMain"
	classpath = project(":CodeGen").sourceSets.main.runtimeClasspath
	
	args "--javamodelprop",
		"--ecore", "$mcEcoreFile",
		"--outdir", "$generatedSrcDir",
		"--imports", "${mcPkgModel}.*,${mcPkgModelExt}.*"
}

The input file is $mcEcoreFile. If I update this file via edit and run this task it is still marked UP-TO-DATE. If I delete any files under outputs.files it is still UP-TO-DATE.

I assume that outputs.files is being calculated (e.g. scan the dir for matching files) each time the task is run. I cannot declare a static list of files for outputs.files because they vary depending on what’s in the $mcEcoreFile.

My understanding is that you have to use inputs.files and outputs.files together or else the incremental detection in gradle doesn’t work. I did try with no outputs.files but that always resulted in UP-TO-DATE.

What I want is for gradle to run this task whenever the inputs.file ($mcEcoreFile) changes.

What am I missing?

EDIT 2

I have modified my task to create a single output file:

task generateJavaUiProp(type: JavaExec) {
	def timestampFile = "${generatedSrcDir}/timestamp"
	inputs.file file("$mcEcoreFile")
	outputs.file file("$timestampFile")

	doLast {
	    file("$timestampFile").write 'updated'
	}

	main = "$rootProject.codeGenMain"
	classpath = project(":CodeGen").sourceSets.main.runtimeClasspath
	
	args "--javamodelprop",
		"--ecore", "$mcEcoreFile",
		"--outdir", "$generatedSrcDir",
		"--imports", "${mcPkgModel}.*,${mcPkgModelExt}.*"
}

This task runs fine the first time, but after that will always report UP-TO-DATE even if the inputs.file file has been updated. I tested as such

# gradle generateJavaUiProp
....
:generateJavaUiProp
...
# gradle generateJavaUiProp
:generateJavaUiProp UP-TO-DATE
# touch $mcEcoreFile
# gradle generateJavaUiProp
:generateJavaUiProp UP-TO-DATE

The “touch $mcEcoreFile” updates the last modified time of the inputs.file yet gradle still thinks the task is up-to-date. What is going on here?