# Host.Process — run a program on the computer

> Run a program on the computer a local app runs on. `Host.Process.Run("git", ["--version"])` waits for it and answers a `ProcessResult` (`ExitCode`, `Output`, `Error`, `IsSuccess`). `Host.Process.Start(…)` answers at once with a `HostProcess` that keeps running; `Host.Process.Watch(process)` is its lines as they arrive, for a page to show, and `Host.Process.Kill(process)` ends it with everything it started. The arguments are a list — never a command line — so no shell is involved, and the program must be declared (`program "git";`).

<!-- id: stdlib-host-process · area: stdlib · stability: stable · html: https://osysharp.com/reference/stdlib/host-process/ -->

## Summary        {#summary}
**`Host.Process.Run`** runs a program to its end and answers what it left behind. It is the local-app way to use a
tool the user already has — `git`, a compiler, a converter — from a function.

```osy title="ask git for its version" test app=stdlib-host-process
app Toolbox {
  model "**/*.osy";
  use Osysharp.Ui;
  use Osysharp.Host {
    program "git";
    folder  "~/Projects";
  }
}

using Osysharp.Host;

string GitVersion() {
  ProcessResult run = Host.Process.Run("git", ["--version"]);
  return run.IsSuccess ? run.Output.Trim() : "git answered " + run.ExitCode + ": " + run.Error.Trim();
}
```

A program the app did not declare does not start; see [Host — the computer a local app runs on](https://osysharp.com/reference/stdlib/host/) for the declaration and the rules every `Host`
call follows. The types on this page — `ProcessResult`, `HostProcess`, `ProcessLine` — are named in a file that says
`using Osysharp.Host;`.

## Signature      {#signature}
```osy syntax
ProcessResult Host.Process.Run(
  string fileName,                  // a bare name ("git") or a full path
  List<string> arguments,           // one string per argument; [] or omitted for none
  TimeSpan timeout = 30 seconds,    // may be named: timeout: TimeSpan.FromMinutes(2)
  string workingDirectory = home,   // a folder the app declared
  string input = none)              // text for the program's standard input

class ProcessResult {
  int    ExitCode;     // 0 is success by convention
  string Output;       // everything written to standard output
  string Error;        // everything written to standard error
  bool   IsSuccess;    // ExitCode == 0
}

// a program that keeps running
HostProcess Host.Process.Start(
  string fileName,
  List<string> arguments,
  string workingDirectory = home,
  string input = none,              // written to its standard input, which then stays open
  string logFile = none,            // every line it prints, kept in a file the app declared a folder for
  bool   outlives = false)          // true: it is NOT ended when the platform stops — needs `lasting "…";`

stream<ProcessLine> Host.Process.Watch(process, timeout:)   // its lines so far, then each new one, until it exits
HostProcess?        Host.Process.Find(Guid id)         // the program as it is now, or null when there is none
void                Host.Process.Kill(process)         // end it and everything it started
void                Host.Process.Write(process, string text)   // to its standard input
void                Host.Process.CloseInput(process)

// `process` is the HostProcess that Start answered, or its Id.

class HostProcess {
  Guid     Id;          // how it is found again
  int      ProcessId;   // the operating system's process id
  string   FileName;
  DateTime StartedAt;
  bool     HasExited;   // when this was read
  int?     ExitCode;    // null while it runs
  bool     ChildrenRunning;   // a program it started still runs, after it exited too
}

class ProcessLine {
  long     Sequence;    // 1, 2, 3 … in the order the lines arrived
  string   Text;
  bool     IsError;     // written to standard error
  DateTime At;
}
```

## Description    {#description}
**The arguments are a list, and that is the safety of it.** Each string in the list reaches the program as one
argument, exactly as written. Nothing splits it, and nothing reads it as a command — so a value a person typed can be
an argument without being able to run anything:

```osy title="a person's text as one argument, and as standard input" test app=stdlib-host-process
int Commit(string folder, string message) =>
  Host.Process.Run("git", ["commit", "-m", message], workingDirectory: folder).ExitCode;

string Sorted(string lines) =>
  Host.Process.Run("git", ["stripspace"], input: lines).Output;
```

There is no form that takes a whole command line, and passing one string where the list goes is a compile error that
says so. To use a shell feature — a pipe, a redirect — run the two programs and pass one's `Output` as the other's
`input`.

**A non-zero exit code is an answer, not an exception.** Most programs say "no" that way, so branch on `ExitCode` or
`IsSuccess`. Text on `Error` does not mean failure either: many programs write progress there on a run that worked.
What does throw:

| what happened | what the call does |
|---|---|
| the program is not declared | refused, naming the `program "…";` line to add |
| it is declared but this computer has none | throws, saying so — ask `Host.Which(name)` first when that is likely |
| it is still running at the timeout | the program **and everything it started** are ended, and a `TimeoutException` is thrown |
| it printed more than 8 MB | it is ended and the call throws — this call holds everything a program prints |

**Naming the program.** A bare name is looked up on the user's search path, as a terminal would. A full path is
accepted when it is the very file a declaration means — so `Host.Process.Run(Host.Which("git"), …)` works under
`program "git";`, and a file somewhere else that is merely called `git` does not.

**Where it runs.** In the user's home folder unless `workingDirectory` names another, which must be inside a folder
the app declared. The program gets the user's own environment — the variables a terminal would give it, with the
user's current search path — without the platform's own configuration.

**On Windows, a `.cmd` or `.bat` file is the one exception to "nothing reads the arguments".** Windows runs a batch
file through its command interpreter, which would act on characters such as `&`, `|`, `%` and `"` inside an argument.
So an argument holding one of those is refused for a batch file, with a message that says to send the text on
`input:` instead. Programs installed through npm are batch files on Windows.

### A program that is left running when the platform stops    {#outlives}
Everything an app starts is ended when the local platform stops — when it is stopped, and when it goes quiet and
exits by itself. That is the promise that an app leaves nothing running on the computer. One kind of program must
break it: a server the person is meant to keep using after the app that started it is closed. For that, say so
twice — in the manifest, where a reader can see it, and at the call:

```osy title="declare it, then start it" syntax
use Osysharp.Host {
  program "osy";
  lasting "osy";          // this app may leave `osy` running after it stops
}

HostProcess Open(string folder) =>
  Host.Process.Start("osy", ["launch"], workingDirectory: folder, outlives: true);
```

While the platform runs, such a program is a started program like any other: `Find` finds it, `Watch` reads its
lines, `Kill` ends it. When the platform stops it is let go of instead of ended, with everything it started. It is
also not a reason for the platform to stay up.

| what is written | what happens |
|---|---|
| `outlives: true` and a `lasting` line for the program | it starts, and is left running when the platform stops |
| `outlives: true` and no `lasting` line | refused, naming the line to add — nothing starts |
| a `lasting` line for a program with no `program` line | a compile error: a program that may be left running is first one that may be started |
| no `outlives:` | it is ended with the platform, whatever the manifest says |

⚠ After the platform is gone nothing reads what the program prints, and `logFile:` stops being written. A program
meant to be left should keep its own log.

`osy explain` lists the programs an app may leave running, under their own heading.

### A program that keeps running, and its output on a page    {#start}
`Host.Process.Start` answers at once. The program is the platform's, not the request's: it keeps running when the
page that started it is closed. A [yield — a function that produces results over time](https://osysharp.com/reference/function/yield/) function hands its lines to a page as they are printed:

```osy title="start a program, show its lines as they arrive, and stop it" test app=stdlib-host-process
using Osysharp.Host;

HostProcess StartLog(string folder) =>
  Host.Process.Start("git", ["log", "--oneline"], workingDirectory: folder);

stream<ProcessLine> Lines(Guid id) {
  if (Host.Process.Find(id) == null) { return; }       // not started yet, or long gone
  foreach (var line in Host.Process.Watch(id)) {
    yield return line;
  }
}

void Stop(Guid id) { Host.Process.Kill(id); }

component LogView(string folder) {
  Guid running = Guid.Empty;
  live var lines = Lines(running);                     // follows `running`: a new program, a new list

  action Go() { running = StartLog(folder).Id; }
  action Halt() { Stop(running); }

  render {
    Stack(gap: 2) {
      Row(gap: 2) {
        Button("Start", onPress: Go);
        Button("Stop", onPress: Halt, disabled: running == Guid.Empty);
      }
      foreach (var line in lines) { Text(line.Text); }
    }
  }
}
```

**The lines are kept by the platform.** `Watch` gives every line the program has printed so far and then each new one,
and ends when the program has exited. So a page that opens late, is closed and opened again, or is open in two tabs
sees the same lines from the first. The newest 20,000 are held; `logFile:` keeps all of them in a file — standard
output in the file named, standard error in the same name with `.err` added.
The file can be read with `Host.File` while the program is still writing it — on every system, Windows included,
where a file open for writing is otherwise refused to a reader ("being used by another process"). A read answers what
has been written so far.

**Reading with a deadline.** In a function — not a page following a stream — a loop over `Watch` runs until the
program exits, and a program that has gone silent never gives the loop another turn to look at the clock. Give the
read its own deadline:

```osy title="read a started program for at most five minutes" syntax
var launch = Host.Process.Start("osy", ["launch"], workingDirectory: folder);
var printed = "";
foreach (var line in Host.Process.Watch(launch.Id, timeout: TimeSpan.FromMinutes(5))) {
  printed = printed + line.Text + "\n";
}
var now = Host.Process.Find(launch.Id);
if (now != null && !now.HasExited) {
  Host.Process.Kill(launch.Id);        // still running: reading stopped, the program did not
  return "It took more than five minutes to start. It last said: " + printed;
}
```

`timeout:` is how long the reading goes on, by the clock, counted from the moment it begins. When it passes, the
stream ends — whether or not the program has. The lines read until then are kept, **the program is left running**,
and `Host.Process.Find` says which happened: `HasExited` is false for a program the deadline cut short. Stopping it
is `Host.Process.Kill`; a later `Watch` reads every line again from the first. (`Host.Process.Run`'s `timeout:` is
different on purpose: a run owns its program, so it ends it and throws.)

**Stop ends the whole tree.** `Kill` ends the program and everything it started, including a helper whose own parent
has already exited, and answers once the program has ended. It works after the program has exited by itself too: what it
left running is still its tree, and `ChildrenRunning` says whether anything of it is still going. Stopping the local platform does the same for every
started program, so nothing an app started is left running behind it. And while a started program runs, the local
platform does not shut itself down for being idle.

**`HostProcess` is the program as it was when read.** `HasExited` and `ExitCode` on a value already held do not
change; `Host.Process.Find(id)` reads the present. Keep the `Id` — in page state or in a row — to reach the program
from a later request. A program is known from its start until an hour after it exits, to the app that started it,
and not across a restart of the local platform; `Find` answers null otherwise.

**Talking to it.** `input:` and `Host.Process.Write` write to its standard input; `Host.Process.CloseInput` closes
it, which is how most programs learn there is no more to read.

### Standing in for a program in a test    {#stub}
A test should not need the program to be installed, signed in or on a network. Both ways of starting one can be
stubbed for the rest of a test:

```osy title="a run answered by the test, and a started program replayed from recorded lines" syntax
Host.Process.Run.Stub((fileName, arguments) =>
  new ProcessResult { ExitCode = 0, Output = "git version 9.9.9", Error = "" });

Host.Process.Start.Stub((fileName, arguments) => ["first line", "second line"]);
```

`Run`'s stub answers the `ProcessResult` (`IsSuccess` follows from the exit code). `Start`'s stub answers the lines
the program prints; what the stubbed `Start` hands back is a `HostProcess` like any other, already exited with code
0, so the `Watch`, `Find` and `Kill` the app wrote run against it unchanged — a recorded transcript of a long tool,
replayed.

When the lines alone do not say it, `Start`'s stub answers a **`ProcessReplay`** instead: a program that fails, one
that complains on standard error, or one that prints and then keeps running until it is stopped — which is how a
test sees what the app does with a program that has gone quiet, and that its Stop ends one.

```osy title="a replayed program that fails, and one that keeps running" syntax
Host.Process.Start.Stub((fileName, arguments) =>
  new ProcessReplay { Lines = ["working…"], ErrorLines = ["out of disk"], ExitCode = 3 });

Host.Process.Start.Stub((fileName, arguments) =>
  new ProcessReplay { Lines = ["started"], KeepsRunning = true });   // Watch does not end until Kill
```

| `ProcessReplay` | |
|---|---|
| `Lines` | what it prints to standard output, in order |
| `ErrorLines` | what it prints to standard error, after its output (`ProcessLine.IsError`) |
| `ExitCode` | the code it exits with — 0 unless said |
| `KeepsRunning` | it prints its lines and stays running: `HasExited` is false and `Watch` does not end until `Host.Process.Kill`, after which its exit code is 137, as a stopped program's is |
| `LeavesChildRunning` | a program it started is still running, after it exits too: `ChildrenRunning` is true until `Host.Process.Kill` ends the tree |

A replayed program also keeps what the app **writes to it** — the `input:` it was started with, then each
`Host.Process.Write` while it "runs" — in the log file's name with `.in` added. That is how a test says what an app
told a program it is driving: `Assert.Contains("stop after two", Host.File.ReadAllText(log + ".in"))`.

A replayed program keeps its **`logFile:`** too — its output in the file named and its errors in the same name with
`.err` added — so code that reads a program's log back runs in a test as it does for real.

A stub stands in for the program, not for the app's declaration: the program must still be one the app declared,
and a `workingDirectory:` or `logFile:` outside the declared folders is refused as it is for a real start. A test
does not pass on a call the app itself would be refused.

## Examples       {#examples}
```osy title="a program that is not declared does not start" run app=stdlib-host-process
[Test]
void an_undeclared_program_is_refused_and_named() {
  var refused = Assert.Throws<Exception>(() => Host.Process.Run("curl", ["--version"]));
  Assert.Contains("this app has not declared it", refused.Message);
  Assert.Contains("`program \"git\";`", refused.Message);
  Assert.Contains("add `program \"curl\";`", refused.Message);
}
```

## See also       {#see-also}
- [Host — the computer a local app runs on](https://osysharp.com/reference/stdlib/host/) — the capability, what an app declares, and where it runs
- [Host.File and Host.Directory — files and folders on the computer](https://osysharp.com/reference/stdlib/host-files/) — the files a program reads and writes, and watching a folder for the ones it makes
- [yield — a function that produces results over time](https://osysharp.com/reference/function/yield/) — a function whose answer arrives over time, and the `live var` that shows it
- [Http.*](https://osysharp.com/reference/http/facade/) — the same shape for a call over the network: the answer is data, a failure to reach is the exception
- [Stop a task (CancellationToken) and complete one by hand (TaskCompletionSource)](https://osysharp.com/reference/function/cancellation/) — stopping work a function has started
