Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ jobs:
contents: read
strategy:
matrix:
java: ['17', '21', '23']
java: ['17', '21', '24']
env:
DEFAULT_JAVA: '17'
runs-on: ubuntu-latest
Expand Down Expand Up @@ -47,7 +47,7 @@ jobs:

- name: Sonar analysis
if: ${{ env.DEFAULT_JAVA == matrix.java && env.SONAR_TOKEN != null }}
run: ./gradlew sonar --info --exclude-task integrationTest -Dsonar.token=$SONAR_TOKEN
run: ./gradlew sonar --info -Dsonar.token=$SONAR_TOKEN
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/gh-pages.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ jobs:
- name: Build Javadoc
run: ./gradlew javadoc --info
- name: Build Reports
run: ./gradlew check jacocoTestReport --exclude-task integrationTest --info
run: ./gradlew check jacocoTestReport --info
- name: Collect artifacts
run: cp -r build/reports/ build/docs/
- name: Upload artifact
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,8 @@ jobs:
if: ${{ !inputs.skip-deploy-maven-central }}
run: ./gradlew publishToSonatype closeAndReleaseSonatypeStagingRepository --warning-mode all
env:
ORG_GRADLE_PROJECT_sonatypeUsername: ${{ secrets.OSSRH_USERNAME }}
ORG_GRADLE_PROJECT_sonatypePassword: ${{ secrets.OSSRH_PASSWORD }}
ORG_GRADLE_PROJECT_sonatypeUsername: ${{ secrets.MAVEN_CENTRAL_PORTAL_TOKEN }}
ORG_GRADLE_PROJECT_sonatypePassword: ${{ secrets.MAVEN_CENTRAL_PORTAL_USERNAME }}
ORG_GRADLE_PROJECT_signingKey: ${{ secrets.OSSRH_GPG_SECRET_KEY }}
ORG_GRADLE_PROJECT_signingPassword: ${{ secrets.OSSRH_GPG_SECRET_KEY_PASSWORD }}

Expand Down
4 changes: 4 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,8 @@
"-Djava.util.logging.config.file=src/test/resources/logging.properties"
]
},
"sonarlint.connectedMode.project": {
"connectionId": "itsallcode",
"projectKey": "org.itsallcode:simple-process"
}
}
11 changes: 11 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# Changelog
All notable changes to this project will be documented in this file.

The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [0.2.0] - unreleased

## [0.1.0] - 2025-02-??

* [PR #1](https://github.com/itsallcode/simple-process/pull/1): Initial release
91 changes: 91 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,94 @@ Wrapper to simplify working with external processes.

**This project is at an early development stage and the API will change without backwards compatibility.**

[![Java CI](https://github.com/itsallcode/simple-process/actions/workflows/build.yml/badge.svg)](https://github.com/itsallcode/simple-process/actions/workflows/build.yml)
[![CodeQL](https://github.com/itsallcode/simple-process/actions/workflows/codeql-analysis.yml/badge.svg)](https://github.com/itsallcode/simple-process/actions/workflows/codeql-analysis.yml)
[![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=org.itsallcode%3Asimple-process&metric=alert_status)](https://sonarcloud.io/summary/new_code?id=org.itsallcode%3Asimple-process)
[![Coverage](https://sonarcloud.io/api/project_badges/measure?project=org.itsallcode%3Asimple-process&metric=coverage)](https://sonarcloud.io/summary/new_code?id=org.itsallcode%3Asimple-process)
[![Reliability Rating](https://sonarcloud.io/api/project_badges/measure?project=org.itsallcode%3Asimple-process&metric=reliability_rating)](https://sonarcloud.io/summary/new_code?id=org.itsallcode%3Asimple-process)
[![Security Rating](https://sonarcloud.io/api/project_badges/measure?project=org.itsallcode%3Asimple-process&metric=security_rating)](https://sonarcloud.io/summary/new_code?id=org.itsallcode%3Asimple-process)
[![Vulnerabilities](https://sonarcloud.io/api/project_badges/measure?project=org.itsallcode%3Asimple-process&metric=vulnerabilities)](https://sonarcloud.io/summary/new_code?id=org.itsallcode%3Asimple-process)
[![Maven Central](https://img.shields.io/maven-central/v/org.itsallcode/simple-process)](https://search.maven.org/artifact/org.itsallcode/simple-process)

* [Changelog](CHANGELOG.md)
* [API JavaDoc](https://blog.itsallcode.org/simple-process/javadoc/org.itsallcode.process/module-summary.html)
* [Test report](https://blog.itsallcode.org/simple-process/reports/tests/test/index.html)
* [Coverage report](https://blog.itsallcode.org/simple-process/reports/jacoco/test/html/index.html)

## Usage

This project requires Java 17 or later.

### Add Dependency

Add dependency to your Gradle project:

```groovy
dependencies {
implementation 'org.itsallcode:simple-process:0.1.0'
}
```

Add dependency to your Maven project:

```xml
<dependency>
<groupId>org.itsallcode</groupId>
<artifactId>simple-process</artifactId>
<version>0.1.0</version>
</dependency>
```

### Features

Simplified API for starting external processes and executable JARs. Allows easy capturing stdout and stderr and forwarding to log output.

## Development

### Check if dependencies are up-to-date

```sh
./gradlew dependencyUpdates
```

### Building

Install to local maven repository:

```sh
./gradlew publishToMavenLocal
```

### Test Coverage

To calculate and view test coverage:

```sh
./gradlew check jacocoTestReport
open build/reports/jacoco/test/html/index.html
```

### View Generated Javadoc

```sh
./gradlew javadoc
open build/docs/javadoc/index.html
```

### Publish to Maven Central

#### Preparations

1. Checkout the `main` branch, create a new branch.
2. Update version number in `build.gradle` and `README.md`.
3. Add changes in new version to `CHANGELOG.md`.
4. Commit and push changes.
5. Create a new pull request, have it reviewed and merged to `main`.

#### Perform the Release

1. Start the release workflow
* Run command `gh workflow run release.yml --repo itsallcode/simple-process --ref main`
* or go to [GitHub Actions](https://github.com/itsallcode/simple-process/actions/workflows/release.yml) and start the `release.yml` workflow on branch `main`.
2. Update title and description of the newly created [GitHub release](https://github.com/itsallcode/simple-process/releases).
3. After some time the release will be available at [Maven Central](https://repo1.maven.org/maven2/org/itsallcode/simple-process/).
18 changes: 12 additions & 6 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ plugins {
id 'jacoco-report-aggregation'
id 'signing'
id 'maven-publish'
id 'org.sonarqube' version '6.0.1.5171'
id 'org.sonarqube' version '6.2.0.5505'
id "io.github.gradle-nexus.publish-plugin" version "2.0.0"
id 'com.github.ben-manes.versions' version '0.52.0'
}
Expand All @@ -18,17 +18,21 @@ repositories {
}

dependencies {
// This dependency is exported to consumers, that is to say found on their compile classpath.
api libs.commons.math3

// This dependency is used internally, and not exposed to consumers on their own compile classpath.
implementation libs.guava
}

testing {
suites {
test {
useJUnitJupiter(libs.versions.junitJupiter.get())
dependencies {
implementation libs.assertj
implementation libs.mockitoJunit
}
targets.all {
testTask.configure {
systemProperty 'java.util.logging.config.file', file('src/test/resources/logging.properties')
}
}
}
}
}
Expand Down Expand Up @@ -119,6 +123,8 @@ nexusPublishing {
repositories {
sonatype {
stagingProfileId = "546ea6ce74787e"
nexusUrl.set(uri("https://ossrh-staging-api.central.sonatype.com/service/local/"))
snapshotRepositoryUrl.set(uri("https://central.sonatype.com/repository/maven-snapshots/"))
}
}
}
Expand Down
13 changes: 8 additions & 5 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,14 @@
# https://docs.gradle.org/current/userguide/platforms.html#sub::toml-dependencies-format

[versions]
commons-math3 = "3.6.1"
guava = "33.3.1-jre"
junitJupiter = "5.11.1"
mockito = "5.18.0"
assertj = "3.27.3"
equalsverifier = "3.18.1"
tostringverifier = "1.4.8"

[libraries]
commons-math3 = { module = "org.apache.commons:commons-math3", version.ref = "commons-math3" }
guava = { module = "com.google.guava:guava", version.ref = "guava" }

mockitoJunit = { module = "org.mockito:mockito-junit-jupiter", version.ref = "mockito" }
equalsverifier = { module = "nl.jqno.equalsverifier:equalsverifier", version.ref = "equalsverifier" }
tostringverifier = { module = "com.jparams:to-string-verifier", version.ref = "tostringverifier" }
assertj = { module = "org.assertj:assertj-core", version.ref = "assertj" }
Binary file modified gradle/wrapper/gradle-wrapper.jar
Binary file not shown.
2 changes: 1 addition & 1 deletion gradle/wrapper/gradle-wrapper.properties
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.12.1-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.2-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
Expand Down
6 changes: 3 additions & 3 deletions gradlew
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ case "$( uname )" in #(
NONSTOP* ) nonstop=true ;;
esac

CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
CLASSPATH="\\\"\\\""


# Determine the Java command to use to start the JVM.
Expand Down Expand Up @@ -205,15 +205,15 @@ fi
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'

# Collect all arguments for the java command:
# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
# and any embedded shellness will be escaped.
# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
# treated as '${Hostname}' itself on the command line.

set -- \
"-Dorg.gradle.appname=$APP_BASE_NAME" \
-classpath "$CLASSPATH" \
org.gradle.wrapper.GradleWrapperMain \
-jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \
"$@"

# Stop when "xargs" is not available.
Expand Down
4 changes: 2 additions & 2 deletions gradlew.bat
Original file line number Diff line number Diff line change
Expand Up @@ -70,11 +70,11 @@ goto fail
:execute
@rem Setup the command line

set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
set CLASSPATH=


@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %*

:end
@rem End local scope for the variables with windows NT shell
Expand Down
15 changes: 15 additions & 0 deletions src/main/java/module-info.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@

/**
* Simplified usage of Java's {@link java.lang.Process} API.
* <p>
* Create a new process builder using
* {@link org.itsallcode.process.SimpleProcessBuilder#create()} and start the
* process with
* {@link org.itsallcode.process.SimpleProcessBuilder#start()}.
*/

module simple.process {
exports org.itsallcode.process;

requires java.logging;
}
18 changes: 0 additions & 18 deletions src/main/java/org/example/Library.java

This file was deleted.

42 changes: 42 additions & 0 deletions src/main/java/org/itsallcode/process/AsyncStreamConsumer.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package org.itsallcode.process;

import java.io.*;
import java.nio.charset.StandardCharsets;
import java.util.logging.Level;
import java.util.logging.Logger;

class AsyncStreamConsumer implements Runnable {
private static final Logger LOG = Logger.getLogger(AsyncStreamConsumer.class.getName());
private final String name;
private final long pid;
private final ProcessStreamConsumer consumer;
private final InputStream stream;

AsyncStreamConsumer(final String name, final long pid, final InputStream stream,
final ProcessStreamConsumer consumer) {
this.name = name;
this.pid = pid;
this.stream = stream;
this.consumer = consumer;
}

@Override
public void run() {
LOG.finest(() -> "Start reading from '%s' stream of process %d...".formatted(name, pid));
try (final BufferedReader reader = new BufferedReader(new InputStreamReader(stream, StandardCharsets.UTF_8))) {
String line = null;
while ((line = reader.readLine()) != null) {
consumer.accept(line);
}
LOG.finest(() -> "Stream '%s' of process %d finished".formatted(name, pid));
consumer.streamFinished();
} catch (final IOException exception) {
final Level logLevel = "Stream closed".equals(exception.getMessage()) ? Level.FINEST : Level.WARNING;
LOG.log(logLevel,
"Reading stream '%s' of process %d failed: %s".formatted(name, pid,
exception.getMessage()),
exception);
consumer.streamReadingFailed(exception);
}
}
}
29 changes: 29 additions & 0 deletions src/main/java/org/itsallcode/process/DelegatingConsumer.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package org.itsallcode.process;

import java.io.IOException;
import java.util.Collections;
import java.util.List;

class DelegatingConsumer implements ProcessStreamConsumer {

private final List<ProcessStreamConsumer> delegates;

DelegatingConsumer(final List<ProcessStreamConsumer> delegates) {
this.delegates = Collections.unmodifiableList(delegates);
}

@Override
public void accept(final String line) {
delegates.forEach(delegate -> delegate.accept(line));
}

@Override
public void streamFinished() {
delegates.forEach(ProcessStreamConsumer::streamFinished);
}

@Override
public void streamReadingFailed(final IOException exception) {
delegates.forEach(delegate -> delegate.streamReadingFailed(exception));
}
}
Loading