Skip to content

Instantly share code, notes, and snippets.

@mcgivrer
Last active January 29, 2026 12:52
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 -cp libs/ -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 static core.App.Log.error;
import static core.App.Log.info;
import static core.App.Log.warn;
import java.io.IOException;
import java.time.ZonedDateTime;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
public class App {
public enum LogLevel {
DEBUG,
INFO,
WARN,
ERROR,
FATAL;
}
public enum AppMode {
DEVELOPMENT,
TESTING,
PRODUCTION;
}
public static class Log {
public static void log(Class<?> cls, LogLevel level, String message, Object... args) {
if (!(mode.equals(AppMode.DEVELOPMENT) && LogLevel.DEBUG.equals(level)) && isDebugGreaterThan(0)) {
return;
}
System.out.printf("%s;%s;%s;%s%n",
ZonedDateTime.now(),
cls.getCanonicalName(),
level.name(),
message.formatted(args));
}
public static void debug(Class<?> cls, String message, Object... args) {
log(cls, LogLevel.DEBUG, message, args);
}
public static void info(Class<?> cls, String message, Object... args) {
log(cls, LogLevel.INFO, message, args);
}
public static void warn(Class<?> cls, String message, Object... args) {
log(cls, LogLevel.WARN, message, args);
}
public static void error(Class<?> cls, String message, Object... args) {
log(cls, LogLevel.ERROR, message, args);
}
public static void fatal(Class<?> cls, int errCode, String message, Object... args) {
log(cls, LogLevel.FATAL, message, args);
System.exit(errCode);
}
}
public static class Configuration {
interface ConfigParser<T> {
void parseConfig(String value);
T getDefaultValue();
T getValue();
String getDescription();
}
private Properties config = new Properties();
public static final String DEBUG = "debug";
public static final String MODE = "mode";
public static final String TIMEOUT = "timeout";
public static final Map<String, Object> values = new HashMap<>();
public static final Map<String, ConfigParser<?>> parsers = new HashMap<>();
public Configuration(String[] args) {
// set default values
info(getClass(), "Set default configuration");
config.setProperty("debug", "0");
config.setProperty("mode", "DEVELOPMENT");
parsers.put("debug", new ConfigParser<Integer>() {
int debug = 0;
public void parseConfig(String value) {
try {
debug = Integer.parseInt(value);
} catch (NumberFormatException e) {
debug = getDefaultValue();
}
};
public Integer getDefaultValue() {
return 0;
}
public Integer getValue() {
return Integer.valueOf(debug);
}
public String getDescription() {
return "Set debug level (0=none, 1=some, 2=verbose)";
}
});
parsers.put("mode", new ConfigParser<AppMode>() {
AppMode mode = AppMode.DEVELOPMENT;
public void parseConfig(String value) {
try {
mode = AppMode.valueOf(value.toUpperCase());
} catch (IllegalArgumentException e) {
mode = getDefaultValue();
}
};
public AppMode getDefaultValue() {
return AppMode.DEVELOPMENT;
}
public AppMode getValue() {
return mode;
}
public String getDescription() {
return "Set application mode (DEVELOPMENT, TESTING, PRODUCTION)";
}
});
parsers.put("help", new ConfigParser<Boolean>() {
public void parseConfig(String value) {
info(getClass(), " help requested, exiting...");
System.out.println("Usage: java -jar app.jar [key=value]...");
parsers.forEach((key, parser) -> {
System.out.printf(" --%-10s=<%-10s> : %s (default: %s)%n",
key,
parser.getDefaultValue().getClass().getSimpleName(),
parser.getDescription(),
parser.getDefaultValue().toString());
});
};
public Boolean getDefaultValue() {
return true;
}
public Boolean getValue() {
System.exit(0);
return null;
}
public String getDescription() {
return "Show help message";
}
});
extractConfigurationValues(args);
}
private void extractConfigurationValues(String[] args) {
parseConfiguration();
info(App.class, " -> default configuration initialized");
// parse configuration file
try {
info(getClass(), "Load configuration from file");
config.load(App.class.getResourceAsStream("/config.properties"));
info(App.class, " loaded config.properties");
parseConfiguration();
info(App.class, " -> configuration loaded");
} catch (IOException e) {
error(App.class, " 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) {
info(getClass(), "parse args:");
int i = 0;
for (String arg : args) {
Log.debug(getClass(), "- arg[%d]: %s", i++, arg);
parseArg(arg);
}
} else {
info(getClass(), "- no argument...");
}
}
private void parseArg(String arg) {
if (arg.contains("=")) {
String[] key = arg.split("=", 2);
config.setProperty(key[0].trim().replace("--", ""), key[1].trim());
} else {
config.setProperty(arg.trim().replace("--", ""), "true");
}
}
private void parseConfig(String key, String value) {
ConfigParser<?> parser = parsers.get(key);
if (parser != null) {
parser.parseConfig(value);
values.put(key, parser.getValue());
info(getClass(), " set %s=%s (%s)", key, parser.getValue().toString(), parser.getDescription());
} else {
warn(getClass(), " unknown argument: %s=%s", key, value);
}
}
public <T> T getValue(String key) {
return (T) values.get(key);
}
}
private Configuration config;
public static int debug = 0;
private static AppMode mode = AppMode.DEVELOPMENT;
public App() {
info(getClass(), "Start App class...");
}
public void run(String[] args) {
initialize(args);
process();
info(getClass(), "End App class.");
}
protected void process() {
}
private void initialize(String[] args) {
config = new Configuration(args);
debug = (int) config.getValue("debug");
mode = (AppMode) config.getValue("mode");
info(getClass(), " -> App initialized with debug=%d, mode=%s", debug, mode.name());
}
public static void main(String[] args) {
App app = new App();
app.run(args);
}
public static boolean isDebugGreaterThan(int level) {
return debug > level;
}
public Configuration getConfiguration() {
return config;
}
}
#!/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.1
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,.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 .gitignore file
echo """# Compiled class files
*.class
# Log files
*.log
# Package files
*.jar
*.war
*.ear
# Maven target directory
target/
# Eclipse files
.classpath
.project
.settings/
# IntelliJ IDEA files
*.iml
.idea/
# VSCode files
.vscode/
""" >$1/.gitignore
## Create Main app class source
echo """package core;
import static core.App.Log.error;
import static core.App.Log.info;
import static core.App.Log.warn;
import java.io.IOException;
import java.time.ZonedDateTime;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
public class App {
public enum LogLevel {
DEBUG,
INFO,
WARN,
ERROR,
FATAL;
}
public enum AppMode {
DEVELOPMENT,
TESTING,
PRODUCTION;
}
public static class Log {
public static void log(Class<?> cls, LogLevel level, String message, Object... args) {
if (!(mode.equals(AppMode.DEVELOPMENT) && LogLevel.DEBUG.equals(level)) && isDebugGreaterThan(0)) {
return;
}
System.out.printf(\"%s;%s;%s;%s%n\",
ZonedDateTime.now(),
cls.getCanonicalName(),
level.name(),
message.formatted(args));
}
public static void debug(Class<?> cls, String message, Object... args) {
log(cls, LogLevel.DEBUG, message, args);
}
public static void info(Class<?> cls, String message, Object... args) {
log(cls, LogLevel.INFO, message, args);
}
public static void warn(Class<?> cls, String message, Object... args) {
log(cls, LogLevel.WARN, message, args);
}
public static void error(Class<?> cls, String message, Object... args) {
log(cls, LogLevel.ERROR, message, args);
}
public static void fatal(Class<?> cls, int errCode, String message, Object... args) {
log(cls, LogLevel.FATAL, message, args);
System.exit(errCode);
}
}
public static class Configuration {
interface ConfigParser<T> {
void parseConfig(String value);
T getDefaultValue();
T getValue();
String getDescription();
}
private Properties config = new Properties();
public static final String DEBUG = \"debug\";
public static final String MODE = \"mode\";
public static final String TIMEOUT = \"timeout\";
public static final Map<String, Object> values = new HashMap<>();
public static final Map<String, ConfigParser<?>> parsers = new HashMap<>();
public Configuration(String[] args) {
// set default values
info(getClass(), \"Set default configuration\");
config.setProperty(\"debug\", \"0\");
config.setProperty(\"mode\", \"DEVELOPMENT\");
parsers.put(\"debug\", new ConfigParser<Integer>() {
int debug = 0;
public void parseConfig(String value) {
try {
debug = Integer.parseInt(value);
} catch (NumberFormatException e) {
debug = getDefaultValue();
}
};
public Integer getDefaultValue() {
return 0;
}
public Integer getValue() {
return Integer.valueOf(debug);
}
public String getDescription() {
return \"Set debug level (0=none, 1=some, 2=verbose)\";
}
});
parsers.put(\"mode\", new ConfigParser<AppMode>() {
AppMode mode = AppMode.DEVELOPMENT;
public void parseConfig(String value) {
try {
mode = AppMode.valueOf(value.toUpperCase());
} catch (IllegalArgumentException e) {
mode = getDefaultValue();
}
};
public AppMode getDefaultValue() {
return AppMode.DEVELOPMENT;
}
public AppMode getValue() {
return mode;
}
public String getDescription() {
return \"Set application mode (DEVELOPMENT, TESTING, PRODUCTION)\";
}
});
parsers.put(\"help\", new ConfigParser<Boolean>() {
public void parseConfig(String value) {
info(getClass(), \" help requested, exiting...\");
System.out.println(\"Usage: java -jar app.jar [key=value]...\");
parsers.forEach((key, parser) -> {
System.out.printf(\" --%-10s=<%-10s> : %s (default: %s)%n\",
key,
parser.getDefaultValue().getClass().getSimpleName(),
parser.getDescription(),
parser.getDefaultValue().toString());
});
};
public Boolean getDefaultValue() {
return true;
}
public Boolean getValue() {
System.exit(0);
return null;
}
public String getDescription() {
return \"Show help message\";
}
});
extractConfigurationValues(args);
}
private void extractConfigurationValues(String[] args) {
parseConfiguration();
info(App.class, \" -> default configuration initialized\");
// parse configuration file
try {
info(getClass(), \"Load configuration from file\");
config.load(App.class.getResourceAsStream(\"/config.properties\"));
info(App.class, \" loaded config.properties\");
parseConfiguration();
info(App.class, \" -> configuration loaded\");
} catch (IOException e) {
error(App.class, \" 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) {
info(getClass(), \"parse args:\");
int i = 0;
for (String arg : args) {
Log.debug(getClass(), \"- arg[%d]: %s\", i++, arg);
parseArg(arg);
}
} else {
info(getClass(), \"- no argument...\");
}
}
private void parseArg(String arg) {
if (arg.contains(\"=\")) {
String[] key = arg.split(\"=\", 2);
config.setProperty(key[0].trim().replace(\"--\", \"\"), key[1].trim());
} else {
config.setProperty(arg.trim().replace(\"--\", \"\"), \"true\");
}
}
private void parseConfig(String key, String value) {
ConfigParser<?> parser = parsers.get(key);
if (parser != null) {
parser.parseConfig(value);
values.put(key, parser.getValue());
info(getClass(), \" set %s=%s (%s)\", key, parser.getValue().toString(), parser.getDescription());
} else {
warn(getClass(), \" unknown argument: %s=%s\", key, value);
}
}
public <T> T getValue(String key) {
return (T) values.get(key);
}
}
private Configuration config;
public static int debug = 0;
private static AppMode mode = AppMode.DEVELOPMENT;
public App() {
info(getClass(), \"Start App class...\");
}
public void run(String[] args) {
initialize(args);
process();
info(getClass(), \"End App class.\");
}
protected void process() {
}
private void initialize(String[] args) {
config = new Configuration(args);
debug = (int) config.getValue(\"debug\");
mode = (AppMode) config.getValue(\"mode\");
info(getClass(), \" -> App initialized with debug=%d, mode=%s\", debug, mode.name());
}
public static void main(String[] args) {
App app = new App();
app.run(args);
}
public static boolean isDebugGreaterThan(int level) {
return debug > level;
}
public Configuration getConfiguration() {
return config;
}
}
""" >$1/src/main/java/core/App.java
# add config file
echo """debug=2
mode=DEVELOPMENT
""" >$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