001/* 002 * SPDX-License-Identifier: Apache-2.0 003 * 004 * Copyright 2025-2026 The Enola <https://enola.dev> Authors 005 * 006 * Licensed under the Apache License, Version 2.0 (the "License"); 007 * you may not use this file except in compliance with the License. 008 * You may obtain a copy of the License at 009 * 010 * https://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, software 013 * distributed under the License is distributed on an "AS IS" BASIS, 014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 015 * See the License for the specific language governing permissions and 016 * limitations under the License. 017 */ 018package dev.enola.common.exec; 019 020import static java.util.Objects.requireNonNull; 021 022import com.google.common.base.Supplier; 023import com.google.common.collect.ImmutableList; 024 025import dev.enola.common.linereader.ExecutionContext; 026 027import java.nio.file.Path; 028 029/** 030 * ProcessRequest describes a request for the Operating System to run a program from an image in an 031 * executable file. 032 * 033 * <p>This is not a {@link Process}, but what is needed to create one process (or several). 034 * 035 * <p>{@link ProcessBuilder} is similar to this. But that's tied to the JVM's builtin process 036 * launcher - while this may use other means to execute the command. This intentionally has no 037 * start() method, because it is passed to a {@link ProcessLauncher} to actually run it. 038 * 039 * @param directory the working directory for the process 040 * @param command the command to execute 041 * @param ctx the {@link ExecutionContext} 042 * @param mergeErrIntoOut whether to merge the error stream into the output stream 043 */ 044public record ProcessRequest( 045 Path directory, 046 ImmutableList<String> command, 047 Supplier<ExecutionContext> ctx, 048 boolean mergeErrIntoOut) { 049 050 // TODO ExecutionContext: Flip InputStream/OutputStream? To avoid unnecessary pumping! 051 052 // TODO Use a "Handler", with ByteBuffer; like 053 // https://brettwooldridge.github.io/NuProcess/apidocs/com/zaxxer/nuprocess/NuProcessHandler.html 054 055 // TODO Integration with common.io.Resource; https://docs.enola.dev/use/fetch/#exec 056 057 public ProcessRequest { 058 requireNonNull(directory, "directory"); 059 requireNonNull(command, "command"); 060 requireNonNull(ctx, "ctx"); 061 if (command.isEmpty()) throw new IllegalArgumentException("command is empty"); 062 } 063 064 public static class Builder { 065 private Path directory; 066 private final ImmutableList.Builder<String> command = new ImmutableList.Builder<>(); 067 private Supplier<ExecutionContext> ctx; 068 private boolean mergeErrIntoOut = false; 069 070 public Builder directory(Path directory) { 071 if (this.directory != null) throw new IllegalStateException("directory already set"); 072 this.directory = requireNonNull(directory, "directory"); 073 return this; 074 } 075 076 public Builder command(String... command) { 077 this.command.add(command); 078 return this; 079 } 080 081 public Builder executionContext(Supplier<ExecutionContext> ctx) { 082 if (this.ctx != null) throw new IllegalStateException("ctx already set"); 083 this.ctx = requireNonNull(ctx, "ctx"); 084 return this; 085 } 086 087 public Builder mergeErrIntoOut(boolean mergeErrIntoOut) { 088 this.mergeErrIntoOut = mergeErrIntoOut; 089 return this; 090 } 091 092 public Builder mergeErrIntoOut() { 093 return mergeErrIntoOut(true); 094 } 095 096 public ProcessRequest build() { 097 return new ProcessRequest(directory, command.build(), ctx, mergeErrIntoOut); 098 } 099 } 100}