This message was deleted.
# community-support
s
This message was deleted.
v
Why do you not simply share all fragments (or the directory) through the
openApi
configuration? While you might get the path in-place with your setup and thus can use those tricks you used, I'm not sure this is always the case or in any way guaranteed.
j
well I wondered about that : i was under the impression that it was not possible to use directories as artifacts so I don't know how to share a directory through the configuration. (I'll try and find the reference that led me to that) also the openapi generator requires a single file input (the main spec file) for its inputspec setting inputSpec.set(provider { openApiConf.singleFile.path }) I'm not sure how I can share both the path of the entrypoint and the path of the directory containing the fragments that should be considered as input for the gradle task cache
v
Well, your impression was wrong. :-) Otherwise also secondary variant with the classes directory you mentioned would not be possible. ;-) Regarding the entry point, many options exist. Looking at the file contents to find the entry point, using a conventional name you use everywhere as entry point, just document which is the entry point, having an additional file in the configuration that defines the entry point, ....
j
I had conventional name in mind as it is the easiest way, I would rather not rely on documentation and manual human action. I'm not sure how having the additional file in the confguration to define the entry point would work
the openapi generator expect the inputspec as an extension setting (this line)
Copy code
inputSpec.set(provider { openApiConf.singleFile.path })
how would I inspect the content of a file in that context ?
I would like to preserve the caching capabilities of course šŸ™‚
I guess having an extra file with a conventional name in the configuration that documents the entrypoint would also require inspecting that file's contents
v
Those were just done quick rough ideas. Additional file could e.g. be some text file with the entrypoint in it that then has some conventional name, if you for example don't want to use a conventional name for the entrypoint. Looking at file contents you can just do in the provider. Another way could be to share the entrypoint in one configuration and the fragments in a second configuration, requiring to first combine them again on consumer side. Or to just have the entrypoint on top-level and all other files at least one directory deep.
As I said, many ways. Be creative. :-)
j
so within the scope of the provider's lambda I'm free to do as many side effects as I want without breaking either task or config cache
Another way could be to share the entrypoint in one configuration and the fragments in a second configuration, requiring to first combine them again on consumer side.
wouldn't this require having 2 entries in the dependencies : one for each configuration ?
šŸ‘ 1
v
Providers are calculated when storing the CC cache entry, so probably. But just try it and see.
j
so the dual configuration is not the way I want to go šŸ™‚ I would rather keep a single dependency entry
I'll try
a file that defines the entry point
as I don't understand how
Or to just have the entrypoint on top-level and all other files at least one directory deep.
would help
v
Yeah, as I said, just rough ideas. Be creative and maybe make up yet another way, that's the fun of developing software. :-)
Why not?
The entry point will then be the only file on top level
j
yeah well the problem is that many of the creative ways break caches or trigger warnings or have beahviours that in no way guaranteed šŸ™‚
The entry point will then be the only file on top level
hmm and I would add the directory to the configuration and inspect the directory content to find the entrypoint (it being the single file) and inspect the directory content again to find the fragment's directory I guess that could work
v
I don't know why you should need the "second inspection"
j
Copy code
openApiGenerate {
  // [... irrelevant openapi config ...]
  // inputSpec.set("$projectDir/../api-contract/contract/spec.yaml")
  // replaced by
  inputSpec.set(provider { openApiConf.singleFile.path })
}
 tasks.openApiGenerate {
     // ensures the task depends on all open api fragments next to the contract (aka common.yaml and foo.yaml)
     inputs.dir(file("$projectDir/../api-contract/contract/"))
 }
one inspection in the openApiGenerate block's provider to get the entrypoint one in the openApiGenerate task definition to add the inputs
since they are not in the same scope I don't think I can reuse the results in a cache safe way (or at least I don't know how)
v
You don't need the results. Just define the whole configuration as input files. No need to separate out only the fragments.
j
Just define the whole configuration as input files. No need to separate out only the fragments.
I have literally no idea what this sentence means šŸ˜ž I have published a repo with the full current solution at https://gitlab.com/jeantil/gradle-openapi-sharing the publisher project is https://gitlab.com/jeantil/gradle-openapi-sharing/-/blob/main/api-contract/build.gradle.kts the consumer projects are https://gitlab.com/jeantil/gradle-openapi-sharing/-/blob/main/api-client/build.gradle.kts and https://gitlab.com/jeantil/gradle-openapi-sharing/-/blob/main/api/build.gradle.kts to verify that things work as expected I run
Copy code
./gradlew openApiGenerate
I expect that running this twice in a row results in 2 up 2 date tasks on the second run I expect that changing anything in either the spec.yaml file or in any of the fragments results then running the comand again triggers a build I have not yet started verifying the config cache behavior
v
inputs.files(openApiConf)
j
so I changed my structure to
Copy code
.
ā”œā”€ā”€ api
│  └── build.gradle.kts
ā”œā”€ā”€ api-client
│  └── build.gradle.kts
ā”œā”€ā”€ api-contract
│  ā”œā”€ā”€ build.gradle.kts
│  └── contract
│     ā”œā”€ā”€ fragments
│     │  ā”œā”€ā”€ common.yaml
│     │  └── foo.yaml
│     └── spec.yaml
ā”œā”€ā”€ build.gradle.kts
ā”œā”€ā”€ README.md
└── settings.gradle.kts
(moved the fragment files to the
fragments
directory) I changed the producer build to
Copy code
artifacts {
    add("openApi", file(layout.projectDirectory.dir("contract")))
}
but my attempt at deriving the entrypoint
Copy code
inputSpec.set(provider {
        openApiConf.singleFile.listFiles()?.filter { it.isFile }?.first()?.path
    })
fails
Copy code
* Exception is:
org.gradle.internal.execution.WorkValidationException: A problem was found with the configuration of task ':api-client:openApiGenerate' (type 'GenerateTask').
  - In plugin 'org.openapi.generator' type 'org.openapitools.generator.gradle.plugin.tasks.GenerateTask' property '$1' has unsupported value 'configuration ':api-client:openApiConf''.

    Reason: Type 'DefaultConfiguration' cannot be converted to a directory.

    Possible solutions:
      1. Use a String or CharSequence path, for example 'src/main/java' or '/usr/include'.
      2. Use a String or CharSequence URI, for example 'file:/usr/include'.
      3. Use a File instance.
      4. Use a Path instance.
      5. Use a Directory instance.
      6. Use a RegularFile instance.
      7. Use a URI or URL instance.
      8. Use a TextResource instance.
I'm not sure what I did wrong šŸ˜ž
v
You used
inputs.dir
not
inputs.files
j
so this
Copy code
add("openApi", file(layout.projectDirectory.dir("contract")))
should be
Copy code
add("openApi", layout.projectDirectory.files("contract"))
?
v
No
On the consumer side
Where you define the additional inputs for the task
There you used
inputs.dir...
instead of
inputs.files...
like I showed
j
on the consumer side I have commented this for now and it still fails to resolve the input spec
Copy code
//tasks.openApiGenerate {
    // ensures the task depends on all open api fragments next to the contract
//    inputs.dir(openApiConf)
//}
v
But not with the same error probably
j
nevermind, in my fumbling try to try to make things work I had reverted the inputs.dir comment
Copy code
inputSpec.set(  provider {
        openApiConf.singleFile.listFiles()?.filter { it.isFile }?.first()!!.path
    })
}

tasks.openApiGenerate {
    // ensures the task depends on all open api fragments next to the contract
    inputs.files(openApiConf)
}
at least says it succeeds šŸ™‚
the singleFile works because on the producer side I declare only a single artifact for the directory
so the file collection does indeed contain a single entry
v
Yes, I deleted my message already, realizing that there is only the directory as file, not the single files šŸ™‚
j
thank you very much for you help
šŸ‘Œ 1
the error is really hard to understand šŸ˜•
v
Use
inputs.dir(openApiConf).withPropertyName("openApiSpec")
, then the error should be better understandable
As then "openApiSpec" should be used instead of "1" in the error message
šŸ’” 1
And besides that, if you run with
--stacktrace
(or
-s
) you should also see the exact line where the error happens
j
well not really
Copy code
* Exception is:
org.gradle.internal.execution.WorkValidationException: A problem was found with the configuration of task ':api-client:openApiGenerate' (type 'GenerateTask').
  - In plugin 'org.openapi.generator' type 'org.openapitools.generator.gradle.plugin.tasks.GenerateTask' property '$1' has unsupported value 'configuration ':api-client:openApiConf''.

    Reason: Type 'DefaultConfiguration' cannot be converted to a directory.

    Possible solutions:
      1. Use a String or CharSequence path, for example 'src/main/java' or '/usr/include'.
      2. Use a String or CharSequence URI, for example 'file:/usr/include'.
      3. Use a File instance.
      4. Use a Path instance.
      5. Use a Directory instance.
      6. Use a RegularFile instance.
      7. Use a URI or URL instance.
      8. Use a TextResource instance.

    For more information, please refer to <https://docs.gradle.org/8.2.1/userguide/validation_problems.html#unsupported_notation> in the Gradle documentation.
        at org.gradle.internal.execution.WorkValidationException$BuilderWithSummary.build(WorkValidationException.java:133)
        at org.gradle.internal.execution.WorkValidationException$BuilderWithSummary.get(WorkValidationException.java:115)
        at org.gradle.internal.execution.steps.ValidateStep.throwValidationException(ValidateStep.java:162)
        at org.gradle.internal.execution.steps.ValidateStep.execute(ValidateStep.java:86)
        at org.gradle.internal.execution.steps.ValidateStep.execute(ValidateStep.java:49)
        at org.gradle.internal.execution.steps.CaptureStateBeforeExecutionStep.execute(CaptureStateBeforeExecutionStep.java:71)
        at org.gradle.internal.execution.steps.CaptureStateBeforeExecutionStep.execute(CaptureStateBeforeExecutionStep.java:45)
        at org.gradle.internal.execution.steps.SkipEmptyWorkStep.executeWithNonEmptySources(SkipEmptyWorkStep.java:177)
        at org.gradle.internal.execution.steps.SkipEmptyWorkStep.execute(SkipEmptyWorkStep.java:81)
        at org.gradle.internal.execution.steps.SkipEmptyWorkStep.execute(SkipEmptyWorkStep.java:53)
        at org.gradle.internal.execution.steps.RemoveUntrackedExecutionStateStep.execute(RemoveUntrackedExecutionStateStep.java:32)
        at org.gradle.internal.execution.steps.RemoveUntrackedExecutionStateStep.execute(RemoveUntrackedExecutionStateStep.java:21)
        at org.gradle.internal.execution.steps.legacy.MarkSnapshottingInputsStartedStep.execute(MarkSnapshottingInputsStartedStep.java:38)
        at org.gradle.internal.execution.steps.LoadPreviousExecutionStateStep.execute(LoadPreviousExecutionStateStep.java:36)
        at org.gradle.internal.execution.steps.LoadPreviousExecutionStateStep.execute(LoadPreviousExecutionStateStep.java:23)
        at org.gradle.internal.execution.steps.CleanupStaleOutputsStep.execute(CleanupStaleOutputsStep.java:75)
        at org.gradle.internal.execution.steps.CleanupStaleOutputsStep.execute(CleanupStaleOutputsStep.java:41)
        at org.gradle.internal.execution.steps.AssignWorkspaceStep.lambda$execute$0(AssignWorkspaceStep.java:32)
        at org.gradle.api.internal.tasks.execution.TaskExecution$4.withWorkspace(TaskExecution.java:293)
        at org.gradle.internal.execution.steps.AssignWorkspaceStep.execute(AssignWorkspaceStep.java:30)
        at org.gradle.internal.execution.steps.AssignWorkspaceStep.execute(AssignWorkspaceStep.java:21)
        at org.gradle.internal.execution.steps.IdentityCacheStep.execute(IdentityCacheStep.java:37)
        at org.gradle.internal.execution.steps.IdentityCacheStep.execute(IdentityCacheStep.java:27)
        at org.gradle.internal.execution.steps.IdentifyStep.execute(IdentifyStep.java:47)
        at org.gradle.internal.execution.steps.IdentifyStep.execute(IdentifyStep.java:34)
        at org.gradle.internal.execution.impl.DefaultExecutionEngine$1.execute(DefaultExecutionEngine.java:64)
is happens with gradle internals very early in the process the withPropertyName tips is very useful thanks !!
I was trying to debug the stack trace when I got your message šŸ™‚
v
Ah, right, this is resolved lazily of course, so the stacktrace indeed does not help much here, sorry.
j
I'll do a writeup of all this with details on the various approaches thanks again
šŸ‘Œ 2