25

If Java is a general purpose language, and building a program is something that can be described using the Java language, why isn't this the best way to write build files and instead we use tools like Ant, Maven, and Gradle? Wouldn't that be more straightforward, and also remove the need to learn yet another programming language? (BTW - this question can also be applied to other languages, like C#)

vainolo
  • 1,321
  • 3
  • 13
  • 22
  • 7
    You might have a bit more interesting of a question as to "why aren't general purpose languages used for build languages" - I mean, C isn't a build language either. I'd also suggest looking at the ceremony around [compiling a single .java file in Java](http://stackoverflow.com/questions/10889186/compiling-external-java-files-from-within-java) –  Mar 25 '14 at 20:42
  • 8
    This could probably be generalized as "Why bother with DSLs?" – FrustratedWithFormsDesigner Mar 25 '14 at 20:48
  • 2
    A better question might be; why are IDEs/compilers/tools so bad that build tools are needed in the first place. – Brendan Mar 26 '14 at 01:43
  • 6
    @Brendan that's a non-question as build tools and IDEs serve different purposes. – jwenting Mar 26 '14 at 07:33
  • 1
    Because the "general purpose" languages should never be used instead of the well-defined, simple domain-specific languages. And, if a need to learn "yet another programming language" concerns you, you should not really be programming, try some other trade. – SK-logic Mar 26 '14 at 12:38
  • @SK-logic I don't think the average programmer is able to learn a new language every year. Most "average" programmers I know never really learned fully the language they are currently using. While it's sad, that's the truth. – vainolo Mar 26 '14 at 16:48
  • There is a huge difference between a so called "general purpose language", which could be something of a scale of 1000+ pages spec, and a domain specific language easily fitting 1-2 pages. – SK-logic Mar 26 '14 at 17:16
  • 1
    @SK-logic to use maven you need to learn a LOT more than 1-2 pages of spec, specially to do thing that are not mainstream. Same for ant and make. But this is a more complex discussion. – vainolo Mar 26 '14 at 18:02

6 Answers6

21

Java is an imperative language, Ant, Maven, etc. are declarative languages:

We could define the difference as follows:

  • Imperative programming: telling the "machine" how to do something, and as a result what you want to happen will happen.
  • Declarative programming: telling the "machine" what you would like to happen, and let the computer figure out how to do it.1

Build languages tell the builder what should be done, from where it should be taken, etc. The engine which runs the build (which is written in an imperative language, like @ElliottFrisch has noted), reads these instructions, and fulfills them.

Declarative languages may seem more appropriate in build scenarios, since build tasks are generally the same all over, and it is considered more maintainable and readable in that form than in full-fledged code form.

Uri Agassi
  • 1,751
  • 11
  • 18
  • 3
    There's at least one build system that uses an imperative language though. Scons uses Python. – user16764 Mar 25 '14 at 21:50
  • Making it imperative is no harder than writing a class which can contain a list of dependencies, and a method to resolve those dependencies, though. I suspect there's another reason --- such as the JVM startup time, perhaps. –  Mar 26 '14 at 00:14
  • 2
    @Lee: Almost all of the build tools used in the Java world (Ant, Maven, Gradle, SBT, …) also typically run on the JVM (with the exceptions of Rake, Buildr or Scons, which *can*, but don't *have to* run on the JVM). – Jörg W Mittag Mar 26 '14 at 02:50
  • Most people don't really like Ant/Maven/Make, so even if they seem more appropriate for the job, they are not the right solution. Oh, and Gradle is the newest build language, and it is not declarative but imperative – vainolo Mar 26 '14 at 08:17
  • 6
    @vainolo "Most people don't really like Ant/Maven/Make" really? That's a bold statement! – Ben Thurley Mar 26 '14 at 11:57
  • 1
    @BenThurley I people liked make, why was ant created? and if ant was liked, why was maven? And now why are people using Gradle? But I agree that it is a bold statement, and it is only backed by "anecdotal" evidence of some tens of java developers – vainolo Mar 26 '14 at 16:43
  • @JörgWMittag Yes, the mistake I made was taking OP's base assumptions (build tools aren't written in Java) for granted. I should've thought about Maven etc. I tend to think of make, scons, etc. when you say build tool. –  Mar 27 '14 at 08:21
21

Specific Tool for a Specific Purpose

  • Verbosity

    General purpose languages are often too verbose. If I had to manage a build in Java, I'd be very depressed very quickly by the size of the beast. However, it could easily be manageable using a DSL written in Java. And to some extent that's how you can see Gradle (for Groovy) and Buildr (for Ruby).

  • Difficulty

    General purpose languages are hard. Well I don't think programming is hard and can't be picked up by anyone, but the point is: your build engineer isn't necessarily your programmer!

  • Purpose

    That's more or less at the intersection of difficulty and verbosity. A language is designed for a purpose, and here we're talking about something very specific. So why would you need or want to use a general purpose language? For builds, you need:

    • branches and conditionals to handle separate configurations, environments, etc...
    • a set of instructions to extract data from your environment,
    • a set of instructions to produce deliverables.

    You don't really need much more.

Sure it's annoying when your build system seems like it's not flexible enough for that one special use case you're after, but it's probably far better than the alternative for most people.

That's probably why there's a polarity between people who prefer declarative build systems over most programmable onces: I guess a developer might have a natural tendency to look for ways to break out of the box.

Wait, Do We Really Need a Tool?

Another related question would be: do we really need a build tool? Isn't the fact that they exist in all languages the sign that they fill a gap that shouldn't even be there in the first place?

Some languages don't necessarily require a build tool. For instance, most scripting languages don't need one and resolve at load time. Or take Go, whose compiler will handle everything for you, which is a nice counterpoint: what if a compiler like gcc suddenly didn't need a bunch of flags, a linker, and a makefile to die everything together? Or if javac didn't need a build.xml or pom.xml to tell him what to do? Shouldn't dependency management directly be part of the language's own tooling, as the dependencies are a part of the final program?

It surely seems like a much simpler approach for the user (the builder). Though one could argue they're just doing in under the hood and taking away your choice and opportunities to influence that build process (but then you'd hope such a tool allows compiler extensions and similar things). Plus we used to see the tools and the language as two separate things, so he might seem unpure to suddenly have them so tightly coupled.

I don't think the language you use to build your programs is sthe issue. It's the language you use to program and its core platform and tooling that should matter, and we're still making headway on that.


Personally, I've used make/gmake/autotools/pmk and been happy with them for C, and I started with Java when all we had was make, then ant, and now I'm generally preferring Maven over all these alternatives. Though I can see value in gradle, buildr and others, but I like the prevalence of maven so far, until a more significant shift occurs. Plus I like that it's rigid, but still leaves you the ability to work around that if necessary. That it's not easy is a good thing.

It's a build tool. Just learn to embrace it and don't fight it. It's a losing battle. Or at least a very very long one.

haylem
  • 28,856
  • 10
  • 103
  • 119
  • Really like your pragmatism. My question is out of curiosity. I use maven (having used make and ant in the past), and it does "black magic" which is sometimes good and sometimes bad. But it is still magic. – vainolo Mar 26 '14 at 16:51
  • 1
    "Or if javac didn't need a build.xml or pom.xml to tell him what to do?" - heh. it doesn't and never did. – user253751 Jul 02 '15 at 07:53
  • @immibis: that's debatable. I wouldn't really enjoy building my projects at the office with only javac :) It certainly would require a fair bit of glue with either a Makefile or a shell script or at the very least shell aliases to put things together. Or a gigantic program with everything in one folder and all deps in the repo. Not really something I'd like! :) But that's another debate entirely, unrelated to the OP's question. – haylem Jul 09 '15 at 09:35
  • Integrating the build system with the language is also problematic for polyglot projects. If I use both language A and B and want to have a single build process for them, which language's build system do I use? – Sebastian Redl May 19 '17 at 11:02
  • @SebastianRedl So how does the problem get easier by introducing a _third_ language? Just pick the language that works better for you. (And of course, if you need a third language, Java can also be it.) – Ville Oikarinen May 27 '17 at 13:33
  • @VilleOikarinen I'm talking about cases where the build system is integrated into the language, not where a general purpose language is used to configure the build system. Sometimes the compiler itself tracks dependencies and automatically rebuilds what's necessary, but these systems suddenly get very awkward when there's a different language involved. – Sebastian Redl May 27 '17 at 15:29
  • @SebastianRedl OK. But I think compilation is just a small part of building, and not even very challenging (of course we don't consider the actual compilation algorithm here, just the build system parts). Many nontrivial projects need a lot more. Code generation, static code analysis, test coverage, different distribution packages, to name a few. – Ville Oikarinen May 29 '17 at 04:46
  • @haylem I would think the asnwer to "why would you?" would be: because there's no shortage of build systems that were built because the existing build system was not flexible enough. Because one does not have to learn a different language. Because the language is more likely to already have great IDE support (compared to build system's language). – Vitaly May 02 '22 at 15:01
4

If you look at the features of a typical build system you find:

  1. Lots of data: names, switches, settings, configuration items, strings, etc
  2. Lots of interaction with the environment: commands, environment variables
  3. A relatively straightforward "build engine" handling dependencies, threading, logging etc.

If you set out to write a series of build files using some language (Java/C#/Python/etc), by about the third or fourth iteration you would settle on (a) keeping most of the data and external commands as data in something like XML (b) writing the "build engine" in your favorite language.

You would also find it useful to treat some of the data in your XML as an interpreted language, to trigger various features in the build engine. You might also interpret some macros or perform string substitutions, in the data.

In other words, you would finish up with Make, or Ant, or Rake, or MsBuild. An imperative language for the things it does well, and data structures to describe what you want to do, now usually in XML.

david.pfx
  • 8,105
  • 2
  • 21
  • 44
2

A number of factors count against using Java/C++/C# in these cases.

Firstly, you'd have to compile your build script before you could run it to build your app. How would you specify any packages, flags, compiler versions, tool paths needed to build your build script? Certainly, you could come up with a way around it, but its much more straightforward to have either have a language that doesn't need that build step (e.g. python) or a language that your build tool natively understands.

Secondly, build files are data heavy whereas Java/C++/C# are much more geared towards writing code and algorithms. Java and friends don't have a very succinct representation of all the data you'd want to store.

Thirdly, Java and friends need a lot of boilerplate to be valid. The build file would have to be inside a method inside a class with all of its imports. When using a scripting language or custom language you can avoid all that boilerplate and just have the build details themselves.

Winston Ewert
  • 24,732
  • 12
  • 72
  • 103
0

Indeed! Why not use a powerful and expressive language for a problem that is more complex than people often (initially) think? Especially when the people facing the problem are already competent with such a language. (Building is programmers' own problem and best solved by programmers.)

I also asked myself this question years ago and decided that Java is a good language for defining builds, especially for Java projects. And, as a result, I started doing something about it.

DISCLAIMER: In this answer I am promoting iwant, a build system I am developing. But since this is an opinionated discussion anyway, I'm sure it's ok.

I won't elaborate on the benefits of Java (power and expressiveness) or iwant specifically. If you are interested, you can read more on the iwant page.

Instead I'll consider why Java (and other GPLs) is so readily dismissed as unsuitable for building. Many answers and comments here are good examples of such thinking. Let's consider some of the typical arguments:

"Java is imperative, but builds are best defined in a declarative way", they might say.

True. But when using a language as a metalanguage for an internal DSL, what really counts is its syntax. Even an imperative language like Java can be tricked to be declarative. If it looks and feels declarative, it is (for practical purposes) declarative. For example:

JacocoTargetsOfJavaModules.with()
    .jacocoWithDeps(jacoco(), modules.asmAll.mainArtifact())
    .antJars(TestedIwantDependencies.antJar(),
            TestedIwantDependencies.antLauncherJar())
    .modules(interestingModules).end().jacocoReport(name)

This is a real example from the demo project of iwant.

In fact, compare this to some supposedly declarative build systems that expose their users to such imperative verbs as "test" or "compile". The above declaration contains only nouns, no verbs. Compiling and testing are tasks implicitly handled by iwant in order to grant the user the nouns he/she wants. It's not the language. It's how you use it.

"Java is verbose"

Yes, a lot of Java code out there is verbose. But again, it's not the language, it's how you use it. If an implementation is verbose, just encapsulate it behind a nice abstraction. Many GPLs provide adequate mechanisms for this.

Just imagine the above Java snippet written in XML. Replace parentheses with angle brackets and move them around. And then duplicate every keyword as a closing tag! Java as a syntax is not verbose.

(I know, comparing to XML is like taking candy from a baby, but so many builds just happen to be defined in XML.)

"You'd have to compile your build script"

This is a valid point. Nevertheless, it's just a minor technical problem to be solved. I could have solved it by using beanshell or some other interpreter. Instead, I solved it by treating it as just another build problem and bootstrapping iwant with a simple shell or ant script that compiles and runs a simple Java bootstrapper.

"Java has boilerplate"

True. You have to import classes, you have to mention "public", "class" and so on. And here simple external DSLs score an initial easy win.

And if your project is so trivial that this boilerplate is significant, congratulations. Your problem isn't a difficult one and it really doesn't matter how you solve it.

But many projects need a lot more than compilation, coverage report and packaging. If the boilerplate of Java is acceptable for customers' problems, why not build problems? Why make shoes only for others' children?

Ville Oikarinen
  • 281
  • 1
  • 7
0

One thing I don't think the other answers have addressed is that the limitations and transparency of these build languages is a huge part of what makes them useful. Let's take Maven, for example. Yes, it executes builds but it also defines dependencies. This allows for a Maven build to pull down those dependencies and look at their dependencies and so on and so forth.

Consider if this was done with Java directly. The build tool would see that there is a dependency. It would then need to pull down so other Java application and execute it to determine what it's dependencies are. But for Maven, it simply looks at the dependency declarations. The maven build file is transparent. A Turing complete language is inherently non-transparent and therefore are inferior for some purposes such as this one.

JimmyJames
  • 24,682
  • 2
  • 50
  • 92
  • How does Gradle fit in this? It defines dependencies in a declarative style, using a general purpose language (Groovy). In a Groovy, JavaScript or Lisp based tool it's natural to use the language's compiler or interpreter to parse declarations, whether you just want to read them or 'execute' them (apply some function). That duality of code and data is not part of the generally accepted Java idiom, though not impossible. Turing completeness does not prevent good data representation. It does open the possibility your data will do something you didn't expect it to do. – joshp May 19 '17 at 16:39
  • @joshp I'm not familiar with Gradle. It's described as a DSL and things look rather declarative. Being based on Groovy doesn't necessarily mean it's equivalent to Groovy in it's power. You'd probably be in a better position to say if Gradle is in fact a Turing-complete language. The dependency examples I looked at seem fairly simple. Can you use arbitrary Groovy expressions in things like dependency declarations? – JimmyJames May 19 '17 at 17:14
  • Personally I don't even like transitive dependencies. Often they just cause surprises in the classpath (multiple incompatible versions of libraries). That's why maven has the exclude element, but the hassle is not just worth it. Explicit dependencies make life simpler. Still, transitive dependencies _can_ be implemented with Java. I have done something like that with [iwant](http://iwant.sourceforge.net) by reusing build definitions of other projects. – Ville Oikarinen May 24 '17 at 11:42
  • @VilleOikarinen I happen to mostly agree with you. I think it also causes a ton of bloat since there's no perceived cost to pulling in more dependencies even if you don't use them. However, in the context of this answer, I'm not commenting on the value or wisdom of that feature but on how the use of a DSL is useful in achieving it. – JimmyJames May 24 '17 at 14:36
  • @JimmyJames Giving merit to interpreted languages for making an unwanted feature easier? :) Anyway, the extra compilation phase caused by using a compiled language as metalanguage is just a minor and solved problem, like I explain in my answer. Reusing module definitions from another project is possible and I have done it. Not for transitive dependencies but to reuse modules from their sources, without having to publish them as binaries to a repository. – Ville Oikarinen May 25 '17 at 05:52
  • @VilleOikarinen Well if someone digs a hole in the wrong place, we don't say shovels are a bad idea... Is there something my answer that suggest that I think the problem is having a 'compilation' phase? – JimmyJames May 25 '17 at 13:39
  • @JimmyJames True, you didn't directly mention compilation. I just assumed that is your point because AFAIK that's the only relevant difference you get by defining modules in Java instead of (pom) xml. You wrote: "Maven, it simply looks at the dependency declarations" and that sounds very much like interpretation (vs compilation) to me. If not compilation, what then did you mean makes the Java counterpart of pom.xml difficult/impossible? – Ville Oikarinen May 27 '17 at 13:16
  • @VilleOikarinen It's not that it's difficult or impossible. The problem is that a Turing complete language is inherently [undecidable](https://en.wikipedia.org/wiki/Decision_problem). Specifically, you can't, in general, know what a program will do without going though all the steps of the algorithm and therefore the dependencies are not transparent. – JimmyJames May 30 '17 at 17:01
  • @JimmyJames And those undecidable GPLs are used for interpreting/compiling the "decidable" DSLs. Practical applications are everywhere regardless of Turing's (or Gödel's, on the math side) disclaimers. I think it's safe to just forget such deep theoretical stuff here. To be more practical, let me guess what you mean: if you download some random GPL code, you can't know what it does. True, but in the spirit of _configuration management_, that's not a good idea anyway: you test all your build ingredients and then you make sure you use them and only them. – Ville Oikarinen May 31 '17 at 05:05
  • @VilleOikarinen It's not theory that I am concerned with. A Turing-complete language is different, in practical usage, than one that is decideable. There are many implications to this. One basic issue is that if there is no way to know what all the dependencies are without recursively executing each algorithm. Another implication is security. Using a Turing-complete language for this is like using a sledgehammer to kill an ant. The extra power of the general language simply adds capabilities that don't make sense for the problem at hand. – JimmyJames May 31 '17 at 13:29
  • @JimmyJames The idea _is_ to execute the code so it doesn't matter if you cannot reason about it without executing. And my previous comment already covered security: you must anyway be in control over your build ingredients so you are not executing random code. – Ville Oikarinen May 31 '17 at 16:36
  • @VilleOikarinen Exactly how do you know the build ingredients are secure? Are you inspecting the code that determines the dependencies or are you assuming trust? – JimmyJames May 31 '17 at 17:02
  • @VilleOikarinen "The idea is to execute the code so it doesn't matter if you cannot reason about it without executing" But I want to know the dependencies of a 3rd party component prior to installing it. How can I do that if I must install it and execute in order to determine the dependencies? – JimmyJames May 31 '17 at 17:15
  • @JimmyJames This is nothing new. Do you inspect the code of the transitive dependencies you are going to execute _after_ the build phase, as part of the application you are building? – Ville Oikarinen May 31 '17 at 17:50
  • @VilleOikarinen I have done that. Yes. Going back to where we started, I'm not a huge fan of automatically pulling down a lot of dependencies especially when a lot of them are not needed. And I know they often aren't because I've done this. In any event, if you want to build your code with a generally programming language, have at it. Nothing is stopping you. But it's unlikely to catch on for 3rd-party dependencies. – JimmyJames May 31 '17 at 19:13
  • @JimmyJames And when inspecting production code of any module you can also inspect its build code. Anyway, since I agree with you that transitive dependencies have their problems, I'm not overly concerned over implementing them. – Ville Oikarinen Jun 01 '17 at 05:59
  • @VilleOikarinen That's great and all but the question is why aren't general programming languages used, not whether transitive dependencies are a good idea or not. On a side note, you realize that your argument could be extended to basically anything. For example, we could dispense with formats for movies or documents or webpages and simply make everything a program. It think it's obvious why that's not likely to happen. This case is more nuanced but the underlying reasoning comes down to the same fundamental issues. – JimmyJames Jun 01 '17 at 14:38
  • @JimmyJames Yes, everything _could_ be a program, but for example movie files need compression. Also their problem domain is well defined and limited. Build scripts, OTOH, often require more and more features, so even if _ideally_ their problem domain is limited, in practice it's not. Just see how real-life pom.xml files are being abused beyond basic needs (and they _do_ call Java, just through a ball of glue). As for the OP's question: see my answer why I think many common claims are not valid. – Ville Oikarinen Jun 02 '17 at 06:11
  • 1
    @VilleOikarinen I think we've kind of reached the end of this but I just want to clarify that I am not talking about pom/maven. This is an example of a build DSL but I don't think much of it. I use it begrudgingly and I think it's clunky. But what has pom so a dominant is the dependency declarations. I used Buildr for a while and it reads pom for dependencies but it doesn't use them for the build specifications. There are a number of tools that are not Java based that understand this pom but AFAIK, all they care about is the dependencies. – JimmyJames Jun 02 '17 at 13:52