Skip to content
This repository has been archived by the owner on May 20, 2020. It is now read-only.

WIP: Refactor cargo commands #182

Closed
wants to merge 4 commits into from

Conversation

mackwic
Copy link

@mackwic mackwic commented Oct 2, 2017

I push this PR now for easier review + early discussions.

Todo:

  • extract the command building into their own testable module
  • create a Command bridge that we can inspect before calling the true Command builder
  • there is probably an opportunity to have a utils module, used by the cargo module
  • remove dead code
  • ?

EDIT: Oh I forgot to add my new nick I got from the RustFest:

Sincerly,
The Test Dude

- Add a CommandBridge which is a testable command builder
- Move Target struct in its own module
- Add cargo::commands module which contains the logic of building cargo
  commands
@mackwic
Copy link
Author

mackwic commented Oct 2, 2017

I am currently is quite puzzled by the compilation error if I uncomment the line 35 of cargo/commands.rs. I'll find a way to fix that quickly

Copy link
Contributor

@mgattozzi mgattozzi left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I always like a good refactor. I left some comments but this is definitely a good stab at making the commands more flexible. I'd also like to hear what @steveklabnik and @euclio think as well.

key_str.push(key.as_ref());
value_str.push(value.as_ref());

self.env.insert(key_str, value_str);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can just use to_os_string

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ooh, I missed that. I am not familiar with the OsStr(ing)? interface

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

People tend to miss it. The Standard Library docs are great! Usually I'll take a look before using something to see what's available, especially if I tend to do common patterns like conversions!

pub fn arg<S: AsRef<OsStr>>(&mut self, arg: S) -> &mut Self {
let mut os_string = OsString::new();
os_string.push(arg);
self.args.push(os_string);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Again to_os_string would be better. Since you keep turning them into OsStrings why not just accept something like Into<OsString> and calling into?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You are probably right. I was mirroring the interface of std::process::Command without thinking of making my life easier.

I'll test that.

pub fn new(name: &str) -> Self {
CommandBridge {
name: String::from(name),
.. Default::default()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is such a neat trick!

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah ! Now that you say it, yes it is. Very useful to replace factories in tests: implement Default and only specify the field you want to test.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll be leveraging this in some of my own stuff now!

self
}

pub fn stderr<S: Into<Stdio>>(&mut self, stderr: S) -> &mut Self {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this supposed to be Into<Stderr>?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

honestly I just mirrored the std::process::Command interface. I imagine Stdio is a reference to the libc streams from stdio.h. I didn't took the time to look for Into<Stdio> implementors as I need to stay compatible with std::process::Command anyway.

If you have any suggestion, I'll take it

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I looked at the docs. I think it's Stdio as the type so this is fine, but this also means you can put an StdIn where there should be an StdErr.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes this is legal. In this case on Linux it's equivalent to passing it /dev/null as stdin is often closed for writing (not always though, in this case it's a blocking read for eternity)

}

pub fn to_command(self) -> Command {
let mut command = Command::new(self.name);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think name is the best choice to describe this. I think command would be better but that looks kind of weird here.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

good point. command_name it is

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Awesome thanks! :D

cmd.stderr(stderr);
// assert
// FIXME: is there is really no way to compare an Stdio instance ??
//assert_eq!(stderr as *const (), cmd.stderr.unwrap() as *const ())
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can turn them into RawFd which is basically just a c_int and compare them.
https://doc.rust-lang.org/std/os/unix/io/type.RawFd.html

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since it implements this it also implies that it implements the into version of it.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh ! Good catch ! Thank you ! :)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're welcome :D

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

that's in std::os::unix though, what about windows?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I dug into the libstd source and couldn't find anything windows specific. This won't work on appveyor.


pub fn retrieve_metadata(manifest_path: &Path) -> CommandBridge {
let mut command = CommandBridge::new("cargo");
command.arg("metadata")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd prefer it to be a builder pattern stylistically so you can do:

CommandBridge::new("cargo")
    .arg("metadata")
    .arg("--manifest-path")
    .arg(manifest_path)
    .arg("--no-deps")
    .arg("--format-version")
    .arg("1")

If you change the function arg to arg(self) -> Self this enables that pattern. Builder patterns are great and this is essentially what you're doing here! :D

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, it would be more canonic. I'll change

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

:D

command.arg("--verbose");
}

//match target.kind {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did you take this out?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

still in the WIP :)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah got it cool

.stdout(Stdio::null());

if is_verbose {
command.arg("--verbose");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmmm this would cause command to be consumed if you did what I mentioned. You'd have to do a let command = command.arg("--verbose");` here

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

assert!(res.is_err())
}
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this whole file new because it got moved?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nope, the file is new. I extracted the logic of command building here and I want to unit test it

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Even better. I'm all for more tests. Looking forward to seeing the end result :D

/// This temporary struct is used to inspect the generated command before calling the process::Command
/// builder, as we can't inspect its structure and make assertions on it
#[derive(Debug, Default)]
pub struct CommandBridge {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just another use case for rust-lang/rust#44434 😞

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

:( If only. Might need an RFC?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, this is much needed. And std::{fs, io} stubbing. So much of that

@mgattozzi
Copy link
Contributor

Hmmm tests are failing but it doesn't seem to be your fault (I think). These changes look great! :D

@steveklabnik
Copy link
Owner

I like this idea in general, but yeah, the tests seem to be failing. do they pass locally? Given that both travis and appveyor fail with the same message, I suspect that it's something in here...

@mackwic
Copy link
Author

mackwic commented Oct 22, 2017

Sorry, not much time to work on it recently. I am still up to finish this PR, but I would understand if you close it. I can always work on it offline and re-submit it later, rebased from master.

@steveklabnik
Copy link
Owner

It happens! I go for a long time without having time too.

I am going to give this a close though, if you or anyone else wants to update it, please submit a new PR! Thanks :)

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants