Skip to content

Instantly share code, notes, and snippets.

@mcgivrer
Last active January 19, 2026 13:33
Show Gist options
  • Select an option

  • Save mcgivrer/3fabaa2cd1a1e1fc934c7b5da6a6e8ca to your computer and use it in GitHub Desktop.

Select an option

Save mcgivrer/3fabaa2cd1a1e1fc934c7b5da6a6e8ca to your computer and use it in GitHub Desktop.
java project micro build with libs
#!/bin/bash
#---- project parameters
project_name=[[DEFAULT_APP_NAME]]
project_version=[[DEFAULT_APP_VERSION]]
main_class=[[DEFAULT_MAIN_CLASS]]
vendor_name="[[DEFAULT_VENDOR_NAME]]"
author_name=[[DEFAULT_AUTHOR_NAME]]
JARS=
SOURCE_VERSION="25"
JAR_OPTS="-Xmx512m"
#
#--- DO NOT CHANGE THE FOLLOWING LINES ---
#
main_class_array=($main_class)
num_main_classes=${#main_class_array[@]}
SRC=./src
LIBS=./libs
TARGET=./target
BUILD=${TARGET}/build
CLASSES=${TARGET}/classes
RESOURCES=${SRC}/main/resources
SOURCE_ENCODING="UTF-8"
JARS=libs/dependencies/
COMPILATION_OPTS="-Xlint:unchecked -Xlint:deprecation -parameters"
# add your test execution commands here
TEST_CLASSES=${TARGET}/test-classes
TEST_RESOURCES=${SRC}/test/resources
LIB_TEST=$LIBS/junit-platform-console-standalone-6.0.0.jar
# detect OS type to set the classpath separator
if [[ "$OSTYPE" == "linux"* ]]; then
FS=":"
else
FS=";"
fi
# define colors
RED='\033[0;31m'
GREEN='\033[0;32m'
BLUE='\033[0;34m'
NC='\033[0m'
#---- process buid
GIT_COMMIT_ID=$(git rev-parse HEAD)
JAVA_BUILD=$(java --version | head -1 | cut -f2 -d' ')
#---- check SDKMAN
REQUIRED_JAVA_VERSION=$SOURCE_VERSION
JAVA_BIN=$(command -v java 2>/dev/null)
JAVA_VERSION_DETECTED=$($JAVA_BIN -version 2>&1 | awk -F '[\"_]' '/version/ {print $2}' | cut -d'.' -f1)
if [ -z "$JAVA_BIN" ] || [ "$JAVA_VERSION_DETECTED" != "$REQUIRED_JAVA_VERSION" ]; then
echo -e "${RED}Java n'est pas installé. Tentative d'installation de Java $REQUIRED_JAVA_VERSION avec SDKMAN.${NC}"
# Ensure the `sdk` command is available by sourcing SDKMAN init if installed
if [ -z "$(command -v sdk 2>/dev/null)" ] && [ -s "$HOME/.sdkman/bin/sdkman-init.sh" ]; then
# shellcheck source=/dev/null
source "$HOME/.sdkman/bin/sdkman-init.sh"
if [ -f .sdkmanrc ]; then
echo "Install SDKMAN environment"
sdk env install
sdk env use
echo "done."
fi
fi
else
echo -e "${GREEN}Java détecté: version $JAVA_VERSION_DETECTED${NC}"
fi
# Prepare Build
rm -vrf target/
find $SRC/main/java $RESOURCES -name "*.java"
mkdir -vp ${TARGET}/{classes,build/libs}
# Compile sources
javac ${COMPILATION_OPTS} -cp libs/ $(find $SRC/main/java $RESOURCES -name "*.java") -d ${CLASSES}
# create MANIFEST file
cp -vr $RESOURCES/* $CLASSES
echo "build jar..."
for app in "${main_class_array[@]}"; do
if [ $num_main_classes -eq 1 ]; then
jar_name="${project_name}-${project_version}.jar"
else
jar_name="${project_name}-$app-${project_version}.jar"
fi
mkdir -p ${TARGET}/META-INF
echo ">> for ${project_name}.$app..."
echo """
Manifest-Version: ${project_name}
Main-Class: ${app}
Class-Path: ${JARS}
Created-By: ${JAVA_BUILD}
Implementation-Title: ${project_name}
Implementation-Version: ${project_version}-build_${GIT_COMMIT_ID:0:12}
Implementation-Vendor: ${vendor_name}
Implementation-Author: ${author_name}
""" >>${TARGET}/META-INF/MANIFEST.MF
jar cvfe ${TARGET}/build/${jar_name} $app -C ${CLASSES} .
# create run script
echo "create run script ${project_name}.sh ..."
rm -f ${project_name}.sh
echo """#!/bin/bash
java -jar ${TARGET}/build/${jar_name} \$@
""" >${project_name}.sh
chmod +x ${project_name}.sh
echo "done."
done
if ([ "$1" == "test" ]); then
echo "Run tests..."
echo -e "|_ ${BLUE}6. Execute tests${NC}..."
echo "> from : ${SRC}/test"
echo "> to : ${TARGET}/test-classes"
mkdir -p ${TARGET}/test-classes
echo "copy test resources"
cp -r ./$RESOURCES/* $TEST_CLASSES
cp -r ./$TEST_RESOURCES/* $TEST_CLASSES
echo "compile test classes"
#list test sources
find ${SRC}/main -name '*.java' >${TARGET}/sources.lst
find ${SRC}/test -name '*.java' >${TARGET}/test-sources.lst
javac -source $SOURCE_VERSION -encoding $SOURCE_ENCODING $COMPILATION_OPTS -cp ".${FS}$LIB_TEST${FS}${EXTERNAL_JARS}" -d $TEST_CLASSES @${TARGET}/sources.lst @${TARGET}/test-sources.lst
echo "execute tests through JUnit"
java $JAR_OPTS -jar $LIB_TEST execute -cp "${EXTERNAL_JARS}${FS}${CLASSES}${FS}${TEST_CLASSES}${FS}." --scan-class-path
echo -e " |_ ${GREEN}done$NC"
echo "- execute tests through JUnit ${SRC}/test." >>${TARGET}/build.log
fi
if ([ "$1" == "run" ]); then
echo "Run the generated JAR(s)..."
for app in "${main_class_array[@]}"; do
if [ $num_main_classes -eq 1 ]; then
jar_name="${project_name}-${project_version}.jar"
else
jar_name="${project_name}-$app-${project_version}.jar"
fi
echo ">> run JAR ${jar_name} ..."
shift 1
java $JAR_OPTS -jar ${TARGET}/build/${jar_name} $@
echo "done."
done
fi
package core;
import java.io.IOException;
import java.util.Properties;
public class App {
private Properties config = new Properties();
public static int debug = 0;
public App() {
System.out.println("Start App class...");
}
public void run(String[] args) {
init(args);
process();
dispose();
}
private void init(String[] args) {
try {
config.load(App.class.getResourceAsStream("/config.properties"));
for (String key : config.stringPropertyNames()) {
String value = config.getProperty(key);
parseArg(key + "=" + value);
}
} catch (IOException e) {
System.err.printf(" failed to load config file: %s%n", e.getMessage());
}
if (args.length > 0) {
System.out.println("parse args:");
int i = 0;
for (String arg : args) {
System.out.printf("- arg[%d]: %s%n", i++, arg);
parseArg(arg);
}
} else {
System.out.println("- no argument...");
}
}
public void parseArg(String arg) {
if (arg.contains("=")) {
String[] key = arg.split("=", 2);
switch (key[0].toLowerCase().trim()) {
case "d", "-d", "debug", "--debug" -> {
debug = Integer.parseInt(key[1]);
System.out.printf(" set debug level to %d%n", debug);
}
default -> {
System.out.printf(" unknown argument: %s%n", arg);
}
}
} else {
System.out.printf(" unknown argument: %s%n Respect the format key=value%n", arg);
}
}
private void process() {
System.out.println("Processing...");
}
private void dispose() {
System.out.println("End App class.");
}
public static void main(String[] args) {
App app = new App();
app.run(args);
}
}
#!/bin/bash
if [ "$#" -ne 1 ]; then
echo "Usage: $0 <project_name>"
echo "Description: create a java project structure into <project_name> with build script"
echo "NOTE: if git user.name and user.email configuration are not set, git initialization will fail"
echo " Make sure to set them using:"
echo " git config --global user.name \"Your Name\""
echo " git config --global user.email \"your.email@example.com\""
echo " After setting these, rerun the script."
exit 1
fi
JUNIT_VERSION=6.0.0
GIT_USERNAME="$(git config --get user.name)<$(git config --get user.email)>"
# create project structure
mkdir -p $1/{src/{{main,test}/{java/core,resources},docs},libs/dependencies,.vscode}
# add readme
echo """# README
## Project $1 version 0.0.1
### Build JAR with sdkman and javac
\`\`\`bash
sdk env use
chmod +x ./build
./build
\`\`\`
### Run tests
\`\`\`bash
./build test
\`\`\`
### Execute project
\`\`\`bash
./build run debug=2
\`\`\`
>**Note**
>You can pass as many argument after run as you need:
>e.g.:
> - \`debug\`=[1 to 5],
> - \`mode\`=[DEVELOPMENT,TESTING,PRODUCTION]
> - \`timeout\`=[milliseconds]
#### help on program
\`\`\`bash
./build run help
\`\`\`
#### run from java
\`\`\`bash
java -jar target/build/$1-0.0.1.jar debug=2
\`\`\`
Thanks,
$GIT_USERNAME.
""" >$1/README.md
# add gitignore
echo "target/" >$1/.gitignore
# add sdkman config
echo "java=25-zulu" >$1/.sdkmanrc
## download JUnit
curl -sL https://repo1.maven.org/maven2/org/junit/platform/junit-platform-console-standalone/$JUNIT_VERSION/junit-platform-console-standalone-$JUNIT_VERSION.jar >$1/libs/junit-platform-console-standalone-$JUNIT_VERSION.jar
## build script
curl -sL https://gist.githubusercontent.com/mcgivrer/3fabaa2cd1a1e1fc934c7b5da6a6e8ca/raw/build >$1/build
sed -i \
-e "s/\[\[DEFAULT_APP_NAME\]\]/$1/g" \
-e "s/\[\[DEFAULT_APP_VERSION\]\]/0.0.1/g" \
-e "s/\[\[DEFAULT_MAIN_CLASS\]\]/core.App/g" \
-e "s/\[\[DEFAULT_AUTHOR_NAME\]\]/\"$GIT_USERNAME\"/g" \
-e "s/\[\[DEFAULT_VENDOR_NAME\]\]/VENDORNAME/g" \
"$1/build"
chmod +x $1/build
## create VSCode support
echo """
{
\"java.project.sourcePaths\": [
\"src/main/java\",
\"src/main/resources\",
\"src/test/java\",
\"src/test/resources\"
],
\"java.project.referencedLibraries\": [
\"libs/junit-platform-console-standalone-6.0.0.jar\"
],
\"java.project.outputPath\": \"target/classes\"
}
""" >$1/.vscode/settings.json
## Create Main app class source
echo """package core;
import java.io.IOException;
import java.time.ZonedDateTime;
import java.util.Properties;
public class App {
public enum DebugLevel {
DEBUG,
INFO,
WARN,
ERROR,
FATAL;
}
public enum AppMode {
DEVELOPMENT,
TESTING,
PRODUCTION;
}
private Properties config = new Properties();
public static int debug = 0;
private static AppMode mode = AppMode.DEVELOPMENT;
private static int timeout = 1000; // in milliseconds
public App() {
log(getClass(), DebugLevel.INFO, \"Start App class...\");
}
public void run(String[] args) {
initialize(args);
try {
log(getClass(), DebugLevel.INFO, \"Sleeping for %d ms...\", timeout);
Thread.sleep(timeout);
log(getClass(), DebugLevel.INFO, \"Execution completed after timeout.\");
} catch (InterruptedException e) {
log(getClass(), DebugLevel.WARN, \"Execution interrupted.\");
}
log(getClass(), DebugLevel.INFO, \"End App class.\");
}
private void initialize(String[] args) {
// set default values
log(getClass(), DebugLevel.INFO, \"Set default configuration\");
config.setProperty(\"debug\", \"0\");
config.setProperty(\"mode\", \"DEVELOPMENT\");
config.setProperty(\"timeout\", \"1000\");
parseConfiguration();
log(App.class, DebugLevel.INFO, \" -> default configuration initialized\");
// parse configuration file
try {
log(getClass(), DebugLevel.INFO, \"Load configuration from file\");
config.load(App.class.getResourceAsStream(\"/config.properties\"));
log(App.class, DebugLevel.INFO, \" loaded config.properties\");
parseConfiguration();
log(App.class, DebugLevel.INFO, \" -> configuration loaded\");
} catch (IOException e) {
log(App.class, DebugLevel.ERROR, \" cannot load config.properties: %s\", e.getMessage());
}
// parse command line arguments
parseCliArgs(args);
parseConfiguration();
}
private void parseConfiguration() {
for (String key : config.stringPropertyNames()) {
parseConfig(key, config.getProperty(key));
}
}
private void parseCliArgs(String[] args) {
if (args.length > 0) {
log(getClass(), DebugLevel.INFO, \"parse args:\");
int i = 0;
for (String arg : args) {
log(getClass(), DebugLevel.INFO, \"- arg[%d]: %s\", i++, arg);
parseArg(arg);
}
} else {
log(getClass(), DebugLevel.INFO, \"- no argument...\");
}
}
private void parseArg(String arg) {
if (arg.contains(\"=\")) {
String[] key = arg.split(\"=\", 2);
config.setProperty(key[0], key[1]);
} else {
config.setProperty(arg, \"true\");
}
}
private void parseConfig(String key, String value) {
// first is config key.value, then last possible cli key args.
switch (key.toLowerCase().trim()) {
// set debug level from config file or CLI key-value pair
case \"debug\" -> {
debug = Integer.parseInt(value);
log(getClass(), DebugLevel.INFO, \" set debug level to %d\", debug);
}
case \"mode\" -> {
mode = AppMode.valueOf(value.toUpperCase());
log(getClass(), DebugLevel.INFO, \" set app mode to %s\", value);
}
case \"timeout\" -> {
timeout = Integer.parseInt(value);
log(getClass(), DebugLevel.INFO, \" set app timeout to %d ms\", timeout);
}
case \"h\", \"-h\", \"help\", \"-help\" -> {
log(getClass(), DebugLevel.INFO, \" help requested, exiting...\");
System.out.println(\"Usage: java -jar app.jar [key=value]...\n\" +
\"Available options:\n\" +
\" debug=<level> Set debug level (0=none, 1=some, 2=verbose)\n\" +
\" mode=<mode> Set application mode (DEVELOPMENT, TESTING, PRODUCTION)\n\" +
\" timeout=<ms> Set application timeout in milliseconds\n\" +
\" help Show this help message\");
System.exit(0);
}
default -> {
log(getClass(), DebugLevel.WARN, \" unknown argument: %s=%s\", key, value);
}
}
}
public static void main(String[] args) {
App app = new App();
app.run(args);
}
public static void log(Class<?> cls, DebugLevel level, String message, Object... args) {
System.out.printf(\"%s;%s;[%s];%s%n\",
ZonedDateTime.now(),
cls.getCanonicalName(),
level.name(),
message.formatted(args));
}
}
""" >$1/src/main/java/core/App.java
# add config file
echo """debug=2
mode=DEVELOPMENT
timeout=3000
""" >$1/src/main/resources/config.properties
git init -b main --quiet $1/
cd $1
git add .
git commit -m "Create Project $1"
echo "==> Project $1 created."
@mcgivrer
Copy link
Author

mcgivrer commented May 7, 2025

Usage

Use the create scripts to create a simple Java project inspired by the Maven project structure :

Create the project

create jdemo

Project Structure :

[Project]
|_ libs
|_ src
|   |_ main
|   |   |_ java
|   |   |_ resources
|   |_ test
|      |_ java
|      |_ resources
|_ target
|  |_ classes
|  |_ build
|_ build

build is the bash script to build your project.

You need to define the following variables in the script:

  1. Defining the jar output :

    • project_name=jdemo
    • project_version=1.0.0
    • main_class=App
  2. List jar dependencies for build and run :

    • JARS= [space separated list of JAR in libs/]
  3. Define entries for MANIFEST:

    • vendor_name=[a vendor name]
    • author_name=[an author name]

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment