Merge pull request #7749 from catamorphism/rustpkg-list-uninstall

rustpkg: Implement `uninstall` and `list` commands
This commit is contained in:
Daniel Micay 2013-07-13 19:18:22 -07:00
commit a1f4843895
11 changed files with 226 additions and 34 deletions

View file

@ -32,3 +32,7 @@ condition! {
condition! {
bad_pkg_id: (super::Path, ~str) -> super::PkgId;
}
condition! {
no_rust_path: (~str) -> super::Path;
}

View file

@ -0,0 +1,40 @@
// Copyright 2013 The Rust Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
// Listing installed packages
use path_util::*;
use std::os;
pub fn list_installed_packages(f: &fn(&PkgId) -> bool) -> bool {
let workspaces = rust_path();
for workspaces.iter().advance |p| {
let binfiles = os::list_dir(&p.push("bin"));
for binfiles.iter().advance() |exec| {
f(&PkgId::new(*exec));
}
let libfiles = os::list_dir(&p.push("lib"));
for libfiles.iter().advance() |lib| {
f(&PkgId::new(*lib));
}
}
true
}
pub fn package_is_installed(p: &PkgId) -> bool {
let mut is_installed = false;
do list_installed_packages() |installed| {
if installed == p {
is_installed = true;
}
false
};
is_installed
}

View file

@ -30,6 +30,15 @@ pub struct PkgId {
version: Version
}
impl Eq for PkgId {
fn eq(&self, p: &PkgId) -> bool {
*p.local_path == *self.local_path && p.version == self.version
}
fn ne(&self, p: &PkgId) -> bool {
!(self.eq(p))
}
}
impl PkgId {
pub fn new(s: &str) -> PkgId {
use conditions::bad_pkg_id::cond;

View file

@ -33,13 +33,18 @@ static PATH_ENTRY_SEPARATOR: &'static str = ";";
#[cfg(not(windows))]
static PATH_ENTRY_SEPARATOR: &'static str = ":";
/// Returns RUST_PATH as a string, without default paths added
pub fn get_rust_path() -> Option<~str> {
os::getenv("RUST_PATH")
}
/// Returns the value of RUST_PATH, as a list
/// of Paths. Includes default entries for, if they exist:
/// $HOME/.rust
/// DIR/.rust for any DIR that's the current working directory
/// or an ancestor of it
pub fn rust_path() -> ~[Path] {
let mut env_rust_path: ~[Path] = match os::getenv("RUST_PATH") {
let mut env_rust_path: ~[Path] = match get_rust_path() {
Some(env_path) => {
let env_path_components: ~[&str] =
env_path.split_str_iter(PATH_ENTRY_SEPARATOR).collect();
@ -378,3 +383,23 @@ pub fn mk_output_path(what: OutputType, where: Target,
debug!("mk_output_path: returning %s", output_path.to_str());
output_path
}
/// Removes files for the package `pkgid`, assuming it's installed in workspace `workspace`
pub fn uninstall_package_from(workspace: &Path, pkgid: &PkgId) {
let mut did_something = false;
let installed_bin = target_executable_in_workspace(pkgid, workspace);
if os::path_exists(&installed_bin) {
os::remove_file(&installed_bin);
did_something = true;
}
let installed_lib = target_library_in_workspace(pkgid, workspace);
if os::path_exists(&installed_lib) {
os::remove_file(&installed_lib);
did_something = true;
}
if !did_something {
warn(fmt!("Warning: there don't seem to be any files for %s installed in %s",
pkgid.to_str(), workspace.to_str()));
}
}

View file

@ -50,6 +50,7 @@ pub mod api;
mod conditions;
mod context;
mod crate;
mod installed_packages;
mod messages;
mod package_id;
mod package_path;
@ -248,6 +249,14 @@ impl CtxMethods for Ctx {
}
}
}
"list" => {
io::println("Installed packages:");
for installed_packages::list_installed_packages |pkg_id| {
io::println(fmt!("%s-%s",
pkg_id.local_path.to_str(),
pkg_id.version.to_str()));
}
}
"prefer" => {
if args.len() < 1 {
return usage::uninstall();
@ -263,11 +272,24 @@ impl CtxMethods for Ctx {
return usage::uninstall();
}
self.uninstall(args[0], None);
let pkgid = PkgId::new(args[0]);
if !installed_packages::package_is_installed(&pkgid) {
warn(fmt!("Package %s doesn't seem to be installed! Doing nothing.", args[0]));
return;
}
else {
let rp = rust_path();
assert!(!rp.is_empty());
for each_pkg_parent_workspace(&pkgid) |workspace| {
path_util::uninstall_package_from(workspace, &pkgid);
note(fmt!("Uninstalled package %s (was installed in %s)",
pkgid.to_str(), workspace.to_str()));
}
}
}
"unprefer" => {
if args.len() < 1 {
return usage::uninstall();
return usage::unprefer();
}
self.unprefer(args[0], None);
@ -447,6 +469,7 @@ pub fn main() {
~"do" => usage::do_cmd(),
~"info" => usage::info(),
~"install" => usage::install(),
~"list" => usage::list(),
~"prefer" => usage::prefer(),
~"test" => usage::test(),
~"uninstall" => usage::uninstall(),

View file

@ -12,9 +12,10 @@
use context::Ctx;
use std::hashmap::HashMap;
use std::{io, libc, os, result, run, str, vec};
use std::{io, libc, os, result, run, str};
use extra::tempfile::mkdtemp;
use std::run::ProcessOutput;
use installed_packages::list_installed_packages;
use package_path::*;
use package_id::{PkgId};
use package_source::*;
@ -128,20 +129,27 @@ fn test_sysroot() -> Path {
self_path.pop()
}
fn command_line_test(args: &[~str], cwd: &Path) -> ProcessOutput {
command_line_test_with_env(args, cwd, None)
}
/// Runs `rustpkg` (based on the directory that this executable was
/// invoked from) with the given arguments, in the given working directory.
/// Returns the process's output.
fn command_line_test(args: &[~str], cwd: &Path) -> ProcessOutput {
fn command_line_test_with_env(args: &[~str], cwd: &Path, env: Option<~[(~str, ~str)]>)
-> ProcessOutput {
let cmd = test_sysroot().push("bin").push("rustpkg").to_str();
let cwd = normalize(RemotePath(copy *cwd));
debug!("About to run command: %? %? in %s", cmd, args, cwd.to_str());
assert!(os::path_is_dir(&*cwd));
let mut prog = run::Process::new(cmd, args, run::ProcessOptions { env: None,
dir: Some(&*cwd),
in_fd: None,
out_fd: None,
err_fd: None
});
let cwd = cwd.clone();
let mut prog = run::Process::new(cmd, args, run::ProcessOptions {
env: env.map(|v| v.slice(0, v.len())),
dir: Some(&cwd),
in_fd: None,
out_fd: None,
err_fd: None
});
let output = prog.finish_with_output();
debug!("Output from command %s with args %? was %s {%s}[%?]",
cmd, args, str::from_bytes(output.output),
@ -252,6 +260,16 @@ fn command_line_test_output(args: &[~str]) -> ~[~str] {
result
}
fn command_line_test_output_with_env(args: &[~str], env: ~[(~str, ~str)]) -> ~[~str] {
let mut result = ~[];
let p_output = command_line_test_with_env(args, &os::getcwd(), Some(env));
let test_output = str::from_bytes(p_output.output);
for test_output.split_iter('\n').advance |s| {
result.push(s.to_owned());
}
result
}
// assumes short_name and local_path are one and the same -- I should fix
fn lib_output_file_name(workspace: &Path, parent: &str, short_name: &str) -> Path {
debug!("lib_output_file_name: given %s and parent %s and short name %s",
@ -476,8 +494,9 @@ fn test_package_version() {
push("test_pkg_version")));
}
// FIXME #7006: Fails on linux/mac for some reason
#[test] #[ignore]
// FIXME #7006: Fails on linux for some reason
#[test]
#[ignore]
fn test_package_request_version() {
let temp_pkg_id = PkgId::new("github.com/catamorphism/test_pkg_version#0.3");
let temp = mk_empty_workspace(&LocalPath(Path("test_pkg_version")), &ExactRevision(~"0.3"));
@ -613,7 +632,33 @@ fn rust_path_parse() {
}
#[test]
#[ignore(reason = "Package database not yet implemented")]
fn test_list() {
let foo = PkgId::new("foo");
let dir = mkdtemp(&os::tmpdir(), "test_list").expect("test_list failed");
create_local_package_in(&foo, &dir);
let bar = PkgId::new("bar");
create_local_package_in(&bar, &dir);
let quux = PkgId::new("quux");
create_local_package_in(&quux, &dir);
command_line_test([~"install", ~"foo"], &dir);
let env_arg = ~[(~"RUST_PATH", dir.to_str())];
let list_output = command_line_test_output_with_env([~"list"], env_arg.clone());
assert!(list_output.iter().any(|x| x.starts_with("foo-")));
command_line_test([~"install", ~"bar"], &dir);
let list_output = command_line_test_output_with_env([~"list"], env_arg.clone());
assert!(list_output.iter().any(|x| x.starts_with("foo-")));
assert!(list_output.iter().any(|x| x.starts_with("bar-")));
command_line_test([~"install", ~"quux"], &dir);
let list_output = command_line_test_output_with_env([~"list"], env_arg);
assert!(list_output.iter().any(|x| x.starts_with("foo-")));
assert!(list_output.iter().any(|x| x.starts_with("bar-")));
assert!(list_output.iter().any(|x| x.starts_with("quux-")));
}
#[test]
fn install_remove() {
let foo = PkgId::new("foo");
let bar = PkgId::new("bar");
@ -622,18 +667,43 @@ fn install_remove() {
create_local_package_in(&foo, &dir);
create_local_package_in(&bar, &dir);
create_local_package_in(&quux, &dir);
let rust_path_to_use = ~[(~"RUST_PATH", dir.to_str())];
command_line_test([~"install", ~"foo"], &dir);
command_line_test([~"install", ~"bar"], &dir);
command_line_test([~"install", ~"quux"], &dir);
let list_output = command_line_test_output([~"list"]);
assert!(list_output.iter().any(|x| x == &~"foo"));
assert!(list_output.iter().any(|x| x == &~"bar"));
assert!(list_output.iter().any(|x| x == &~"quux"));
command_line_test([~"remove", ~"foo"], &dir);
let list_output = command_line_test_output([~"list"]);
assert!(!list_output.iter().any(|x| x == &~"foo"));
assert!(list_output.iter().any(|x| x == &~"bar"));
assert!(list_output.iter().any(|x| x == &~"quux"));
let list_output = command_line_test_output_with_env([~"list"], rust_path_to_use.clone());
assert!(list_output.iter().any(|x| x.starts_with("foo")));
assert!(list_output.iter().any(|x| x.starts_with("bar")));
assert!(list_output.iter().any(|x| x.starts_with("quux")));
command_line_test([~"uninstall", ~"foo"], &dir);
let list_output = command_line_test_output_with_env([~"list"], rust_path_to_use.clone());
assert!(!list_output.iter().any(|x| x.starts_with("foo")));
assert!(list_output.iter().any(|x| x.starts_with("bar")));
assert!(list_output.iter().any(|x| x.starts_with("quux")));
}
#[test]
fn install_check_duplicates() {
// should check that we don't install two packages with the same full name *and* version
// ("Is already installed -- doing nothing")
// check invariant that there are no dups in the pkg database
let dir = mkdtemp(&os::tmpdir(), "install_remove").expect("install_remove");
let foo = PkgId::new("foo");
create_local_package_in(&foo, &dir);
command_line_test([~"install", ~"foo"], &dir);
command_line_test([~"install", ~"foo"], &dir);
let mut contents = ~[];
let check_dups = |p: &PkgId| {
if contents.contains(p) {
fail!("package database contains duplicate ID");
}
else {
contents.push(copy *p);
}
false
};
list_installed_packages(check_dups);
}
#[test]

View file

@ -8,4 +8,4 @@
// option. This file may not be copied, modified, or distributed
// except according to those terms.
fn f() -> int { 42 }
pub fn f() -> int { 42 }

View file

@ -19,4 +19,10 @@ The test runner should check that, after `rustpkg install install-paths`
* install-paths/build/install_pathsbench exists and is an executable
*/
fn main() {}
use lib::f;
mod lib;
fn main() {
f();
}

View file

@ -14,7 +14,7 @@ pub fn general() {
io::println("Usage: rustpkg [options] <cmd> [args..]
Where <cmd> is one of:
build, clean, do, info, install, prefer, test, uninstall, unprefer
build, clean, do, info, install, list, prefer, test, uninstall, unprefer
Options:
@ -55,6 +55,12 @@ Options:
-j, --json Output the result as JSON");
}
pub fn list() {
io::println("rustpkg list
List all installed packages.");
}
pub fn install() {
io::println("rustpkg [options..] install [url] [target]

View file

@ -28,8 +28,11 @@ use search::find_library_in_search_path;
use path_util::target_library_in_workspace;
pub use target::{OutputType, Main, Lib, Bench, Test};
// It would be nice to have the list of commands in just one place -- for example,
// you could update the match in rustpkg.rc but forget to update this list. I think
// that should be fixed.
static COMMANDS: &'static [&'static str] =
&["build", "clean", "do", "info", "install", "prefer", "test", "uninstall",
&["build", "clean", "do", "info", "install", "list", "prefer", "test", "uninstall",
"unprefer"];
@ -152,12 +155,6 @@ pub fn ready_crate(sess: session::Session,
@fold.fold_crate(crate)
}
pub fn need_dir(s: &Path) {
if !os::path_is_dir(s) && !os::make_dir(s, 493_i32) {
fail!("can't create dir: %s", s.to_str());
}
}
// FIXME (#4432): Use workcache to only compile when needed
pub fn compile_input(ctxt: &Ctx,
pkg_id: &PkgId,

View file

@ -18,13 +18,25 @@ use std::{char, os, result, run, str};
use package_path::RemotePath;
use extra::tempfile::mkdtemp;
#[deriving(Eq)]
pub enum Version {
ExactRevision(~str), // Should look like a m.n.(...).x
SemanticVersion(semver::Version),
NoVersion // user didn't specify a version -- prints as 0.1
}
impl Eq for Version {
fn eq(&self, other: &Version) -> bool {
match (self, other) {
(&ExactRevision(ref s1), &ExactRevision(ref s2)) => *s1 == *s2,
(&SemanticVersion(ref v1), &SemanticVersion(ref v2)) => *v1 == *v2,
(&NoVersion, _) => true,
_ => false
}
}
fn ne(&self, other: &Version) -> bool {
!self.eq(other)
}
}
impl Ord for Version {
fn lt(&self, other: &Version) -> bool {