Fork me on GitHub
#clojars
<
2023-04-01
>
emccue15:04:51

Are there any maven experts here? I've been writing my own pom parser + dependency resolver and the full flow of how POMs are resolved is still super wacky to me and idk if I get it (details in thread)

emccue15:04:41

So first, I know how to parse the content literally represented in the POM

emccue15:04:54

from there, I think the next step is "find the parents"

emccue15:04:42

Then there is a step where the inheritance tree is collapsed and properties are subbed

emccue15:04:58

and at some point <scope> import is handled in some way?

emccue15:04:30

which is recursive, since i think I need to fully parse that pom too!?!

emccue15:04:50

and then i need to collapse dependencies and dependencyManagement

emccue15:04:57

/**
 *   POM (xml file)
 *        |
 *        |   Parse XML
 *        V
 *     PomInfo
 *        |
 *        |   Fetch all parent poms
 *        V
 * ChildHavingPomInfo
 *        |
 *        |   Squash parent poms and replace properties
 *        V
 * EffectivePomInfo
 *        |
 *        |   Fetch BOMs and other deps with import scope
 *        v
 *      ?????
 *        |
 *        |   Collapse dependency and dependency-management
 *        v
 *      PomManifest  [ contains just list of deps ]
 *        |
 *        |   Normalize snapshots and version ranges
 *        v
 *      PomManifest  [ type unchanged ]
 */

emccue15:04:41

i'm just kinda lost

emccue15:04:22

and i've been reading both other code doing this same task and the documentation - its helping but its painful

Alex Miller (Clojure team)15:04:16

There are a couple maven books they wrote a long while ago that might actually cover some of this

Alex Miller (Clojure team)15:04:02

You didn’t mention property resolution above but that also should be in there

emccue15:04:20

yeah i squished property resolution and parent poms into the same step

emccue15:04:31

maybe they should be distinct

emccue15:04:16

this is the "flow" i got rn (huge code chunks)

record PomInfo(
        PomGroupId groupId,
        PomArtifactId artifactId,
        PomVersion version,

        List<PomDependency> dependencies,

        PomParent parent,

        List<PomDependency> dependencyManagement,

        List<PomProperty> properties,

        PomPackaging packaging
) {
}
| v
record ChildHavingPomInfo(
        PomGroupId groupId,
        PomArtifactId artifactId,
        PomVersion version,
        List<PomDependency> dependencies,
        List<PomDependency> dependencyManagement,
        List<PomProperty> properties,
        PomPackaging packaging,
        Optional<ChildHavingPomInfo> child
) {
| v
record EffectivePomInfo(
        PomGroupId groupId,
        PomArtifactId artifactId,
        PomVersion version,
        List<PomDependency> dependencies,
        List<PomDependency> dependencyManagement,
        PomPackaging packaging
) {

    static EffectivePomInfo from(final ChildHavingPomInfo childHavingPomInfo) {
        var properties = new LinkedHashMap<String, String>();

        var top = childHavingPomInfo;
        while (top != null) {
            for (PomProperty property : top.properties()) {
                properties.put(property.key(), property.value());
            }
            top = top.child().orElse(null);
        }

        Function<String, String> resolve =
                str -> resolveProperties(properties, str);

        Function<PomDependency, PomDependency> resolveDep = dependency ->
                new PomDependency(
                        dependency.groupId().map(resolve),
                        dependency.artifactId().map(resolve),
                        dependency.version().map(resolve),
                        dependency.exclusions().stream()
                                .map(exclusion -> new PomExclusion(
                                        exclusion.groupId().map(resolve),
                                        exclusion.artifactId().map(resolve)
                                ))
                                .collect(Collectors.toUnmodifiableSet()),
                        dependency.type().map(resolve),
                        dependency.classifier().map(resolve),
                        dependency.optional().map(resolve),
                        dependency.scope().map(resolve)
                );

        PomGroupId groupId = PomGroupId.Undeclared.INSTANCE;
        PomVersion version = PomVersion.Undeclared.INSTANCE;
        PomPackaging packaging = PomPackaging.Undeclared.INSTANCE;
        var dependencies = new LinkedHashMap<PomDependencyKey, PomDependency>();
        var dependencyManagement = new LinkedHashMap<PomDependencyKey, PomDependency>();

        top = childHavingPomInfo;
        while (top != null) {
            if (top.groupId() instanceof PomGroupId.Declared) {
                groupId = top.groupId().map(resolve);
            }
            if (top.version() instanceof PomVersion.Declared) {
                version = top.version().map(resolve);
            }
            if (top.packaging() instanceof PomPackaging.Declared) {
                packaging = top.packaging().map(resolve);
            }

            top = top.child().orElse(null);
        }

        groupId.ifDeclared(value -> properties.put("project.groupId", value));
        version.ifDeclared(value -> properties.put("project.version", value));


        var artifactId = childHavingPomInfo.artifactId().map(resolve);
        artifactId.ifDeclared(value -> properties.put("project.artifactId", value));

        top = childHavingPomInfo;
        while (top != null) {

            top.dependencies()
                    .forEach(dependency -> {
                        var newDep = resolveDep.apply(dependency);
                        dependencies.put(PomDependencyKey.from(newDep), newDep);
                    });

            top.dependencyManagement()
                    .forEach(dependency -> {
                        var newDep = resolveDep.apply(dependency);
                        dependencyManagement.put(PomDependencyKey.from(newDep), newDep);
                    });

            top = top.child().orElse(null);
        }

        return new EffectivePomInfo(
                groupId,
                artifactId,
                version,
                dependencies.values().stream().toList(),
                dependencyManagement.values().stream().toList(),
                packaging
        );
    }
| v
record PomManifest(
        @Override List<Dependency> dependencies
) implements Manifest {

Alex Miller (Clojure team)15:04:24

I'm curious why you're doing this :)

lread15:04:26

For what it is worth I find the maven APIs overwhelming too. I have learned a thing or two by reading https://github.com/clj-commons/pomegranate and https://github.com/clojure/tools.deps sources.

emccue15:04:33

bright side - i have successfully detached resolution from all this nonsense in the same way tools.deps has

emccue15:04:31

> I'm curious why you're doing this Mental illness, tbh

Alex Miller (Clojure team)15:04:44

every time I read the Maven source I just want to throttle someone. it's such a paragon of 00's Java style, but it's all just data that could be maps and you would need none of the "abstractions" and "patterns"

1
emccue15:04:13

but the thought is that java should have an actual build tool built in

Alex Miller (Clojure team)15:04:55

I think maybe we've finally reached a point where new languages actually think about this early

emccue15:04:32

and one part of that would be a resolver

emccue15:04:40

I have no power to make that happen

emccue15:04:52

but I do have the ability to experiment in a public way

Alex Miller (Clojure team)15:04:14

I remember well building Java applications before Maven and repos existed

lread15:04:58

Trying to make sense of the Maven APIs put me in a similar mood to when I tried to make sense of Windows shell escaping rules.

lread15:04:31

And continuous integration YAML config.

emccue15:04:05

yeah there is stuff maven does like "every repo is checked for every library" that feels insane

emccue15:04:27

and I don't feel a strong need to recreate

emccue15:04:52

pretty sure I can just attach a list of repos to check to the coordinate.

Alex Miller (Clojure team)15:04:22

it's not only insane, it has all kinds of security issues

emccue15:04:00

right now this is my conception of a coordiante

public interface Coordinate {

    default Coordinate normalize(Library library, Cache cache) {
        return this;
    }

    enum VersionComparison {

        INCOMPARABLE,
        GREATER_THAN,
        LESS_THAN,
        EQUAL_TO;


        public static VersionComparison fromInt(int comparisonResult) {
            return comparisonResult == 0 ? EQUAL_TO : comparisonResult > 0 ? GREATER_THAN : LESS_THAN;
        }
    }


    CoordinateId id();

    Manifest getManifest(Library library, Cache cache);

    /**
     * Gets the location of the given library on disk, assuming the library was located
     * with this coordinate.
     *
     * <p>
     *     If the library is not downloaded on disk, this method will do so before returning.
     * </p>
     */
    Path getLibraryLocation(Library library, Cache cache);

    default Optional<Path> getLibrarySourcesLocation(Library library, Cache cache) {
        return Optional.empty();
    }

    default Optional<Path> getLibraryDocumentationLocation(Library library, Cache cache) {
        return Optional.empty();
    }
}

emccue15:04:32

Manifest is just a named List<Dependency> and thats what all this pom BS collapses into

emccue15:04:38

public record MavenCoordinate(
        Version version,
        List<MavenRepository> repositories,
        List<Scope> scopes
) implements Coordinate {

emccue15:04:30

(scopes...idk need to read one of those books i guess)

emccue15:04:58

but the big ? for me at this level is whether exclusions are conceptually part of the coordinate

Alex Miller (Clojure team)16:04:22

scopes tell you how to make different kinds of classpaths out of the same set of dependencies (at least that's how I think about it)

Alex Miller (Clojure team)16:04:05

you seem to be missing classifiers too unless that's part of your version. classifiers are a weird little world of their own as they bring in conditional stuff

emccue16:04:33

they are not - i was just thinking of source/javadocs/none

emccue16:04:46

like on maven repository there is this method

emccue16:04:56

abstract InputStream getFile(
            Library library,
            Version version,
            Classifier classifier,
            Extension extension
    ) throws LibraryNotFound;

Alex Miller (Clojure team)16:04:17

they are really important for a small percentage of Java libs

Alex Miller (Clojure team)16:04:23

particularly those with native stuff

Alex Miller (Clojure team)16:04:13

the two big conditionals they cover usually are jdk (which is rarely used anymore and shouldn't even be needed now that we have multi release jars), and native architecture

Alex Miller (Clojure team)16:04:15

classifiers are weird in that all classifiers technically share the same pom and have the same coordinate. that they wedged docs and source into that is a bit of a hack

emccue16:04:49

so i can conceive of how to add that to the MavenCoordinate - like use this classifier for the jar - but how would that affect resolution?

emccue16:04:56

record MavenCoordinateId(Version version)
        implements CoordinateId {
}

emccue16:04:32

rn this is what I am keying things on in my (non-functioning) clone of t.deps algo

Alex Miller (Clojure team)16:04:45

in deps I decided to make it part of the artifact: group/artifact[$classifier]

Alex Miller (Clojure team)16:04:43

vs making it part of the version

emccue16:04:37

ugh so thats not suuuper fun

emccue16:04:55

Library is currently this

emccue16:04:08

public record Library(
        Group group,
        Artifact artifact
) {

emccue16:04:11

which is already a little maven specific, but slapping Classifier on there feels gross...

Alex Miller (Clojure team)16:04:18

classifier is really a variant of an artifact that is specific to jdk/arch/something else (we use "aot" as a classifier in a few of the contrib libs)

emccue16:04:56

I can make Library an interface - its basically just a key in a hashmap

Alex Miller (Clojure team)16:04:58

clojure itself publishes both the "normal" compiled jar, but also a "slim" classifier jar with just source

emccue16:04:25

just questioning how to handle sources + docs in that world

Alex Miller (Clojure team)16:04:41

well like I said, those are imo hacks - they are also classifiers

Alex Miller (Clojure team)16:04:36

really, Maven should have had a way to associate some kind of ancillary information (like Clojure metadata) to an artifact

emccue16:04:34

gradle has that with its module metadata

emccue16:04:38

you see why im thinking about this? I think tools.deps does its job for clojure, but would be neat if a foundational part of modern programming didn't elicit reactions like > I just want to throttle someone. and > a similar mood to when I tried to make sense of Windows shell escaping rules.

Alex Miller (Clojure team)16:04:02

Fight the good fight :)

borkdude20:04:42

@U3JH98J4R are you writing your own version of maven and why? :)

emccue21:04:09

@U04V15CAJ of the resolver, not the build tool

borkdude21:04:05

@U3JH98J4R and why, if I may ask?

borkdude21:04:19

Right, I'm asking since I considered doing something like this once when I had trouble compiling tools.deps.alpha (at the time) to native

borkdude21:04:40

so I wondered what your motivations were

emccue21:04:43

That would be a benefit

emccue21:04:05

I'm not using any SPI or dynamic stuff and the number of deps is 1

emccue21:04:15

So id imagine compiling to native would be easier

borkdude21:04:41

what is the dep? perhaps it could also run with bb :)

emccue21:04:41

No it is the dep

emccue21:04:57

As of now the whole thing is zero dep

👏 2
emccue21:04:27

You are historically a way more productive person, so I would welcome participation

borkdude21:04:36

It would be interesting to have a pure .clj solution (even more so if it had a chance of being adopted by tools.deps). The resolution mechanism isn't changing all the time is it?

Alex Miller (Clojure team)21:04:23

I’d consider it if it got me everything I needed, but really this is just part of the puzzle

emccue21:04:47

I'm doing all my stuff in a style that would be idiomatically translatable enough

emccue22:04:33

Idk if that would be useful or not for that goal but :woman-shrugging:

Alex Miller (Clojure team)22:04:43

There’s so many features we can provide via the maven libs and it’s a lot to take all of that on

Alex Miller (Clojure team)22:04:11

Which is why I’ve given up every time I’ve considered it :)

borkdude22:04:15

yes, it's going to be work

borkdude22:04:08

btw in the end I managed to compile tools.deps to native: https://github.com/babashka/tools-deps-native and this is one of the projects using it: https://github.com/babashka/tools.bbuild nothing more than a "look how far we can go" project at this point, in bb I'm still shelling out to Java via deps.clj to fetch deps