Skip to content

Conversation

btomala
Copy link

@btomala btomala commented Sep 23, 2025

I found that the resource from the directive test.resourceDir is added to the classpath in the main scope. It should be only in the test scope.

I thought that was an easy fix, so I tried to do so. However, I failed. I'm opening a PR with a test case for this. I'm happy to fix it if you could give me some guidance on where to look.

@Gedochao
Copy link
Contributor

The directive is declared here:

@DirectiveName("test.resourceDirs")
testResourceDirs: DirectiveValueParser.WithScopePath[List[Positioned[String]]] =
DirectiveValueParser.WithScopePath.empty(Nil)

and then put into options, here:

Resources.buildOptions(testResourceDirs).map(_.withScopeRequirement(Scope.Test))

At a glance, test scope is being passed as a requirement...

@Gedochao
Copy link
Contributor

We construct the build options with the resource directory here:

def buildOptions(resourceDirs: DirectiveValueParser.WithScopePath[List[Positioned[String]]])
: Either[BuildException, BuildOptions] = Right {
val paths = resourceDirs.value.map(_.value)
val (virtualRootOpt, rootOpt) = Directive.osRootResource(resourceDirs.scopePath)
// TODO Return a BuildException for malformed paths
val paths0 = rootOpt
.toList
.flatMap { root =>
paths.map(os.Path(_, root))
}
val virtualPaths = virtualRootOpt.map { virtualRoot =>
paths.map(path => virtualRoot / os.SubPath(path))
}
// warnIfNotExistsPath(paths0, logger) // this should be reported elsewhere (more from BuildOptions)
BuildOptions(
classPathOptions = ClassPathOptions(
resourcesDir = paths0,
resourcesVirtualDir = virtualPaths.toList.flatten
)
)
}

It seems it's then extracted while processing sources here:

private def resolveResourceDirs(
allInputs: Inputs,
preprocessedSources: Seq[PreprocessedSource]
): Seq[WithBuildRequirements[os.Path]] = {
val fromInputs = allInputs.elements
.collect { case r: ResourceDirectory => WithBuildRequirements(BuildRequirements(), r.path) }
val fromSources =
preprocessedSources.flatMap(_.options)
.flatMap(_.classPathOptions.resourcesDir)
.map(r => WithBuildRequirements(BuildRequirements(), r))
val fromSourcesWithRequirements = preprocessedSources
.flatMap(_.optionsWithTargetRequirements)
.flatMap(_.map(_.classPathOptions.resourcesDir).flatten)
fromInputs ++ fromSources ++ fromSourcesWithRequirements
}

To debug this, I'd track if build requirements (being the test scope) is respected when this field is being passed around:

resourceDirs: Seq[WithBuildRequirements[os.Path]],

Let me know if this helps, this is where I'd start.

@Gedochao Gedochao marked this pull request as draft September 23, 2025 10:08
@Gedochao
Copy link
Contributor

(note: I converted the PR to a draft, since it's not ready for review)

@btomala btomala force-pushed the bugfix/test-resource-dir-in-main-scope branch from 74b7f89 to ce1a868 Compare September 24, 2025 11:02
expect(resourcesDirs.exists(_.last == "mainResources"))
val hasTestResources = resourcesDirs.exists(_.last == "testResources")
expect(if isTestScope then hasTestResources else !hasTestResources)
expect(resourcesDirs.toSet == sourcesResourcesDirs.toSet)
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure if these tests were testing the resolving of test scope resourceDir; it was testing only if the build options are passed correctly.
I was expecting that this would be equal, but it's not. It wasn't even before my changes. Now this test is failing.

.filter(!resourceDirsFromCli.contains(_))
.map(ResourceDirectory(_))
}
val finalInputs = allInputs.add(resourceDirectoriesFromDirectives)
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removing this resolves my issue and fixes the tests. The resourceDirectoriesFromDirectives extracts the directives and puts them in the input without scope. Later on in the resolveResourceDirs, the paths were extracted from input and wrapped into WithBuildRequirements without scope (because it wasn't available at this point) using the default main scope.

However, I'm not sure what the other consequences of this change are, as the allInputs are returned from the forInputs method. I don't know where this is used yet.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

right... so I'm guessing we need to respect the scope here

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another option to address the issue will be to pass the allInputs to resoveReourceDirs instead of finalInputs

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants