From 4d4cabff9ede49ae3642b05c4cfb023a0a9222b2 Mon Sep 17 00:00:00 2001 From: Tim Chevalier Date: Thu, 2 May 2013 13:09:28 -0700 Subject: [PATCH] rustpkg: Implement install command The install command should work now, though it only installs in-place (anything else has to wait until I implement RUST_PATH). Also including: core: Add remove_directory_recursive, change copy_file Make copy_file preserve permissions, and add a remove_directory_recursive function. --- src/libcore/os.rs | 37 ++++++++++ src/librustpkg/conditions.rs | 10 ++- src/librustpkg/path_util.rs | 130 +++++++++++++++++++++++++++++++--- src/librustpkg/rustpkg.rc | 132 +++++++++++++++++++++-------------- src/librustpkg/tests.rs | 71 +++++++++++++------ src/librustpkg/util.rs | 7 +- src/libstd/tempfile.rs | 26 ++++++- 7 files changed, 328 insertions(+), 85 deletions(-) diff --git a/src/libcore/os.rs b/src/libcore/os.rs index c4b03d76cefe..7b68e6597a17 100644 --- a/src/libcore/os.rs +++ b/src/libcore/os.rs @@ -772,6 +772,28 @@ pub fn list_dir_path(p: &Path) -> ~[~Path] { list_dir(p).map(|f| ~p.push(*f)) } +/// Removes a directory at the specified path, after removing +/// all its contents. Use carefully! +pub fn remove_dir_recursive(p: &Path) -> bool { + let mut error_happened = false; + for walk_dir(p) |inner| { + if !error_happened { + if path_is_dir(inner) { + if !remove_dir_recursive(inner) { + error_happened = true; + } + } + else { + if !remove_file(inner) { + error_happened = true; + } + } + } + }; + // Directory should now be empty + !error_happened && remove_dir(p) +} + /// Removes a directory at the specified path pub fn remove_dir(p: &Path) -> bool { return rmdir(p); @@ -877,6 +899,10 @@ pub fn copy_file(from: &Path, to: &Path) -> bool { if istream as uint == 0u { return false; } + // Preserve permissions + let from_mode = from.get_mode().expect("copy_file: couldn't get permissions \ + for source file"); + let ostream = do as_c_charp(to.to_str()) |top| { do as_c_charp("w+b") |modebuf| { libc::fopen(top, modebuf) @@ -908,6 +934,15 @@ pub fn copy_file(from: &Path, to: &Path) -> bool { } fclose(istream); fclose(ostream); + + // Give the new file the old file's permissions + unsafe { + if do str::as_c_str(to.to_str()) |to_buf| { + libc::chmod(to_buf, from_mode as mode_t) + } != 0 { + return false; // should be a condition... + } + } return ok; } } @@ -1594,6 +1629,7 @@ mod tests { == buf.len() as size_t)) } assert!((libc::fclose(ostream) == (0u as c_int))); + let in_mode = in.get_mode(); let rs = os::copy_file(&in, &out); if (!os::path_exists(&in)) { fail!(fmt!("%s doesn't exist", in.to_str())); @@ -1601,6 +1637,7 @@ mod tests { assert!((rs)); let rslt = run::run_program(~"diff", ~[in.to_str(), out.to_str()]); assert!((rslt == 0)); + assert!(out.get_mode() == in_mode); assert!((remove_file(&in))); assert!((remove_file(&out))); } diff --git a/src/librustpkg/conditions.rs b/src/librustpkg/conditions.rs index 35e70af7914c..5b19a3bd6604 100644 --- a/src/librustpkg/conditions.rs +++ b/src/librustpkg/conditions.rs @@ -18,5 +18,13 @@ condition! { } condition! { - nonexistent_package: (super::PkgId, ~str) -> super::Path; + nonexistent_package: (super::PkgId, ~str) -> (); +} + +condition! { + copy_failed: (super::Path, super::Path) -> (); +} + +condition! { + missing_pkg_files: (super::PkgId) -> (); } diff --git a/src/librustpkg/path_util.rs b/src/librustpkg/path_util.rs index 0490f066f0be..161cb75e9e50 100644 --- a/src/librustpkg/path_util.rs +++ b/src/librustpkg/path_util.rs @@ -12,6 +12,7 @@ use util::PkgId; use core::libc::consts::os::posix88::{S_IRUSR, S_IWUSR, S_IXUSR}; +use core::os::mkdir_recursive; #[deriving(Eq)] pub enum OutputType { Main, Lib, Bench, Test } @@ -23,7 +24,7 @@ pub fn rust_path() -> ~[Path] { ~[Path(".")] } -static u_rwx: i32 = (S_IRUSR | S_IWUSR | S_IXUSR) as i32; +pub static u_rwx: i32 = (S_IRUSR | S_IWUSR | S_IXUSR) as i32; /// Creates a directory that is readable, writeable, /// and executable by the user. Returns true iff creation @@ -70,34 +71,137 @@ pub fn pkgid_src_in_workspace(pkgid: PkgId, workspace: &Path) -> Path { result.push(pkgid.path.to_str()) } +/// Figure out what the executable name for in 's build +/// directory is, and if the file exists, return it. +pub fn built_executable_in_workspace(pkgid: PkgId, workspace: &Path) -> Option { + let mut result = workspace.push("build"); + result = result.push_rel(&pkgid.path); + // should use a target-specific subdirectory + result = mk_output_path(Main, fmt!("%s-%s", pkgid.path.to_str(), pkgid.version.to_str()), + result); + debug!("built_executable_in_workspace: checking whether %s exists", + result.to_str()); + if os::path_exists(&result) { + Some(result) + } + else { + None + } +} + +/// Figure out what the library name for in 's build +/// directory is, and if the file exists, return it. +pub fn built_library_in_workspace(pkgid: PkgId, workspace: &Path) -> Option { + let mut result = workspace.push("build"); + result = result.push_rel(&pkgid.path); + // should use a target-specific subdirectory + result = mk_output_path(Lib, pkgid.path.to_str(), result); + debug!("built_library_in_workspace: checking whether %s exists", + result.to_str()); + + // We don't know what the hash is, so we have to search through the directory + // contents + let dir_contents = os::list_dir(&result.pop()); + debug!("dir has %? entries", dir_contents.len()); + + // n.b. This code assumes the pkgid's path only has one element + let lib_prefix = fmt!("%s%s", os::consts::DLL_PREFIX, pkgid.path.to_str()); + let lib_filetype = fmt!("%s%s", pkgid.version.to_str(), os::consts::DLL_SUFFIX); + + debug!("lib_prefix = %s and lib_filetype = %s", lib_prefix, lib_filetype); + + let mut result_filename = None; + for dir_contents.each |&p| { + let mut which = 0; + let mut hash = None; + // Find a filename that matches the pattern: (lib_prefix)-hash-(version)(lib_suffix) + // and remember what the hash was + for p.each_split_char('-') |piece| { + debug!("a piece = %s", piece); + if which == 0 && piece != lib_prefix { + break; + } + else if which == 0 { + which += 1; + } + else if which == 1 { + hash = Some(piece.to_owned()); + which += 1; + } + else if which == 2 && piece != lib_filetype { + hash = None; + break; + } + else if which == 2 { + break; + } + else { + // something went wrong + hash = None; + break; + } + } + if hash.is_some() { + result_filename = Some(p); + break; + } + } + + // Return the filename that matches, which we now know exists + // (if result_filename != None) + debug!("result_filename = %?", result_filename); + match result_filename { + None => None, + Some(result_filename) => { + let result_filename = result.with_filename(result_filename); + debug!("result_filename = %s", result_filename.to_str()); + Some(result_filename) + } + } +} + /// Returns the executable that would be installed for /// in +/// As a side effect, creates the bin-dir if it doesn't exist pub fn target_executable_in_workspace(pkgid: PkgId, workspace: &Path) -> Path { - let result = workspace.push("bin"); - // should use a target-specific subdirectory - mk_output_path(Main, pkgid.path.to_str(), result) + target_file_in_workspace(pkgid, workspace, Main) } /// Returns the executable that would be installed for /// in +/// As a side effect, creates the bin-dir if it doesn't exist pub fn target_library_in_workspace(pkgid: PkgId, workspace: &Path) -> Path { - let result = workspace.push("lib"); - mk_output_path(Lib, pkgid.path.to_str(), result) + target_file_in_workspace(pkgid, workspace, Lib) } /// Returns the test executable that would be installed for /// in pub fn target_test_in_workspace(pkgid: PkgId, workspace: &Path) -> Path { - let result = workspace.push("build"); - mk_output_path(Test, pkgid.path.to_str(), result) + target_file_in_workspace(pkgid, workspace, Test) } /// Returns the bench executable that would be installed for /// in pub fn target_bench_in_workspace(pkgid: PkgId, workspace: &Path) -> Path { - let result = workspace.push("build"); - mk_output_path(Bench, pkgid.path.to_str(), result) + target_file_in_workspace(pkgid, workspace, Bench) +} + +fn target_file_in_workspace(pkgid: PkgId, workspace: &Path, + what: OutputType) -> Path { + use conditions::bad_path::cond; + + let (subdir, create_dir) = match what { + Main => ("bin", true), Lib => ("lib", true), Test | Bench => ("build", false) + }; + let result = workspace.push(subdir); + if create_dir { + if !os::path_exists(&result) && !mkdir_recursive(&result, u_rwx) { + cond.raise((result, fmt!("I couldn't create the %s dir", subdir))); + } + } + mk_output_path(what, pkgid.path.to_str(), result) + } /// Return the directory for 's build artifacts in . @@ -123,7 +227,11 @@ pub fn mk_output_path(what: OutputType, short_name: ~str, dir: Path) -> Path { match what { Lib => dir.push(os::dll_filename(short_name)), _ => dir.push(fmt!("%s%s%s", short_name, - if what == Test { ~"test" } else { ~"" }, + match what { + Test => "test", + Bench => "bench", + _ => "" + } os::EXE_SUFFIX)) } } diff --git a/src/librustpkg/rustpkg.rc b/src/librustpkg/rustpkg.rc index a296f0ca32a4..cc74f464e0e8 100644 --- a/src/librustpkg/rustpkg.rc +++ b/src/librustpkg/rustpkg.rc @@ -34,6 +34,8 @@ use syntax::{ast, diagnostic}; use util::*; use path_util::normalize; use path_util::{build_pkg_id_in_workspace, pkgid_src_in_workspace}; +use path_util::{built_executable_in_workspace, built_library_in_workspace}; +use path_util::{target_executable_in_workspace, target_library_in_workspace}; use workspace::pkg_parent_workspaces; use rustc::driver::session::{lib_crate, bin_crate, crate_type}; use context::Ctx; @@ -188,49 +190,7 @@ impl Ctx { // argument let pkgid = PkgId::new(args[0]); for pkg_parent_workspaces(pkgid) |workspace| { - let src_dir = pkgid_src_in_workspace(pkgid, workspace); - let build_dir = build_pkg_id_in_workspace(pkgid, workspace); - debug!("Destination dir = %s", build_dir.to_str()); - - // Create the package source - let mut src = PkgSrc::new(&workspace.push("src"), &build_dir, &pkgid); - debug!("Package src = %?", src); - - // Is there custom build logic? If so, use it - let pkg_src_dir = src_dir; - let mut custom = false; - debug!("Package source directory = %s", pkg_src_dir.to_str()); - let cfgs = match src.package_script_option(&pkg_src_dir) { - Some(package_script_path) => { - let pscript = PkgScript::parse(package_script_path, - workspace, - pkgid); - // Limited right now -- we're only running the post_build - // hook and probably fail otherwise - // also post_build should be called pre_build - let (cfgs, hook_result) = pscript.run_custom(~"post_build"); - debug!("Command return code = %?", hook_result); - if hook_result != 0 { - fail!(fmt!("Error running custom build command")) - } - custom = true; - // otherwise, the package script succeeded - cfgs - } - None => { - debug!("No package script, continuing"); - ~[] - } - }; - - // If there was a package script, it should have finished - // the build already. Otherwise... - if !custom { - // Find crates inside the workspace - src.find_crates(); - // Build it! - src.build(&build_dir, cfgs); - } + self.build(workspace, pkgid); } } ~"clean" => { @@ -304,6 +264,53 @@ impl Ctx { fail!(~"`do` not yet implemented"); } + fn build(&self, workspace: &Path, pkgid: PkgId) { + let src_dir = pkgid_src_in_workspace(pkgid, workspace); + let build_dir = build_pkg_id_in_workspace(pkgid, workspace); + debug!("Destination dir = %s", build_dir.to_str()); + + // Create the package source + let mut src = PkgSrc::new(&workspace.push("src"), &build_dir, &pkgid); + debug!("Package src = %?", src); + + // Is there custom build logic? If so, use it + let pkg_src_dir = src_dir; + let mut custom = false; + debug!("Package source directory = %s", pkg_src_dir.to_str()); + let cfgs = match src.package_script_option(&pkg_src_dir) { + Some(package_script_path) => { + let pscript = PkgScript::parse(package_script_path, + workspace, + pkgid); + // Limited right now -- we're only running the post_build + // hook and probably fail otherwise + // also post_build should be called pre_build + let (cfgs, hook_result) = pscript.run_custom(~"post_build"); + debug!("Command return code = %?", hook_result); + if hook_result != 0 { + fail!(fmt!("Error running custom build command")) + } + custom = true; + // otherwise, the package script succeeded + cfgs + } + None => { + debug!("No package script, continuing"); + ~[] + } + }; + + // If there was a package script, it should have finished + // the build already. Otherwise... + if !custom { + // Find crates inside the workspace + src.find_crates(); + // Build it! + src.build(&build_dir, cfgs); + } + + } + fn clean(&self, workspace: &Path, id: PkgId) { // Could also support a custom build hook in the pkg // script for cleaning files rustpkg doesn't know about. @@ -325,9 +332,31 @@ impl Ctx { fail!(~"info not yet implemented"); } - fn install(&self, _workspace: &Path, _id: PkgId) { - // stub - fail!(~"install not yet implemented"); + fn install(&self, workspace: &Path, id: PkgId) { + use conditions::copy_failed::cond; + + // Should use RUST_PATH in the future. + // Also should use workcache to not build if not necessary. + self.build(workspace, id); + + // Now copy stuff into the install dirs + let maybe_executable = built_executable_in_workspace(id, workspace); + let maybe_library = built_library_in_workspace(id, workspace); + let target_exec = target_executable_in_workspace(id, workspace); + let target_lib = target_library_in_workspace(id, workspace); + + for maybe_executable.each |exec| { + debug!("Copying: %s -> %s", exec.to_str(), target_exec.to_str()); + if !os::copy_file(exec, &target_exec) { + cond.raise((*exec, target_exec)); + } + } + for maybe_library.each |lib| { + debug!("Copying: %s -> %s", lib.to_str(), target_lib.to_str()); + if !os::copy_file(lib, &target_lib) { + cond.raise((*lib, target_lib)); + } + } } fn fetch(&self, _dir: &Path, _url: ~str, _target: Option<~str>) { @@ -610,7 +639,7 @@ impl PkgSrc { fn check_dir(&self) -> Path { - use conditions::bad_path::cond; + use conditions::nonexistent_package::cond; debug!("Pushing onto root: %s | %s", self.id.path.to_str(), self.root.to_str()); @@ -620,12 +649,12 @@ impl PkgSrc { debug!("Checking dir: %s", dir.to_str()); if !os::path_exists(&dir) { - return cond.raise((dir, ~"missing package dir")); + cond.raise((self.id, ~"missing package dir")); } if !os::path_is_dir(&dir) { - return cond.raise((dir, ~"supplied path for package dir is a \ - non-directory")); + cond.raise((self.id, ~"supplied path for package dir is a \ + non-directory")); } dir @@ -680,6 +709,7 @@ impl PkgSrc { /// is no custom build logic fn find_crates(&mut self) { use PkgSrc::push_crate; + use conditions::missing_pkg_files::cond; let dir = self.check_dir(); let prefix = dir.components.len(); @@ -704,7 +734,7 @@ impl PkgSrc { util::note(~"Couldn't infer any crates to build.\n\ Try naming a crate `main.rs`, `lib.rs`, \ `test.rs`, or `bench.rs`."); - fail!(~"Failed to infer crates to build"); + cond.raise(self.id); } debug!("found %u libs, %u mains, %u tests, %u benchs", diff --git a/src/librustpkg/tests.rs b/src/librustpkg/tests.rs index bcee2992e5ab..f38fc88f7279 100644 --- a/src/librustpkg/tests.rs +++ b/src/librustpkg/tests.rs @@ -17,7 +17,8 @@ use std::tempfile::mkdtemp; use util::{PkgId, default_version}; use path_util::{target_executable_in_workspace, target_library_in_workspace, target_test_in_workspace, target_bench_in_workspace, - make_dir_rwx}; + make_dir_rwx, u_rwx}; +use core::os::mkdir_recursive; fn fake_ctxt() -> Ctx { Ctx { @@ -33,8 +34,27 @@ fn fake_pkg() -> PkgId { } } -fn mk_temp_workspace() -> Path { - mkdtemp(&os::tmpdir(), "test").expect("couldn't create temp dir") +fn writeFile(file_path: &Path, contents: ~str) { + let out: @io::Writer = + result::get(&io::file_writer(file_path, + ~[io::Create, io::Truncate])); + out.write_line(contents); +} + +fn mk_temp_workspace(short_name: &Path) -> Path { + let workspace = mkdtemp(&os::tmpdir(), "test").expect("couldn't create temp dir"); + let package_dir = workspace.push(~"src").push_rel(short_name); + assert!(mkdir_recursive(&package_dir, u_rwx)); + // Create main, lib, test, and bench files + writeFile(&package_dir.push(~"main.rs"), + ~"fn main() { let _x = (); }"); + writeFile(&package_dir.push(~"lib.rs"), + ~"pub fn f() { let _x = (); }"); + writeFile(&package_dir.push(~"test.rs"), + ~"#[test] pub fn f() { (); }"); + writeFile(&package_dir.push(~"bench.rs"), + ~"#[bench] pub fn f() { (); }"); + workspace } fn is_rwx(p: &Path) -> bool { @@ -42,11 +62,10 @@ fn is_rwx(p: &Path) -> bool { match p.get_mode() { None => return false, - Some(m) => { + Some(m) => ((m & S_IRUSR as uint) == S_IRUSR as uint && (m & S_IWUSR as uint) == S_IWUSR as uint && (m & S_IXUSR as uint) == S_IXUSR as uint) - } } } @@ -54,48 +73,60 @@ fn is_rwx(p: &Path) -> bool { fn test_make_dir_rwx() { let temp = &os::tmpdir(); let dir = temp.push(~"quux"); - let _ = os::remove_dir(&dir); + assert!(!os::path_exists(&dir) || + os::remove_dir_recursive(&dir)); + debug!("Trying to make %s", dir.to_str()); assert!(make_dir_rwx(&dir)); assert!(os::path_is_dir(&dir)); assert!(is_rwx(&dir)); - assert!(os::remove_dir(&dir)); + assert!(os::remove_dir_recursive(&dir)); } #[test] -#[ignore(reason = "install not yet implemented")] fn test_install_valid() { + use rustc::metadata::filesearch; + + let sysroot = filesearch::get_rustpkg_sysroot(); + debug!("sysroot = %s", sysroot.get().to_str()); let ctxt = fake_ctxt(); let temp_pkg_id = fake_pkg(); - let temp_workspace = mk_temp_workspace(); + let temp_workspace = mk_temp_workspace(&temp_pkg_id.path); // should have test, bench, lib, and main ctxt.install(&temp_workspace, temp_pkg_id); // Check that all files exist let exec = target_executable_in_workspace(temp_pkg_id, &temp_workspace); + debug!("exec = %s", exec.to_str()); assert!(os::path_exists(&exec)); assert!(is_rwx(&exec)); let lib = target_library_in_workspace(temp_pkg_id, &temp_workspace); + debug!("lib = %s", lib.to_str()); assert!(os::path_exists(&lib)); assert!(is_rwx(&lib)); // And that the test and bench executables aren't installed assert!(!os::path_exists(&target_test_in_workspace(temp_pkg_id, &temp_workspace))); - assert!(!os::path_exists(&target_bench_in_workspace(temp_pkg_id, &temp_workspace))); + let bench = target_bench_in_workspace(temp_pkg_id, &temp_workspace); + debug!("bench = %s", bench.to_str()); + assert!(!os::path_exists(&bench)); } #[test] -#[ignore(reason = "install not yet implemented")] fn test_install_invalid() { use conditions::nonexistent_package::cond; + use cond1 = conditions::missing_pkg_files::cond; let ctxt = fake_ctxt(); let pkgid = fake_pkg(); - let temp_workspace = mk_temp_workspace(); - let expected_path = Path(~"quux"); - let substituted: Path = do cond.trap(|_| { - expected_path + let temp_workspace = mkdtemp(&os::tmpdir(), "test").expect("couldn't create temp dir"); + let mut error_occurred = false; + let mut error1_occurred = false; + do cond1.trap(|_| { + error1_occurred = true; }).in { - ctxt.install(&temp_workspace, pkgid); - // ok - fail!(~"test_install_invalid failed, should have raised a condition"); - }; - assert!(substituted == expected_path); + do cond.trap(|_| { + error_occurred = true; + }).in { + ctxt.install(&temp_workspace, pkgid); + } + } + assert!(error_occurred && error1_occurred); } diff --git a/src/librustpkg/util.rs b/src/librustpkg/util.rs index 28198e59f86d..1b3d72bf6aa6 100644 --- a/src/librustpkg/util.rs +++ b/src/librustpkg/util.rs @@ -477,6 +477,8 @@ pub fn compile_input(sysroot: Option, let matches = getopts(~[~"-Z", ~"time-passes"] + if building_library { ~[~"--lib"] } + else if test { ~[~"--test"] } + // bench? else { ~[] } + flags + cfgs.flat_map(|&c| { ~[~"--cfg", c] }), @@ -540,9 +542,13 @@ pub fn compile_crate_from_input(input: driver::input, let (crate, _) = driver::compile_upto(sess, cfg, &input, driver::cu_parse, Some(outputs)); + debug!("About to inject link_meta info..."); // Inject the inferred link_meta info if it's not already there // (assumes that name and vers are the only linkage metas) let mut crate_to_use = crate; + + debug!("How many attrs? %?", attr::find_linkage_metas(crate.node.attrs).len()); + if attr::find_linkage_metas(crate.node.attrs).is_empty() { crate_to_use = add_attrs(*crate, ~[mk_attr(@dummy_spanned(meta_list(@~"link", // change PkgId to have a field? @@ -552,7 +558,6 @@ pub fn compile_crate_from_input(input: driver::input, mk_string_lit(@pkg_id.version.to_str())))])))]); } - driver::compile_rest(sess, cfg, what, Some(outputs), Some(crate_to_use)); crate_to_use } diff --git a/src/libstd/tempfile.rs b/src/libstd/tempfile.rs index 6da74834b1a4..3d4e5bb8b79c 100644 --- a/src/libstd/tempfile.rs +++ b/src/libstd/tempfile.rs @@ -27,7 +27,8 @@ pub fn mkdtemp(tmpdir: &Path, suffix: &str) -> Option { mod tests { use tempfile::mkdtemp; use tempfile; - + use core::os; + #[test] fn test_mkdtemp() { let p = mkdtemp(&Path("."), "foobar").unwrap(); @@ -89,4 +90,27 @@ mod tests { assert!(os::path_is_dir(&path2.pop())); }); } + + // Ideally this would be in core, but needs mkdtemp + #[test] + pub fn test_rmdir_recursive_ok() { + use core::libc::consts::os::posix88::{S_IRUSR, S_IWUSR, S_IXUSR}; + use core::os; + + let rwx = (S_IRUSR | S_IWUSR | S_IXUSR) as i32; + + let tmpdir = mkdtemp(&os::tmpdir(), "test").expect("test_rmdir_recursive_ok: \ + couldn't create temp dir"); + let root = tmpdir.push("foo"); + + debug!("making %s", root.to_str()); + assert!(os::make_dir(&root, rwx)); + assert!(os::make_dir(&root.push("foo"), rwx)); + assert!(os::make_dir(&root.push("foo").push("bar"), rwx)); + assert!(os::make_dir(&root.push("foo").push("bar").push("blat"), rwx)); + assert!(os::remove_dir_recursive(&root)); + assert!(!os::path_exists(&root)); + assert!(!os::path_exists(&root.push("bar"))); + assert!(!os::path_exists(&root.push("bar").push("blat"))); + } }