"You must specify a base url or at least one artifact pattern for an Ivy repository." error

Hi all,

I’m running gradle v6.8.3 and randomly run into the following error when one of the gradle tasks are executing
> You must specify a base url or at least one artifact pattern for an Ivy repository.

The error occurs at random times and is not reproducible on command. It seems like any gradle task can run into the issue with some more frequently running into it than others. Most commonly, the ...:compileJava commands are what seem to trigger it.

The confusing part is that it seems like there is an ivy artifact pattern stated.

                ivy {
                    name = "Node.js"
                    setUrl(someStringHere)
                    patternLayout {
                        artifact("v[revision]/[artifact](-v[revision]-[classifier]).[ext]")
                        ivy("v[revision]/ivy.xml")
                    }
                    metadataSources {
                        artifact()
                    }
                }

It’s difficult to debug since it’s not reproducible on command as far as I can tell. Has anyone else ran into an issue like this? It seems similar to some other topics running into the issue You must specify a URL for a Maven repository but so far haven’t been able to pinpoint what the issue here is.

Thanks for your help in advance!

I’ve found the place in the gradle code base that is throwing the exception.

This isn’t super helpful given that I don’t understand the rest of the codebase though.

Maybe you should update your Gradle version, from 7.0 on it also tells the name of the repository that has the problem, so that you know actually whether it is that repository it complains about.

Besides that, what is your full build script?
Where do you do that configuration?
Is it for example done in some tasks execution phase and thus can maybe be too late or something like that?

I’m experiencing a similar issue on Gradle 7.4.2. Unfortunately I haven’t been able to reproduce this in a project that I can share. Here is the error message including the repository display name (with some proprietary details removed):

You must specify a base url or at least one artifact pattern for the Ivy repository 'Node.js(https://our.corporate.repository.com/repository/nodejs/)'

Strangely, the error message asserts that no base url is configured, but the display name of the repository includes a base url.

Here is the stack trace from the build scan:

org.gradle.api.InvalidUserDataException: You must specify a base url or at least one artifact pattern for the Ivy repository 'Node.js(https://our.corporate.repository.com/repository/nodejs/)'
    at org.gradle.api.internal.artifacts.repositories.DefaultIvyArtifactRepository.validate(DefaultIvyArtifactRepository.java:215)	
    at org.gradle.api.internal.artifacts.repositories.DefaultIvyArtifactRepository.createDescriptor(DefaultIvyArtifactRepository.java:164)	
    at org.gradle.api.internal.artifacts.repositories.AbstractResolutionAwareArtifactRepository.getDescriptor(AbstractResolutionAwareArtifactRepository.java:33)	
    at org.gradle.api.internal.artifacts.configurations.ResolveConfigurationResolutionBuildOperationDetails$RepositoryImpl.lambda$transform$0(ResolveConfigurationResolutionBuildOperationDetails.java:164)	
    at org.gradle.util.internal.CollectionUtils.collect(CollectionUtils.java:207)	
    at org.gradle.util.internal.CollectionUtils.collect(CollectionUtils.java:199)	
    at org.gradle.api.internal.artifacts.configurations.ResolveConfigurationResolutionBuildOperationDetails$RepositoryImpl.transform(ResolveConfigurationResolutionBuildOperationDetails.java:164)	
    at org.gradle.api.internal.artifacts.configurations.ResolveConfigurationResolutionBuildOperationDetails$RepositoryImpl.access$000(ResolveConfigurationResolutionBuildOperationDetails.java:159)	
    at org.gradle.api.internal.artifacts.configurations.ResolveConfigurationResolutionBuildOperationDetails.<init>(ResolveConfigurationResolutionBuildOperationDetails.java:63)	
    at org.gradle.api.internal.artifacts.configurations.DefaultConfiguration$1.description(DefaultConfiguration.java:707)	
    at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:59)	
    at org.gradle.internal.operations.DefaultBuildOperationRunner.call(DefaultBuildOperationRunner.java:53)	
    at org.gradle.internal.operations.DefaultBuildOperationExecutor.call(DefaultBuildOperationExecutor.java:73)	
    at org.gradle.api.internal.artifacts.configurations.DefaultConfiguration.resolveGraphIfRequired(DefaultConfiguration.java:646)	
    at org.gradle.api.internal.artifacts.configurations.DefaultConfiguration.lambda$resolveExclusively$4(DefaultConfiguration.java:626)	
    at org.gradle.api.internal.project.DefaultProjectStateRegistry$CalculatedModelValueImpl.update(DefaultProjectStateRegistry.java:464)	
    at org.gradle.api.internal.artifacts.configurations.DefaultConfiguration.resolveExclusively(DefaultConfiguration.java:623)	
    at org.gradle.api.internal.artifacts.configurations.DefaultConfiguration.resolveToStateOrLater(DefaultConfiguration.java:610)	
    at org.gradle.api.internal.artifacts.configurations.DefaultConfiguration.access$1900(DefaultConfiguration.java:159)	
    at org.gradle.api.internal.artifacts.configurations.DefaultConfiguration$SelectedArtifactsProvider.getValue(DefaultConfiguration.java:1422)	
    at org.gradle.api.internal.artifacts.configurations.DefaultConfiguration$SelectedArtifactsProvider.getValue(DefaultConfiguration.java:1412)	
    at org.gradle.api.internal.artifacts.configurations.DefaultConfiguration$ConfigurationFileCollection.getSelectedArtifacts(DefaultConfiguration.java:1486)	
    at org.gradle.api.internal.artifacts.configurations.DefaultConfiguration$ConfigurationFileCollection.visitContents(DefaultConfiguration.java:1473)	
    at org.gradle.api.internal.artifacts.configurations.DefaultConfiguration.visitContents(DefaultConfiguration.java:509)	
    at org.gradle.api.internal.file.AbstractFileCollection.visitStructure(AbstractFileCollection.java:351)	
    at org.gradle.api.internal.file.CompositeFileCollection.lambda$visitContents$0(CompositeFileCollection.java:119)	
    at org.gradle.api.internal.file.collections.UnpackingVisitor.add(UnpackingVisitor.java:64)	
    at org.gradle.api.internal.file.collections.UnpackingVisitor.add(UnpackingVisitor.java:89)	
    at org.gradle.api.internal.file.DefaultFileCollectionFactory$ResolvingFileCollection.visitChildren(DefaultFileCollectionFactory.java:333)	
    at org.gradle.api.internal.file.CompositeFileCollection.visitContents(CompositeFileCollection.java:119)	
    at org.gradle.api.internal.file.AbstractFileCollection.visitStructure(AbstractFileCollection.java:351)	
    at org.gradle.api.internal.file.CompositeFileCollection.lambda$visitContents$0(CompositeFileCollection.java:119)	
    at org.gradle.api.internal.tasks.PropertyFileCollection.visitChildren(PropertyFileCollection.java:48)	
    at org.gradle.api.internal.file.CompositeFileCollection.visitContents(CompositeFileCollection.java:119)	
    at org.gradle.api.internal.file.AbstractFileCollection.visitStructure(AbstractFileCollection.java:351)	
    at org.gradle.internal.fingerprint.impl.DefaultFileCollectionSnapshotter.snapshot(DefaultFileCollectionSnapshotter.java:51)	
    at org.gradle.internal.execution.fingerprint.impl.DefaultInputFingerprinter$InputCollectingVisitor.visitInputFileProperty(DefaultInputFingerprinter.java:131)	
    at org.gradle.api.internal.tasks.execution.TaskExecution.visitRegularInputs(TaskExecution.java:328)	
    at org.gradle.internal.execution.fingerprint.impl.DefaultInputFingerprinter.fingerprintInputProperties(DefaultInputFingerprinter.java:61)	
    at org.gradle.internal.execution.steps.CaptureStateBeforeExecutionStep.captureExecutionStateWithOutputs(CaptureStateBeforeExecutionStep.java:193)	
    at org.gradle.internal.execution.steps.CaptureStateBeforeExecutionStep.lambda$captureExecutionState$1(CaptureStateBeforeExecutionStep.java:141)	
    at org.gradle.internal.execution.steps.BuildOperationStep$1.call(BuildOperationStep.java:37)	
    at org.gradle.internal.operations.DefaultBuildOperationRunner$CallableBuildOperationWorker.execute(DefaultBuildOperationRunner.java:204)	
    at org.gradle.internal.operations.DefaultBuildOperationRunner$CallableBuildOperationWorker.execute(DefaultBuildOperationRunner.java:199)	
    at org.gradle.internal.operations.DefaultBuildOperationRunner$2.execute(DefaultBuildOperationRunner.java:66)	
    at org.gradle.internal.operations.DefaultBuildOperationRunner$2.execute(DefaultBuildOperationRunner.java:59)	
    at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:157)	
    at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:59)	
    at org.gradle.internal.operations.DefaultBuildOperationRunner.call(DefaultBuildOperationRunner.java:53)	
    at org.gradle.internal.operations.DefaultBuildOperationExecutor.call(DefaultBuildOperationExecutor.java:73)	
    at org.gradle.internal.execution.steps.BuildOperationStep.operation(BuildOperationStep.java:34)	
    at org.gradle.internal.execution.steps.CaptureStateBeforeExecutionStep.captureExecutionState(CaptureStateBeforeExecutionStep.java:130)	
    at org.gradle.internal.execution.steps.CaptureStateBeforeExecutionStep.lambda$execute$0(CaptureStateBeforeExecutionStep.java:75)	
    at org.gradle.internal.execution.steps.CaptureStateBeforeExecutionStep.execute(CaptureStateBeforeExecutionStep.java:75)	
    at org.gradle.internal.execution.steps.CaptureStateBeforeExecutionStep.execute(CaptureStateBeforeExecutionStep.java:50)	
    at org.gradle.internal.execution.steps.SkipEmptyWorkStep.executeWithNoEmptySources(SkipEmptyWorkStep.java:249)	
    at org.gradle.internal.execution.steps.SkipEmptyWorkStep.executeWithNoEmptySources(SkipEmptyWorkStep.java:204)	
    at org.gradle.internal.execution.steps.SkipEmptyWorkStep.execute(SkipEmptyWorkStep.java:83)	
    at org.gradle.internal.execution.steps.SkipEmptyWorkStep.execute(SkipEmptyWorkStep.java:54)	
    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:43)	
    at org.gradle.internal.execution.steps.LoadPreviousExecutionStateStep.execute(LoadPreviousExecutionStateStep.java:31)	
    at org.gradle.internal.execution.steps.AssignWorkspaceStep.lambda$execute$0(AssignWorkspaceStep.java:40)	
    at org.gradle.api.internal.tasks.execution.TaskExecution$4.withWorkspace(TaskExecution.java:287)	
    at org.gradle.internal.execution.steps.AssignWorkspaceStep.execute(AssignWorkspaceStep.java:40)	
    at org.gradle.internal.execution.steps.AssignWorkspaceStep.execute(AssignWorkspaceStep.java:30)	
    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:44)	
    at org.gradle.internal.execution.steps.IdentifyStep.execute(IdentifyStep.java:33)	
    at org.gradle.internal.execution.impl.DefaultExecutionEngine$1.execute(DefaultExecutionEngine.java:76)	
    at org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter.executeIfValid(ExecuteActionsTaskExecuter.java:144)	
    at org.gradle.api.internal.tasks.execution.ExecuteActionsTaskExecuter.execute(ExecuteActionsTaskExecuter.java:133)	
    at org.gradle.api.internal.tasks.execution.CleanupStaleOutputsExecuter.execute(CleanupStaleOutputsExecuter.java:77)	
    at org.gradle.api.internal.tasks.execution.FinalizePropertiesTaskExecuter.execute(FinalizePropertiesTaskExecuter.java:46)	
    at org.gradle.api.internal.tasks.execution.ResolveTaskExecutionModeExecuter.execute(ResolveTaskExecutionModeExecuter.java:51)	
    at org.gradle.api.internal.tasks.execution.SkipTaskWithNoActionsExecuter.execute(SkipTaskWithNoActionsExecuter.java:57)	
    at org.gradle.api.internal.tasks.execution.SkipOnlyIfTaskExecuter.execute(SkipOnlyIfTaskExecuter.java:56)	
    at org.gradle.api.internal.tasks.execution.CatchExceptionTaskExecuter.execute(CatchExceptionTaskExecuter.java:36)	
    at org.gradle.api.internal.tasks.execution.EventFiringTaskExecuter$1.executeTask(EventFiringTaskExecuter.java:77)	
    at org.gradle.api.internal.tasks.execution.EventFiringTaskExecuter$1.call(EventFiringTaskExecuter.java:55)	
    at org.gradle.api.internal.tasks.execution.EventFiringTaskExecuter$1.call(EventFiringTaskExecuter.java:52)	
    at org.gradle.internal.operations.DefaultBuildOperationRunner$CallableBuildOperationWorker.execute(DefaultBuildOperationRunner.java:204)	
    at org.gradle.internal.operations.DefaultBuildOperationRunner$CallableBuildOperationWorker.execute(DefaultBuildOperationRunner.java:199)	
    at org.gradle.internal.operations.DefaultBuildOperationRunner$2.execute(DefaultBuildOperationRunner.java:66)	
    at org.gradle.internal.operations.DefaultBuildOperationRunner$2.execute(DefaultBuildOperationRunner.java:59)	
    at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:157)	
    at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:59)	
    at org.gradle.internal.operations.DefaultBuildOperationRunner.call(DefaultBuildOperationRunner.java:53)	
    at org.gradle.internal.operations.DefaultBuildOperationExecutor.call(DefaultBuildOperationExecutor.java:73)	
    at org.gradle.api.internal.tasks.execution.EventFiringTaskExecuter.execute(EventFiringTaskExecuter.java:52)	
    at org.gradle.execution.plan.LocalTaskNodeExecutor.execute(LocalTaskNodeExecutor.java:74)	
    at org.gradle.execution.taskgraph.DefaultTaskExecutionGraph$InvokeNodeExecutorsAction.execute(DefaultTaskExecutionGraph.java:333)	
    at org.gradle.execution.taskgraph.DefaultTaskExecutionGraph$InvokeNodeExecutorsAction.execute(DefaultTaskExecutionGraph.java:320)	
    at org.gradle.execution.taskgraph.DefaultTaskExecutionGraph$BuildOperationAwareExecutionAction.execute(DefaultTaskExecutionGraph.java:313)	
    at org.gradle.execution.taskgraph.DefaultTaskExecutionGraph$BuildOperationAwareExecutionAction.execute(DefaultTaskExecutionGraph.java:299)	
    at org.gradle.execution.plan.DefaultPlanExecutor$ExecutorWorker.lambda$run$0(DefaultPlanExecutor.java:143)	
    at org.gradle.execution.plan.DefaultPlanExecutor$ExecutorWorker.execute(DefaultPlanExecutor.java:227)	
    at org.gradle.execution.plan.DefaultPlanExecutor$ExecutorWorker.executeNextNode(DefaultPlanExecutor.java:218)	
    at org.gradle.execution.plan.DefaultPlanExecutor$ExecutorWorker.run(DefaultPlanExecutor.java:140)	
    at org.gradle.internal.concurrent.ExecutorPolicy$CatchAndRecordFailures.onExecute(ExecutorPolicy.java:64)	
    at org.gradle.internal.concurrent.ManagedExecutorImpl$1.run(ManagedExecutorImpl.java:48)

Here are all the details I think might be relevant:

  • This particular repository is a project-specific repository and is configured in the settings.gradle using dependencyResolutionManagement.
  • We configure additional corporate-wide repositories in an init script in $GRADLE_HOME/init.d using a settingsEvaluated callback to add repositories, again using dependencyResolutionManagement.
  • We also call repositoriesMode.set(RepositoriesMode.PREFER_SETTINGS) within dependencyResolutionManagement and our build runs without emitting any “prefer settings” warnings. My understanding is that this is sufficient to say we don’t do any modification of repositories at execution time.
  • This particular repository is set up to replace the repository that is normally added by the gradle node plugin (GitHub - node-gradle/gradle-node-plugin: Gradle plugin for integrating NodeJS in your build.). Normally the gradle node plugin dynamically adds a repository based on an extension property (distBaseUrl). However, this behavior is not compatible with PREFER_SETTINGS. Therefore we opted to set this property to null and configure a suitable replacement in our settings.gradle. Before we made this change, we were getting “prefer settings” warnings during the build. I have no idea if this information is related but I’m including it because it’s unusual.

I did attempt a very shallow dive into DefaultArtifactRepository.java and one thing caught my eye. The createDescriptor method validates the result of getSchemes. getSchemes lazily initializes a private Set field, but has no apparent synchronization to avoid returning a partially-initialized set. It’s not clear to me whether instances of this object are shared between threads in practice, but we do run our builds in parallel with 6 worker threads. And it might explain the apparent contradiction in the error message (url not being set despite appearing in the display name).

    @Override
    protected RepositoryDescriptor createDescriptor() {
        Set<String> schemes = getSchemes();
        validate(schemes);

    ...

    private Set<String> getSchemes() {
        if (schemes == null) {
            URI uri = getUrl();
            schemes = new LinkedHashSet<>();
            layout.addSchemes(uri, schemes);
            additionalPatternsLayout.addSchemes(uri, schemes);
        }
        return schemes;
    }

Thanks in advance for the help!

How do you define the repository in the settings script?

This is the relevant bit from our settings.gradle:

dependencyResolutionManagement {
    repositoriesMode.set(RepositoriesMode.PREFER_SETTINGS)
    repositories {
        ivy {
            name = "Node.js"
            setUrl("https://our.corporate.repository.com/repository/nodejs/")
            patternLayout {
                artifact("v[revision]/[artifact](-v[revision]-[classifier]).[ext]")
            }
            metadataSources {
                artifact()
            }
            content {
                includeModule("org.nodejs", "node")
            }
        }
    }
}

And here is what we have in the init script:

settingsEvaluated { settings ->
    settings.dependencyResolutionManagement {
        repositories {
            maven {
                name = 'another-corporate-repository'
                url = uri('https://another.corporate.repository.com/repository/releases/')
            }
            ... // several more like this
        }
    }
}

Hm, very strange.
We use basically the same repository declaration for Node.js, just that I use ivy("theurlhere") { ... } instead of ivy { ... } with setUrl inside, but that should be exactly the same.

I don’t think that the schemes synchronization is an issue.
There should still not be a partially-initialized set given out I think.
The lazy initialization is just done multiple times if it is used on different threads.
Even if thread A checks for null, enters the if, assigns schemes to the empty list and then thread B kicks in before schemes is filled, thread B will still see schemes as null and just do the same initialization logic.

I have no idea what your issue could be without a reproducer.
Maybe you should report it as bug to Gradle and see whether they have any idea.
But without being able to reproduce and debug it, it might also be hard for them.
If you can reproduce it under the debugger, it might be beneficial if you could actually debug the problem and find the root cause.

I was able to reproduce this under the debugger by forcing the threads to interleave like I described. Here’s the process I used:

  1. Set breakpoint on DefaultIvyArtifactRepository lines 163 and 223 (make sure they only suspend the current thread and not all threads)
  2. Run build with -Dorg.gradle.debug=true --no-daemon
  3. Attach debugger on port 5005 and wait until two or more threads stop at 163
  4. Resume one thread until it stops again at 223
  5. Go back to any other thread and step over the getSchemes() method call
  6. (Optional) Generate a thread dump
  7. Detach debugger

This caused several tasks to fail with the “You must specify a base url…” error. I’ll attach the thread dump from step 6 since it doesn’t contain any sensitive information.

I tried to set up a minimum-reproducible-sample for this to make it easier to share. I can reproduce the message but for some reason in my sample project it only emits a warning instead of failing the task (see below). That’s as far as I got: I haven’t been able to determine the difference between my actual project vs. my sample project that escalates the warning to an error. Do you think this is enough information to file a bug report?

$ ./gradlew resolveConfiguration -Dorg.gradle.debug=true --no-daemon
To honour the JVM settings for this build a single-use Daemon process will be forked. See https://docs.gradle.org/7.4.2/userguide/gradle_daemon.html#sec:disabling_the_daemon.
Daemon will be stopped at the end of the build 

> Task :module-1:resolveConfiguration
Execution optimizations have been disabled for task ':module-1:resolveConfiguration' to ensure correctness due to the following reasons:
  - Property '$1' cannot be resolved:  You must specify a base url or at least one artifact pattern for the Ivy repository 'myrepo(file:/path/to/base-url-issue/fake-repo/)'. Reason: An input file collection couldn't be resolved, making it impossible to determine task inputs. Please refer to https://docs.gradle.org/7.4.2/userguide/validation_problems.html#unresolvable_input for more details about this problem.

> Task :module-2:resolveConfiguration
/path/to/base-url-issue/fake-repo/v1.0/sometext-v1.0-someclassifier.txt

> Task :module-1:resolveConfiguration
/path/to/base-url-issue/fake-repo/v1.0/sometext-v1.0-someclassifier.txt

Deprecated Gradle features were used in this build, making it incompatible with Gradle 8.0.

You can use '--warning-mode all' to show the individual deprecation warnings and determine if they come from your own scripts or plugins.

See https://docs.gradle.org/7.4.2/userguide/command_line_interface.html#sec:command_line_warnings

Execution optimizations have been disabled for 1 invalid unit(s) of work during this build to ensure correctness.
Please consult deprecation warnings for more details.

BUILD SUCCESSFUL in 29s
2 actionable tasks: 2 executed

I went ahead and filed a bug report: Intermittent: "You must specify a base url or at least one artifact pattern for an Ivy repository" even when a base url is specified · Issue #23494 · gradle/gradle · GitHub

For some reason I was not able to upload the files I mentioned to the discussion forums, but they’re attached to the bug report.

Hm, interesting.
I really thought the second thread should not see the changed value for schemes as the field is not transient.
And yes, your analysis is great, definitely enough to file the report imho.

Btw. you could have used settingsDir in the settings script to not require manually setting the path and to make the task fail instead, remove the inputs.files, then the resolution happens only at execution time and then let the task fail there.