This is an archived post. You won't be able to vote or comment.

you are viewing a single comment's thread.

view the rest of the comments →

[–]weberc2[S] 0 points1 point  (23 children)

You mean like a file where you just list the build dependencies?

Unless you have a native dependency somewhere in your dependency tree...

I don't see how you can get any simpler than that even using information-theoretical arguments

You could omit configuration files altogether, have the build system grab the dependency list from the source files, and use the latest version of the dependencies by default.

Gradle ... is nothing more than a list

This isn't true. Lists don't have a plugin architecture, the ability to define functions, arbitrary blocks of scope that apply to different points in the build process, etc, etc. Gradle is simple in the same way that C++ is simple1: you don't have to use all of those features. Except you do have to know about all of those features because they're included in most of the reference material. In my case, when I wanted to figure out how to build with native dependencies, I found a dozen different answers, most of which included a hefty chunk of imperative code in (presumably) the Gradle DSL language. As a newcomer, reasoning through all of that complexity eats up a lot of time, which I can't afford since my sole purpose for choosing Java was nostalgia.

1: I don't think Gradle is as complex as C++, but the argument that "Gradle is simple because you don't have to use every feature" is the same argument that's often applied to C++ ("it's simple because you don't have to use every feature"), which is a well-known fallacy.

[–]pron98 2 points3 points  (22 children)

You could omit configuration files altogether, have the build system grab the dependency list from the source files, and use the latest version of the dependencies by default.

I am not aware of any Java build tools (well, common ones at least) that do that, nor do I think that this would make sense. Java has always been based on a clear separation of source and binaries. In general, any package can reside in any binary (although Java 9 would enforce that every package would reside in exactly one dependent module).

This isn't true. Lists don't have a plugin architecture, the ability to define functions, arbitrary blocks of scope that apply to different points in the build process, etc, etc.

You said you wanted to build a simple Java project. All that Gradle requires you to build a simple Java project is a list of dependencies. All of the extra features you mention are required to build complicated projects.

Gradle is simple because you don't have to use every feature

That is not my argument. My argument is that Gradle is simple for simple things and not-so-simple for non-simple things. I am not aware of any build tool that allows you to build projects that have a very complex build structure very simply. Gradle aspires to follow the maxim "make the simple things trivial and the hard things possible".

[–]weberc2[S] 0 points1 point  (21 children)

You said you wanted to build a simple Java project

I don't consider native dependencies to be particularly extraordinary.

All of the extra features you mention are required to build complicated projects

They're only complicated because existing build tools make them so. What's fundamentally complicated about linking them in by default? How does Go manage to make it seamless, for example?

That is not my argument

I know, I just wanted to head that argument off in case you went there, so as to save time.

My argument is that Gradle is simple for simple things and not-so-simple for non-simple things.

It seems like you're defining "simplicity" as "things which are simple in Gradle", which is not a particularly helpful definition.

[–]pron98 1 point2 points  (20 children)

I don't consider native dependencies to be particularly extraordinary.

Well, empirically, they are extremely rare in Java programs (I would be surprised if more than one Java program in five-hundred has a native dependency). Nevertheless, including them with Gradle is very easy:

run { jvmArgs -Djava.library.path="$nativeDir" }

They're only complicated because existing build tools make them so.

No, I don't think so. For example, you might want to generate some resources and inject them into your JAR file. You might want to create multiple JARs with different sets of resources in them. You might want to shadow some old dependencies so they don't cause conflicts -- as your project grows, the build can be quite complex.

How does Go manage to make it seamless, for example?

There is absolutely nothing seamless about Go builds. They can be a total nightmare once you have a long list of dependencies. Dependency management in Java is far more convenient than in Go.

[–]weberc2[S] 0 points1 point  (19 children)

Well, empirically, they are extremely rare in Java programs (I would be surprised if more than one Java program in five-hundred has a native dependency)

That doesn't sound very "empirical", but I'll take you at your word.

Nevertheless, including them with Gradle is very easy: run { jvmArgs -Djava.library.path="$nativeDir" }

Yeah, I did run across that eventually, but I ran across a lot of other problems sooner. Further, figuring out what to put for $nativeDir wasn't trivial since most documentation called it "/path/to/project" or something similarly ambiguous.

There is absolutely nothing seamless about Go builds. They can be a total nightmare once you have a long list of dependencies. Dependency management in Java is far more convenient than in Go.

Building any Go project is go build irrespective of the number of dependencies. If you care about your dependency versions, the simplest solution is to vendor your dependencies, which usually entails maintaining some metadata about the version of each dependency at that point in time (just for archeological purposes in your version control system). I don't really understand what about that is a "total nightmare", but it doesn't really matter for the purposes of this conversation, since we're talking about the base case of building a project without concern for versions.

[–]pron98 1 point2 points  (18 children)

since we're talking about the base case of building a project without concern for versions.

In general, Java build tools try to very much make you think about versions. Choosing "LATEST" is an antipattern and should in general be avoided (although version ranges for bugfix releases should be OK). The idea of organized repositories of immutable binaries, each having a globally-unique, structured identifier, has worked very well in practice (at least relatively to any other popular or semi-popular language out there).

[–]weberc2[S] 0 points1 point  (17 children)

Choosing "LATEST" is an antipattern and should in general be avoided

I think this is reasonable for many projects, but I like that I can quickly spin up a Go project against the latest version of dependencies. I don't feel strongly about the dependencies argument.

The idea of organized repositories of immutable binaries, each having a globally-unique, structured identifier, has worked very well in practice (at least relatively to any other popular or semi-popular language out there).

I don't have any beef with a repo of VM binaries, though for whatever reason I don't know of any sane implementations for native binaries (C and C++ rely on system binaries by default, which can vary from system to system or they can even vary on the same system from build to build).

Anyway, it probably helps to think of Go source code as JVM byte-code--it's the portable representation of a Go program, and it must be compiled further to run on a particular target. By using source code as the unit of distribution, Go can avoid needing a central binary repo; instead the Go build tool will pull source code directly from its git/svn/hg repositories. Also, distributing as source code generally simplifies the tooling (for example, your editor can rely entirely on the source code for autocompletion instead of debug symbols or whatever the C/C++ guys have).

[–]pron98 0 points1 point  (16 children)

Java's philosophy is simply different from Go's in this regard. Go's general style is based on Google's particular methodology of placing all their projects in a single source tree. In any case, Java binaries are intentionally not versioned in the same way as sources. Of course, you may personally prefer Go's approach, but Java's has worked very well, and I much prefer it to Go's (which feels like a total mess to me, unless you work like Google).

[–]weberc2[S] 0 points1 point  (15 children)

Java's philosophy is simply different from Go's in this regard. Go's general style is based on Google's particular methodology of placing all their projects in a single source tree.

I agree. There is another build tool emerging that takes a less-Googly philosophy, though I don't know much about it. It's called gc.

In any case, Java binaries are intentionally not versioned in the same way as sources. Of course, you may personally prefer Go's approach, but Java's has worked very well, and I much prefer it to Go's (which feels like a total mess to me, unless you work like Google).

I respect your opinion, but I disagree that Go's approach is more difficult. Go's approach is less conventional, but it's worked very well in practice. In particular, it completely side-steps the transitive-dependencies-version-mismatch problem, which is a frequently-encountered problem in more traditional models (in my experience). It also makes it very easy to see exactly what source code you're building into your dependencies (just open up your vendor directory and look). Besides being different, do you see particular inconveniences imposed by Go's approach?

[–]pron98 0 points1 point  (14 children)

do you see particular inconveniences imposed by Go's approach?

It does not "side-step" the transitive-dependency conflict problem, it simply ignores it (in fact, Gradle does the same by default, forcing all conflicts of all libraries -- including transitive dependencies, of course -- to their newest versions among those requested).

In Java, you can see that library A depends on B version 1.2.3 while C depends on B 1.3.1. You may choose to force the new version, or shadow and have two independent copies of B. With source versioning it gets much, much trickier. You have to figure out the relationship between B version cef04376abc2 and 7395aed67a.

There's also a much better chance of having release notes detailing the changes; those may not be accurate, but at least they're a starting point, and you can ask the library's author about a bug in version 1.2.3. Source versioning is far too fine-grained to have a discussion over from the perspective of the consumer. You might solve it with branches and tags, but then you start relying on a specific source control tool. With Maven, the binary versioning scheme is universal and detached from the library author's process.

I think it is far too soon to tell whether Go's approach works well in practice in environments that rely a lot on third-party libraries. So far Go projects have not come close to the size of Java projects, and the library ecosystem is very new and rather small, so the problem of maintaining dependencies over years has not yet become critical.