technomancy / leiningen

Moved to Codeberg; this is a convenience mirror
https://codeberg.org/leiningen/leiningen
Other
7.29k stars 1.61k forks source link

Add more versatile dependency/plugin resolution options (issue with plugin resolution order) #2807

Closed endvvell closed 1 year ago

endvvell commented 1 year ago

I've been trying to pull a Leiningen plugin from a private GitLab package registry which only allows authentication through either HTTP headers or query parameters. There doesn't appear to exists a "native" way of passing either of those in the arguments used in the :repositories vector, so I was at first glad to find this plugin which specifies a custom wagon factory class to use with repositories the urls of which are specified with "gitlab://" protocol. But, as I soon found out (I believe it's in this "load-plugins" functions of leiningen-core where everything seems to go well up until the "classpath/resolve-managed-dependencies" function), the custom wagon factories are only registered once the addresses of all the plugins are successfully resolved, which allows for the dependencies under the :dependencies key to be eventually resolved but not those under :plugins key, so when I have this specification in my project.clj file:

:plugins [[net.clojars.hissyfit/lein-gitlab-cart "1.0.0"]
              [my-private-plugin "0.1.0"]]
:repositories [["whatever-name" {:url "gitlab://somedomain.com/api/v4/projects/123/packages/maven"
                                                   :username "Private-Token"
                                                   :password "1236153716325"}]]

When I run lein deps I eventually receive the following error:

Could not transfer artifact my-private-plugin:my-private-plugin:jar:0.1.0 from/to whatever-name [...] cannot access gitlab://somedomain.com/api/v4/projects/123/packages/maven with type default using the available connector factories: BasicRepositoryConnectorFactory Meaning that the custom wagon factory class wasn't registered for the "whatever-name" repository specified in project.clj.

The first solution I could think of, and probably most naive one, would be to introduce some sort of mechanism to check whether there exists a registered wagon factory for the urls which, I assume, are already merged together into one map from :plugin-repositories and the :repositories map. This solution would definitely require re-arranging the order of operations in the process of dependency resolution. Would probably mean that we need to resolve plugins(/dependencies in general?) in the order they are specified one by one and checking each time if the resolved plugin/dependency introduces any new wagon factories, so that wagon factories are registered before the urls of the subsequent plugins are checked for their validity, which, considering how fundamental dependency resolution is, I assume would not be easy to implement.

The next solution I could think of was to add an extra optional keyword/s to allow for specification of an authentication method to use for a specific repository (this way we can avoid requiring a custom wagon factory):

:repositories [["whatever-name" {:creds :headers
                                                   :url "https://somedomain.com/api/v4/projects/123/packages/maven"
                                                   :headers {"USERNAME" "SomeName"
                                                                   "PASSWORD" "123abc"}]]

-- or --

:repositories [["whatever-name" {:creds :query-params
                                                   :url "https://somedomain.com/api/v4/projects/123/packages/maven?token-key=some-token-value"}]]

The issue with this solution is that, I assume, the authentication process is handled entirely by pomegranate and not being too familiar with it I'm only guessing that this would become more of a pomegranate issue, leaving us only an option to adapt to its API, which, I assume, Leiningen already did, meaning, in turn, that the first solution would be most feasible one.

I would assume this same issue with the plugin deps resolution order is present with other protocols such as S3, DAV, etc.. as well, as all of them are processed with the same logic. And so, this seems to me like a fundamental feature/issue that requires implementing/solving because there currently doesn't appear to be any other way of resolving private plugin dependencies in the project other than those limited few mentioned in authentication docs.

technomancy commented 1 year ago

This solution would definitely require re-arranging the order of operations in the process of dependency resolution.

The plugin loading cycle is already very complicated and easy to mess up. Changes to this code nearly always result in unforeseen bugs worse than the problem they were attempting to solve. Adding more complexity here just to support the ability to load wagon type plugins from private repositories isn't a good trade-off.

The issue you described sounds like a shortcoming in pomegranate or its dependencies; we should get it fixed there and then pull in the fix to Leiningen that way. Or alternately, publish a plugin which is in a public repository rather than a private one. It's unclear why such a plugin would need to be kept in a private repository to begin with.