Skip to content

Add std::process::Command::envs() #38856

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Jan 25, 2017
Merged

Add std::process::Command::envs() #38856

merged 3 commits into from
Jan 25, 2017

Conversation

zackw
Copy link
Contributor

@zackw zackw commented Jan 5, 2017

Command::envs() adds a vector of key-value pairs to the child
process environment all at once. Suggested in #38526.

This is not fully baked and frankly I'm not sure it even works, but I need some help finishing it up, and this is the simplest way to show you what I've got. The problems I know exist and don't know how to solve, from most to least important, are:

  • I don't know if the type signature of the new function is correct.
  • The new test might not be getting run. I didn't see it go by in the output of x.py test src/libstd --stage 1.
  • The tidy check says process.rs:402: different `since` than before which I don't know what it means.

r? @brson

Command::envs() adds a vector of key-value pairs to the child
process environment all at once.  Suggested in rust-lang#38526.
@rust-highfive
Copy link
Contributor

Thanks for the pull request, and welcome! The Rust team is excited to review your changes, and you should hear from @brson (or someone else) soon.

If any changes to this PR are deemed necessary, please add them as extra commits. This ensures that the reviewer can see what has changed since they last reviewed the code. Due to the way GitHub handles out-of-date commits, this should also make it reasonably obvious what issues have or haven't been addressed. Large or tricky changes may require several passes of review and changes.

Please see the contribution instructions for more information.

@achanda
Copy link
Contributor

achanda commented Jan 5, 2017

The tidy check error will go away if you rename the feature to something else. A feature called process has been declared to be stable sine 1.0.0, in the same file. You are trying to use the same name. The error message from tidy can be better though.

@frewsxcv frewsxcv added the T-libs-api Relevant to the library API team, which will review and decide on the PR/issue. label Jan 5, 2017
@zackw
Copy link
Contributor Author

zackw commented Jan 5, 2017

@achanda

The tidy check error will go away if you rename the feature to something else.

Ah, I misunderstood how that works. I thought that I was expressing "this was added to the 'process' feature in version 1.16". It sounds like you're saying the contents of a 'feature' are permanently frozen once laid down. Is that right? Is it necessary to give this its own feature tag, then, and what would an appropriate name be? If not, what should be done instead?

(Please bear with me; I haven't even fully learned the language itself, never mind the procedures for adding stuff to the library. Much of the relevant documentation only talks about bigger "new features" than adding a single function to an existing module.)

@aturon
Copy link
Member

aturon commented Jan 9, 2017

@zackw

Is it necessary to give this its own feature tag, then, and what would an appropriate name be?

Yep. A good name would be command_envs.

 * give the new feature its own feature tag
 * correct a lifetime problem in the test
 * use .output() instead of .spawn() in the test so that output is
   actually collected
 * correct the same error in the test whose skeleton I cribbed
@zackw
Copy link
Contributor Author

zackw commented Jan 10, 2017

@aturon Thanks. That should be fixed now, and I also figured out how to make the test actually run, so my only remaining concern is I need someone to check whether I got the new function's type signature right.

@achanda
Copy link
Contributor

achanda commented Jan 10, 2017

@zackw you should be able to run the tests by doing ./x.py test src/test/run-pass. (sorry I missed your last comment!)

@zackw
Copy link
Contributor Author

zackw commented Jan 10, 2017

@achanda I figured that out for myself this morning, but thanks.

@aturon
Copy link
Member

aturon commented Jan 11, 2017

cc @rust-lang/libs, new proposed unstable API.

@aturon
Copy link
Member

aturon commented Jan 11, 2017

@zachW We would want to land this API as unstable initially, to give it some time for feedback before committing to it fully.

@aturon
Copy link
Member

aturon commented Jan 11, 2017

Regarding the function signature, you might consider generalizing to take an IntoIterator rather than just a slice.

@alexcrichton
Copy link
Member

Looks good to me, and yeah I'd be in favor of generalizing to IntoIterator rather than only taking a slice. We could also take this opportunity to generalize args as well.

@zackw
Copy link
Contributor Author

zackw commented Jan 12, 2017

I'd be in favor of generalizing to IntoIterator rather than only taking a slice

I agree with this, but I have gotten stuck trying to implement it. Consider

use std::ffi::OsStr;
use std::collections::HashMap;
use std::env;

fn test<I, K, V>(x: &I)
    where I: IntoIterator<Item=(K, V)>,
          K: AsRef<OsStr>, V: AsRef<OsStr>
{
    for (ref k, ref v) in *x {
        println!("{:?}: {:?}", k.as_ref(), v.as_ref());
    }
}

fn test_vec() {
    let filtered_env : Vec<(String, String)> =
        env::vars().filter(
            | &(ref k, _) |
            k == "TERM" || k == "TZ" || k == "LANG" || k == "PATH"
        ).collect();

    test(&filtered_env);
}

fn test_hashmap() {
    let mut map = HashMap::new();
    map.insert("a", "foo");
    map.insert("b", "bar");
    map.insert("c", "baz");

    test(&map);
}

fn main() {
    test_vec();
    test_hashmap();
}

test has the right external signature, I think, but it fails to compile with

error[E0507]: cannot move out of borrowed content
 --> test.rs:8:27
  |
8 |     for (ref k, ref v) in *x {
  |                           ^^ cannot move out of borrowed content

Shouldn't it be possible to take a read-only iterator over a borrowed collection?

@alexcrichton
Copy link
Member

@zackw oh I think you'd take just I instead of &I

@zackw
Copy link
Contributor Author

zackw commented Jan 13, 2017

@alexcrichton It's more subtle than that. If we can't make this work for both &[(&str, &str)] and HashMap<&str, &str> then I don't think it buys us much of anything. But...

use std::collections::HashMap;

fn test<I, K, V>(x: I)
    where I: IntoIterator<Item=(K, V)>,
          K: AsRef<str>, V: AsRef<str>
{
    for (ref k, ref v) in x {
        println!("{}: {}", k.as_ref(), v.as_ref());
    }
}

fn main() {
    let vc = vec![
        ("a", "foo"),
        ("b", "bar"),
        ("c", "baz")
    ];
    test(&vc);

    let map: HashMap<&str, &str> = [
        ("d", "blurf"),
        ("e", "quux"),
        ("f", "xyzzy")
    ].iter().cloned().collect();
    test(&map);
}

fails to compile with

error[E0271]: type mismatch resolving `<&std::vec::Vec<(&str, &str)> 
    as std::iter::IntoIterator>::Item == (_, _)`
  --> test.rs:18:5
   |
18 |     test(&vc);
   |     ^^^^ expected reference, found tuple
   |
   = note: expected type `&(&str, &str)`
   = note:    found type `(_, _)`
   = note: required by `test`

I asked about this on Stack Overflow and was told that an adaptor trait is required. I'm hesitant to make up a one-off adaptor trait for a standard library function, though; it seems like if we're going to go that way, something like E_net4's KeyValue trait should be designed and given as many impl's as possible... and I feel like we're headed deep into the land of mission creep here. 😟

@alexcrichton
Copy link
Member

Oh right yeah that's a very good point that slices by default yield references, so they wouldn't work here. Ok I think we can scratch that idea then and we should stick with slices, sorry for the runaround!

@zackw
Copy link
Contributor Author

zackw commented Jan 14, 2017

Thinking about it some more, I can make the above program work if I change it like so...

    let vc = vec![
        ("a", "foo"),
        ("b", "bar"),
        ("c", "baz")
    ];
-   test(&vc);
+   test(vc.iter().map(|&(ref k, ref v)| (k, v)));

Abstractly, having this API expect the Item type you get from HashMap::iter() seems like the most natural thing, and I can also feed it env::vars() directly. I only wanted &[(&str, &str)] to work in the first place because I had a program that did

let mut child_env: Vec<(String, String)> =
    env::vars().filter(|&(ref k, _)|
        k == "TERM" || k == "TZ" || k == "LANG" || k.starts_with("LC_")
    ).collect();

and I could perfectly well make that child_env: HashMap<String, String> instead.

So maybe we should go ahead with IntoIterator after all. What do you think?

@alexcrichton
Copy link
Member

Yeah it's true that you can still go from an iterator of a references-to-tuples to an iterator of tuples relatively easy, but it's definitely not the most ergonomic. I'd be fine either way in terms of this API, I think the use cases should just drive it (e.g. does it come from a vec or hash map more often?

 * Command::envs() now takes anything that is IntoIterator<Item=(K, V)>
   where both K and V are AsRef<OsStr>.
 * Since we're not 100% sure that's the right signature, envs() is
   now marked unstable.  (You can use envs() with HashMap<str, str> but
   not Vec<(str, str)>, for instance.)
 * Update the test to match.

 * By analogy, args() now takes any IntoIterator<Item=S>, S: AsRef<OsStr>.
   This should be uncontroversial.
@zackw
Copy link
Contributor Author

zackw commented Jan 21, 2017

I think the use cases should just drive it (e.g. does it come from a vec or hash map more often?

I don't know the answer to that question (and I'm not sure anyone does, yet); the thing that I wanted it for doesn't particularly care.

The revision I just pushed uses the version that works with maps but not vec-of-tuples, and marks it unstable. It also includes the generalization of args() to iterators, which I hope can be done without destabilizing args() - unlike envs() we don't have two subtly different Item signatures to choose from, so it should Just Work with more stuff than it used to.

@BurntSushi
Copy link
Member

I think I'm OK with an API like this.

Should the generalization of args get a crater run? These types of changes have a habit of introducing breakage because of inference failures, although I don't know how likely those are in this case.

@aturon
Copy link
Member

aturon commented Jan 23, 2017

@BurntSushi Yeah, it's probably a good idea.

@brson
Copy link
Contributor

brson commented Jan 23, 2017

I've started crater

@brson
Copy link
Contributor

brson commented Jan 24, 2017

Toolchains built, testing crates.

@brson
Copy link
Contributor

brson commented Jan 25, 2017

Crater says.

@zackw
Copy link
Contributor Author

zackw commented Jan 25, 2017

Five of the six failures appear to all have the same basic cause: calling args with an &&[stringy] used to work, but doesn't with the changed signature. The lsio failure is one of the simplest: this code used to compile and now doesn't...

pub fn run_args(cmd: &str, args: &[String], shell: bool) -> Result<(Output)> {
    let output;

    if args.len() > 0 {
        if !shell {
            output = try!(Command::new(cmd).args(&args).output());
        } else {
    ...

The & in &args here is unnecessary, but an easy mistake to make. I would abstractly be okay with declaring this code buggy.

skia-sys' build system is too convoluted for the problem to be obvious, but if

mkdir: cannot create directory '/home/crate/target/debug/build/skia-sys-e80b95b369ab0855/out/src/ports': No such file or directory
make: *** [/home/crate/target/debug/build/skia-sys-e80b95b369ab0855/out/src/ports/SkAtomics_sync.h] Error 1

is the actual primary failure (which I'm not certain of) then I don't see how it can be caused by this change.

@aturon
Copy link
Member

aturon commented Jan 25, 2017

@zackw Thanks for the analysis!

That fallout doesn't seem too bad (and this kind of change is definitely permitted under our semver guidelines). That said, it'd be good to try to make PRs against these repos before landing the change.

Otherwise, I'm 👍 on going forward. @alexcrichton?

@alexcrichton
Copy link
Member

Sounds good to me!

@aturon
Copy link
Member

aturon commented Jan 25, 2017

@bors: r+

Thanks much, @zackw! If you're up for making PRs against the root regression repos from crater, that's always appreciated (but not required).

@bors
Copy link
Collaborator

bors commented Jan 25, 2017

📌 Commit 2580950 has been approved by aturon

@zackw
Copy link
Contributor Author

zackw commented Jan 25, 2017

If you're up for making PRs against the root regression repos from crater

I'm sorry, I don't have time to do that this week.

@bors
Copy link
Collaborator

bors commented Jan 25, 2017

⌛ Testing commit 2580950 with merge 94d4589...

bors added a commit that referenced this pull request Jan 25, 2017
Add std::process::Command::envs()

`Command::envs()` adds a vector of key-value pairs to the child
process environment all at once.  Suggested in #38526.

This is not fully baked and frankly I'm not sure it even _works_, but I need some help finishing it up, and this is the simplest way to show you what I've got.  The problems I know exist and don't know how to solve, from most to least important, are:

* [ ] I don't know if the type signature of the new function is correct.
* [x] The new test might not be getting run.  I didn't see it go by in the output of `x.py test src/libstd --stage 1`.
* [x] The tidy check says ``process.rs:402: different `since` than before`` which I don't know what it means.

r? @brson
@bors
Copy link
Collaborator

bors commented Jan 25, 2017

☀️ Test successful - status-appveyor, status-travis
Approved by: aturon
Pushing 94d4589 to master...

@bors bors merged commit 2580950 into rust-lang:master Jan 25, 2017
@Ms2ger
Copy link
Contributor

Ms2ger commented Jan 27, 2017

ring is fixed in briansmith/ring#430

@rustonaut
Copy link

just as a side note, the example in the documentation is not displayed as code block for some reason (instead as text with three ` at begin and end... At last with the documentation you get shipped with rustup/nightly.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
T-libs-api Relevant to the library API team, which will review and decide on the PR/issue.
Projects
None yet
Development

Successfully merging this pull request may close these issues.