aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJakob Odersky <jakob@odersky.com>2018-12-11 18:58:55 -0800
committerJakob Odersky <jakob@odersky.com>2018-12-11 19:08:35 -0800
commit3fe296294d00af43a148cb6ce83193805fd9ec56 (patch)
treeb053c0543c8c94db0f97d264ab910baffe8254a6
downloadscala-makefile-3fe296294d00af43a148cb6ce83193805fd9ec56.tar.gz
scala-makefile-3fe296294d00af43a148cb6ce83193805fd9ec56.tar.bz2
scala-makefile-3fe296294d00af43a148cb6ce83193805fd9ec56.zip
Initial commit
-rw-r--r--.gitignore1
-rw-r--r--Makefile53
-rw-r--r--Maven.list6
-rw-r--r--README.md98
-rwxr-xr-xscripts/combinejars26
-rw-r--r--src/main.scala6
6 files changed, 190 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..9f97022
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1 @@
+target/ \ No newline at end of file
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..8a38ee4
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,53 @@
+sources=$(wildcard src/*.scala)
+main=Main
+
+all: target/library.jar
+
+# contains jars of all dependencies (including transitive ones)
+target/dependencies.list: Maven.list
+ mkdir -p target
+ coursier fetch $(shell sed '/^#/d' Maven.list) > $@
+
+# concatenate dependencies to be parseable as a classpath argument
+target/classpath.line: target/dependencies.list
+ paste --serial --delimiters ':' $^ > $@
+
+# compile scala source files (note that a stamp file is used, since
+# many classfiles may be created per source file)
+target/classfiles.stamp: $(sources) target/classpath.line
+ mkdir -p target/classfiles
+ scalac \
+ -d target/classfiles \
+ -cp $(shell cat target/classpath.line) \
+ -target:jvm-1.8 \
+ $(sources)
+ touch target/classfiles.stamp
+
+# bundle classfiles into a jar
+target/library.jar: target/classfiles.stamp
+ jar cf $@ -C target/classfiles .
+
+# combine lirary jar with all dependencies to produce a fat jar
+target/application.jar: target/library.jar target/dependencies.list
+ mkdir -p target
+ scripts/combinejars $@ $(shell cat target/dependencies.list) $<
+
+# inject a startup script to the fat jar, producing an executable
+target/application: target/application.jar
+ echo "#!/bin/sh" > target/application
+ echo 'exec java -cp $$0 $(main) "$$@"' >> target/application
+ cat target/application.jar >> target/application
+ chmod +x target/application
+
+# run the application
+run: target/application.jar
+ @java -cp $< $(main)
+
+# format source files
+fmt:
+ scalafmt $(sources)
+
+clean:
+ rm -rf target
+
+.PHONY: clean all
diff --git a/Maven.list b/Maven.list
new file mode 100644
index 0000000..6114b60
--- /dev/null
+++ b/Maven.list
@@ -0,0 +1,6 @@
+# Maven coordinates of dependencies are listed here.
+#
+# lines starting with # are ignored
+#
+io.crashbox:identicon_2.12:0.2.0
+#com.typesafe.akka:akka-actor_2.12:2.5.19
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..c052c12
--- /dev/null
+++ b/README.md
@@ -0,0 +1,98 @@
+# A makefile for Scala Applications
+
+A makefile that demonstrates how building Scala applications can be
+very simple.
+
+It supports *dependency resolution of maven artefacts*, enables
+building *executables* (not only fat jars, actual executable files)
+and all **builds are reproducible** (anyone building the same source
+gets bit-wise identical binaries).
+
+## Dependencies
+
+As this is a plain makefile, it relies on a number of tools that must
+be present in the caller's environment. Those are:
+
+- [scalac](https://www.scala-lang.org/download/) to compile Scala
+- [coursier](https://coursier.github.io/coursier/1.1.0-SNAPSHOT/docs/quick-start-cli.html#installation) for dependency resolution
+- openjdk to run binaries and for packaging jar files
+- zip and unzip for creating standalone fat jars
+- (optional) [scalafmt](https://scalameta.org/scalafmt/docs/installation.html#cli)
+
+Tested on Linux, should work on Darwin and BSDs, does not support
+Windows.
+
+## How-To
+
+Source files are in `src/` and maven dependencies are declared in
+`Maven.list`.
+
+The main targets for a development workflow are typically
+
+- `make` to build a library jar in target/library.jar
+- `make run` to run the application
+
+The application may be deployed as a standalone binary, only requiring
+a Java runtime environemnt, by running `make target/application`.
+
+## Example
+
+This project contains an example application that prints the identicon
+of a given string. Build it by running `make target/application`.
+
+- Generate an identicon: `target/application username`.
+- View an identicon: `inkview <(target/application admin)`.
+
+## Known Issues / FAQ
+
+- **It's slow**. Yes, this Makefile invokes a cold compiler on every
+ source change. Using [bloop](https://scalacenter.github.io/bloop/)
+ could help here.
+
+- **Tools not included**. To run this makefile, all its constituent
+ tools (scalac, scalafmt, coursier, etc) must be configured in the
+ developer's environment. It's not as convenient for newcomers as the
+ out-of-the-box experience that sbt or cbt offer, however adhering to
+ the unix philosophy of "do one thing and do it well" is the whole
+ purpose of this project.
+
+- **Doesn't work with multiple Akka libraries**. The jar combination
+ script is quite dumb: it simply extracts contents of jar files in a
+ directory tree and recombines the final directory into a jar. In the
+ process, any identically named files get overwritten. Since Akka
+ relies on `reference.conf` files within jars, if multiple Akka
+ libraries are used, the latest config in the classpath overwrites
+ all others. A smarter merging script would be required to fix tis
+ issue.
+
+- **The resulting binary is large**. The fat jar which is contained in
+ the application contains *all* dependent jars, which may add up for
+ projects with complex transitive dependencies. Using a tool like
+ proguard could drastically decrease the size of the final binary.
+
+- **The resulting binary requires a Java runtime**. See
+ [scala-native](http://www.scala-native.org/en/v0.3.8/), in the
+ future hopefully we can add support for it in the form of an
+ independent makefile.
+
+- **Should I use this?** Maybe. This project was intended as a
+ proof-of-concept; it was motivated by my feeling that build tools in
+ the Scala world tend to be very complex machines, and that using
+ them to their full extent requires almost as much effort as learning
+ a new language.
+
+ My suggestion is that you should give this Makefile a shot if
+
+ 1. You are new to Scala but familiar with programming and the unix
+ environment, and want a quick but thorough overview of the steps
+ involved in going from a source file to an executable.
+
+ 2. You are building an **application**. This makefile is too
+ bare-bones for libraries (for example it does not support
+ multiple scala version or uploading to Maven Central).
+
+ 3. You agree with the following sentiment, as
+ [cbt](https://github.com/cvogt/cbt/)'s author, Chris Vogt, puts
+ it:
+
+ > Do you own your Build Tool or does your Build Tool own you?
diff --git a/scripts/combinejars b/scripts/combinejars
new file mode 100755
index 0000000..338bd6d
--- /dev/null
+++ b/scripts/combinejars
@@ -0,0 +1,26 @@
+#!/bin/bash
+# Usage: combinejars <destination> <jars...>
+#
+# Combines contents of jars into a single destination jar. The
+# destination jar will have all it entries dates set to the unix
+# epoch, hence making it reproducible for the same input.
+#
+# Contents at same paths are overwritten, the latter overwriting the earlier.
+
+destination="$(realpath "$1")"
+shift
+sources=("$@")
+
+workdir="$(mktemp -d)"
+
+cleanup() {
+ rm -rf "$workdir"
+}
+trap cleanup EXIT
+
+for s in "${sources[@]}"; do
+ absolute=$(realpath "$s")
+ (cd "$workdir" || exit 1; unzip -uo "$absolute" > /dev/null)
+done
+find "$workdir" -exec touch -t 197001010000 {} \;
+jar -cMf "$destination" -C "$workdir" .
diff --git a/src/main.scala b/src/main.scala
new file mode 100644
index 0000000..0e28710
--- /dev/null
+++ b/src/main.scala
@@ -0,0 +1,6 @@
+object Main extends App {
+ val username = args.headOption.getOrElse("anonymous")
+ val svg = identicon.svg(username)
+ System.err.println(s"Identicon for user '$username'")
+ println(svg)
+}