Conversation
| } | ||
| ``` | ||
|
|
||
| The preceding example uses ``run(_:arguments:environment:workingDirectory:platformOptions:input:output:error:)-(_,_,_,_,_,Input,_,_)``: |
There was a problem hiding this comment.
I think this is supposed to end after error:)
There was a problem hiding this comment.
Nope, need the disambiguating extension on that one. The only one that doesn’t need it (or the run) is the one with body: closure
There was a problem hiding this comment.
I realize now this is DocC-specific markup, so it won't appear exactly like this once processed by DocC. Feel free to mark this as resolved.
|
Heads-up: #349 removes the deprecated |
| The function `run` returns only after the body returns and the process exits. | ||
| Because of that, the `execution` value, its streams, and the input writer are valid only inside the body. | ||
|
|
||
| ## Drain every stream, concurrently |
There was a problem hiding this comment.
Thank you!! This section is very useful. I was struggling to find the right place to emphasize the concurrency requirement.
|
thanks @broken-circle - no worries, it's already merged - so I'll rebase and I need to update based on it (and other's) changes anyway based on the feedback. Bits I missed when assembling ;-) |
invalidname
left a comment
There was a problem hiding this comment.
This looks really useful. I've left a few suggestions to consider.
|
|
||
| > Note: A non-zero exit never surfaces as a Swift error, and neither does output on standard error. | ||
| > To run a `catch` block when a command “fails,” check ``TerminationStatus/isSuccess`` yourself and throw from your own code. | ||
|
|
There was a problem hiding this comment.
This was a nice section that clears up some potential misunderstandings.
| } | ||
| ``` | ||
|
|
||
| The same structure can chain two subprocesses, sending the stream of output from one subprocess to the input of another. |
There was a problem hiding this comment.
I think creating the equivalent to a shell pipe is important enough that this should be its own section.
Possibly 'Chaining Subprocesses' or even 'Chaining Subprocesses To Pipe Commands' or 'Pipe Commands By Chaining Subprocesses'.
When I first scanned the article, I was hoping to find code on how to pipe using Subprocess and missed this section completely.
I also think using the term 'pipe' or 'piping' either in a heading title or body text would make this section easier to find in searches.
There was a problem hiding this comment.
I think this would be an excellent additional article to add, referenced from here. I don't think it's a great addition for this article though, since it's trying to be more introductory and common use case, and doing so would expand the scope and narrative of the this article, which is really meant to provide the basics and rules behind handling streaming. The piping and chaining concepts are something I'd describe as more advanced topics. Not invalid, just not something I want to try and cover in this article.
| ``FileDescriptorOutput/fileDescriptor(_:closeAfterSpawningProcess:)``, | ||
| which is efficient for large amounts of data. | ||
| When you use a file descriptor, the bytes never pass through your code. | ||
|
|
There was a problem hiding this comment.
Possibly add a code example showing writing the output of a command that generates a lot of text directly to a file via file descriptor?
There was a problem hiding this comment.
I wanted to keep this article focused on the basics and most common usage. I agree that an example showing how to use the file descriptors effectively - where they shine - would be a great add, but I don't want to expand the scope of this article to include that. I think that would be better as it's own, focused thing - I'm seeing that as "advanced use" of this library.
invalidname
left a comment
There was a problem hiding this comment.
Looks great. Thanks for all your work on this.
| * The *collecting* form waits for the subprocess to finish and provides the result. | ||
| * The *streaming* form lets you read output while the subprocess runs. | ||
|
|
||
| You choose between them depending on whether you pass a trailing closure. |
There was a problem hiding this comment.
This sentence isn't entirely accurate. You choose whether you stream or collect by passing the appropriate options to output and error parameters. Technically, you could choose to collect (via .string, .data, etc.) even when passing a trailing closure. In other words, you are not forced to stream if you pass in a trailing closure.
There was a problem hiding this comment.
Awesome, thank you! I'm going to use almost your sentence description directly!
| let writer = sort.standardInputWriter | ||
| try await withThrowingTaskGroup(of: Void.self) { group in | ||
| group.addTask { | ||
| _ = try await run( |
There was a problem hiding this comment.
I'm not 100% sure if we should highlight this example. Subprocess 1.0 deliberately lacks process piping because it's scheduled for 1.1: #151
While this example works, it's inefficient (parent output shouldn't need to be "read out" to a sequence and then write into child process via writer.write; they should just have the pipes connected directly), and it's not the shape we want to encourage developers to copy.
On the other hand, this example is how you can emulate process piping with the API today, so I do see it has value. @heckj, what do you think? Should we leave this out if we know we will be introducing an official API later?
There was a problem hiding this comment.
I was on the fence about this, given how awkward the API is, but it was a frequent ask in the conversations I followed, not even also tracking #151. I'm inclined to leave it in, primarily because of the asks about it specifically, and it's what we have with the current release of this API. I'd also suggest that as we pin down better API for this down the road, we make a note to explicitly come back and change up this article, or break it out into it's own article (if it's sufficiently deep to warrant it) when updated API is available.
More than happy to pop back when we get there to revise and update, or draft a new article along these lines!
… more common inputs and patterns
…esolving a lingering possessive use
…tail and clarity based on initial feedback
- Add `- Throws:` clauses to all six `run` overloads: they throw
`SubprocessError` on launch/limit failures (or rethrow the body
closure's error), and a non-zero exit is a normal result to inspect
via `terminationStatus`, not a thrown error.
- Fix imprecise "The subprocess throws an error…" comments on `.string`,
`.bytes`, `.data`, and `maxLineLength` to attribute the throw to
`run`/iteration and name `SubprocessError`.
- Correct the landing-page example: `standardOutput` is non-optional, so
it no longer prints `Optional(...)`.
Co-authored-by: Chris Adamson <invalidname@gmail.com>
Co-authored-by: Chris Adamson <invalidname@gmail.com>
Co-authored-by: Chris Adamson <invalidname@gmail.com>
… in terminationStatus, per feedback
Co-authored-by: Chris Adamson <invalidname@gmail.com>
- GettingStarted.md: qualify terminationStatus link as ExecutionResult/terminationStatus - OutputProtocol.md: remove standardOutput/standardError (they live on Execution, not OutputProtocol)
I wanted to wrap up the great conversations I saw during the development work for this library to provide some of the concrete "how to use it" advice touching on collecting result content, searching for executables vs. providing a path, and the just edging on the stream processing for results from a long running process - or even the input streaming.
I put together these two articles, pulling heavily in influence from the conversations in Swift Forums.
Please look over the example code blocks with a high degree of skepticism, and verify that I'm not suggesting poor code patterns or encouraging use in a way that would be harmful.