about summary refs log tree commit diff
diff options
context:
space:
mode:
authorBrian Anderson <banderson@mozilla.com>2016-09-22 01:30:30 +0000
committerBrian Anderson <banderson@mozilla.com>2016-10-02 14:52:15 -0700
commit29e0235415a42299e4a7467c5edb09a159ec680e (patch)
treea23455cc0251918edc2fe61d76da9da9ac5a7a07
parent0fb8379ed86f15943e87721f73165138598d19cc (diff)
downloadrust-29e0235415a42299e4a7467c5edb09a159ec680e.tar.gz
rust-29e0235415a42299e4a7467c5edb09a159ec680e.zip
Add a platform-abstraction tidy script
This is intended to maintain existing standards of code organization
in hopes that the standard library will continue to be refactored to
isolate platform-specific bits, making porting easier; where "standard
library" roughly means "all the dependencies of the std and test
crates".

This generally means placing restrictions on where `cfg(unix)`,
`cfg(windows)`, `cfg(target_os)` and `cfg(target_env)` may appear,
the basic objective being to isolate platform-specific code to the
platform-specific `std::sys` modules, and to the allocation,
unwinding, and libc crates.

Following are the basic rules, though there are currently
exceptions:

- core may not have platform-specific code
- liballoc_system may have platform-specific code
- liballoc_jemalloc may have platform-specific code
- libpanic_abort may have platform-specific code
- libpanic_unwind may have platform-specific code
- other crates in the std facade may not
- std may have platform-specific code in the following places
  - sys/unix/
  - sys/windows/
  - os/

There are plenty of exceptions today though, noted in the whitelist.
-rw-r--r--src/libstd/net/mod.rs2
-rw-r--r--src/libstd/net/test.rs2
-rw-r--r--src/libstd/sync/mpsc/select.rs2
-rw-r--r--src/libstd/sys/common/io.rs3
-rw-r--r--src/tools/tidy/src/main.rs2
-rw-r--r--src/tools/tidy/src/pal.rs231
6 files changed, 239 insertions, 3 deletions
diff --git a/src/libstd/net/mod.rs b/src/libstd/net/mod.rs
index 7dd0e30df03..56286fbe253 100644
--- a/src/libstd/net/mod.rs
+++ b/src/libstd/net/mod.rs
@@ -31,7 +31,7 @@ mod addr;
 mod tcp;
 mod udp;
 mod parser;
-#[cfg(all(test, not(target_os = "emscripten")))]
+#[cfg(test)]
 mod test;
 
 /// Possible values which can be passed to the [`shutdown`] method of
diff --git a/src/libstd/net/test.rs b/src/libstd/net/test.rs
index 98ac61f6461..3f2eacda7d6 100644
--- a/src/libstd/net/test.rs
+++ b/src/libstd/net/test.rs
@@ -8,6 +8,8 @@
 // option. This file may not be copied, modified, or distributed
 // except according to those terms.
 
+#[allow(dead_code)] // not used on emscripten
+
 use env;
 use net::{SocketAddr, SocketAddrV4, SocketAddrV6, Ipv4Addr, Ipv6Addr, ToSocketAddrs};
 use sync::atomic::{AtomicUsize, Ordering};
diff --git a/src/libstd/sync/mpsc/select.rs b/src/libstd/sync/mpsc/select.rs
index 91896e1ab85..e056a350815 100644
--- a/src/libstd/sync/mpsc/select.rs
+++ b/src/libstd/sync/mpsc/select.rs
@@ -366,8 +366,8 @@ impl<'rx, T:Send+'rx> fmt::Debug for Handle<'rx, T> {
     }
 }
 
-#[cfg(all(test, not(target_os = "emscripten")))]
 #[allow(unused_imports)]
+#[cfg(all(test, not(target_os = "emscripten")))]
 mod tests {
     use thread;
     use sync::mpsc::*;
diff --git a/src/libstd/sys/common/io.rs b/src/libstd/sys/common/io.rs
index 47cec4ef5c2..0483725dd83 100644
--- a/src/libstd/sys/common/io.rs
+++ b/src/libstd/sys/common/io.rs
@@ -50,7 +50,8 @@ pub unsafe fn read_to_end_uninitialized(r: &mut Read, buf: &mut Vec<u8>) -> io::
     }
 }
 
-#[cfg(all(test, not(target_os = "emscripten")))]
+#[cfg(test)]
+#[allow(dead_code)] // not used on emscripten
 pub mod test {
     use path::{Path, PathBuf};
     use env;
diff --git a/src/tools/tidy/src/main.rs b/src/tools/tidy/src/main.rs
index 2839bbded1a..cabaee5d060 100644
--- a/src/tools/tidy/src/main.rs
+++ b/src/tools/tidy/src/main.rs
@@ -36,6 +36,7 @@ mod errors;
 mod features;
 mod cargo;
 mod cargo_lock;
+mod pal;
 
 fn main() {
     let path = env::args_os().skip(1).next().expect("need an argument");
@@ -48,6 +49,7 @@ fn main() {
     cargo::check(&path, &mut bad);
     features::check(&path, &mut bad);
     cargo_lock::check(&path, &mut bad);
+    pal::check(&path, &mut bad);
 
     if bad {
         panic!("some tidy checks failed");
diff --git a/src/tools/tidy/src/pal.rs b/src/tools/tidy/src/pal.rs
new file mode 100644
index 00000000000..aac7c266664
--- /dev/null
+++ b/src/tools/tidy/src/pal.rs
@@ -0,0 +1,231 @@
+// Copyright 2016 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.
+
+//! Tidy check to enforce rules about platform-specific code in std
+//!
+//! This is intended to maintain existing standards of code
+//! organization in hopes that the standard library will continue to
+//! be refactored to isolate platform-specific bits, making porting
+//! easier; where "standard library" roughly means "all the
+//! dependencies of the std and test crates".
+//!
+//! This generally means placing restrictions on where `cfg(unix)`,
+//! `cfg(windows)`, `cfg(target_os)` and `cfg(target_env)` may appear,
+//! the basic objective being to isolate platform-specific code to the
+//! platform-specific `std::sys` modules, and to the allocation,
+//! unwinding, and libc crates.
+//!
+//! Following are the basic rules, though there are currently
+//! exceptions:
+//!
+//! - core may not have platform-specific code
+//! - liballoc_system may have platform-specific code
+//! - liballoc_jemalloc may have platform-specific code
+//! - libpanic_abort may have platform-specific code
+//! - libpanic_unwind may have platform-specific code
+//! - libunwind may have platform-specific code
+//! - other crates in the std facade may not
+//! - std may have platform-specific code in the following places
+//!   - sys/unix/
+//!   - sys/windows/
+//!   - os/
+//!
+//! `std/sys_common` should _not_ contain platform-specific code.
+//! Finally, because std contains tests with platform-specific
+//! `ignore` attributes, once the parser encounters `mod tests`,
+//! platform-specific cfgs are allowed. Not sure yet how to deal with
+//! this in the long term.
+
+use std::fs::File;
+use std::io::Read;
+use std::path::Path;
+use std::iter::Iterator;
+
+// Paths that may contain platform-specific code
+const EXCEPTION_PATHS: &'static [&'static str] = &[
+    // std crates
+    "src/liballoc_jemalloc",
+    "src/liballoc_system",
+    "src/liblibc",
+    "src/libpanic_abort",
+    "src/libpanic_unwind",
+    "src/libunwind",
+    "src/libstd/sys/unix", // This is where platform-specific code for std should live
+    "src/libstd/sys/windows", // Ditto
+    "src/libstd/os", // Platform-specific public interfaces
+    "src/rtstartup", // Not sure what to do about this. magic stuff for mingw
+
+    // temporary exceptions
+    "src/libstd/lib.rs", // This could probably be done within the sys directory
+    "src/libstd/rtdeps.rs", // Until rustbuild replaces make
+    "src/libstd/path.rs",
+    "src/libstd/io/stdio.rs",
+    "src/libstd/num/f32.rs",
+    "src/libstd/num/f64.rs",
+    "src/libstd/thread/local.rs",
+    "src/libstd/sys/common/mod.rs",
+    "src/libstd/sys/common/args.rs",
+    "src/libstd/sys/common/net.rs",
+    "src/libstd/sys/common/util.rs",
+    "src/libterm", // Not sure how to make this crate portable, but test needs it
+    "src/libtest", // Probably should defer to unstable std::sys APIs
+
+    // std testing crates, ok for now at least
+    "src/libcoretest",
+
+    // non-std crates
+    "src/test",
+    "src/tools",
+    "src/librustc",
+    "src/librustdoc",
+    "src/libsyntax",
+    "src/bootstrap",
+];
+
+pub fn check(path: &Path, bad: &mut bool) {
+    let ref mut contents = String::new();
+    // Sanity check that the complex parsing here works
+    let ref mut saw_target_arch = false;
+    let ref mut saw_cfg_bang = false;
+    super::walk(path, &mut super::filter_dirs, &mut |file| {
+        let filestr = file.to_string_lossy().replace("\\", "/");
+        if !filestr.ends_with(".rs") { return }
+
+        let is_exception_path = EXCEPTION_PATHS.iter().any(|s| filestr.contains(&**s));
+        if is_exception_path { return }
+
+        check_cfgs(contents, &file, bad, saw_target_arch, saw_cfg_bang);
+    });
+
+    assert!(*saw_target_arch);
+    assert!(*saw_cfg_bang);
+}
+
+fn check_cfgs(contents: &mut String, file: &Path,
+              bad: &mut bool, saw_target_arch: &mut bool, saw_cfg_bang: &mut bool) {
+    contents.truncate(0);
+    t!(t!(File::open(file), file).read_to_string(contents));
+
+    // For now it's ok to have platform-specific code after 'mod tests'.
+    let mod_tests_idx = find_test_mod(contents);
+    let contents = &contents[..mod_tests_idx];
+    // Pull out all "cfg(...)" and "cfg!(...)" strings
+    let cfgs = parse_cfgs(contents);
+
+    let mut line_numbers: Option<Vec<usize>> = None;
+    let mut err = |idx: usize, cfg: &str| {
+        if line_numbers.is_none() {
+            line_numbers = Some(contents.match_indices('\n').map(|(i, _)| i).collect());
+        }
+        let line_numbers = line_numbers.as_ref().expect("");
+        let line = match line_numbers.binary_search(&idx) {
+            Ok(_) => unreachable!(),
+            Err(i) => i + 1
+        };
+        println!("{}:{}: platform-specific cfg: {}", file.display(), line, cfg);
+        *bad = true;
+    };
+
+    for (idx, cfg) in cfgs.into_iter() {
+        // Sanity check that the parsing here works
+        if !*saw_target_arch && cfg.contains("target_arch") { *saw_target_arch = true }
+        if !*saw_cfg_bang && cfg.contains("cfg!") { *saw_cfg_bang = true }
+
+        let contains_platform_specific_cfg =
+            cfg.contains("target_os")
+            || cfg.contains("target_env")
+            || cfg.contains("target_vendor")
+            || cfg.contains("unix")
+            || cfg.contains("windows");
+
+        if !contains_platform_specific_cfg { continue }
+
+        let preceeded_by_doc_comment = {
+            let pre_contents = &contents[..idx];
+            let pre_newline = pre_contents.rfind('\n');
+            let pre_doc_comment = pre_contents.rfind("///");
+            match (pre_newline, pre_doc_comment) {
+                (Some(n), Some(c)) => n < c,
+                (None, Some(_)) => true,
+                (_, None) => false,
+            }
+        };
+
+        if preceeded_by_doc_comment { continue }
+
+        err(idx, cfg);
+    }
+}
+
+fn find_test_mod(contents: &str) -> usize {
+    if let Some(mod_tests_idx) = contents.find("mod tests") {
+        // Also capture a previos line indicating "mod tests" in cfg-ed out
+        let prev_newline_idx = contents[..mod_tests_idx].rfind('\n').unwrap_or(mod_tests_idx);
+        let prev_newline_idx = contents[..prev_newline_idx].rfind('\n');
+        if let Some(nl) = prev_newline_idx {
+            let prev_line = &contents[nl + 1 .. mod_tests_idx];
+            let emcc_cfg = "cfg(all(test, not(target_os";
+            if prev_line.contains(emcc_cfg) {
+                nl
+            } else {
+                mod_tests_idx
+            }
+        } else {
+            mod_tests_idx
+        }
+    } else {
+        contents.len()
+    }
+}
+
+fn parse_cfgs<'a>(contents: &'a str) -> Vec<(usize, &'a str)> {
+    let candidate_cfgs = contents.match_indices("cfg");
+    let candidate_cfg_idxs = candidate_cfgs.map(|(i, _)| i);
+    // This is puling out the indexes of all "cfg" strings
+    // that appear to be tokens succeeded by a paren.
+    let cfgs = candidate_cfg_idxs.filter(|i| {
+        let pre_idx = i.saturating_sub(*i);
+        let succeeds_non_ident = !contents.as_bytes().get(pre_idx)
+            .cloned()
+            .map(char::from)
+            .map(char::is_alphanumeric)
+            .unwrap_or(false);
+        let contents_after = &contents[*i..];
+        let first_paren = contents_after.find('(');
+        let paren_idx = first_paren.map(|ip| i + ip);
+        let preceeds_whitespace_and_paren = paren_idx.map(|ip| {
+            let maybe_space = &contents[*i + "cfg".len() .. ip];
+            maybe_space.chars().all(|c| char::is_whitespace(c) || c == '!')
+        }).unwrap_or(false);
+
+        succeeds_non_ident && preceeds_whitespace_and_paren
+    });
+
+    cfgs.map(|i| {
+        let mut depth = 0;
+        let contents_from = &contents[i..];
+        for (j, byte) in contents_from.bytes().enumerate() {
+            match byte {
+                b'(' => {
+                    depth += 1;
+                }
+                b')' => {
+                    depth -= 1;
+                    if depth == 0 {
+                        return (i, &contents_from[.. j + 1]);
+                    }
+                }
+                _ => { }
+            }
+        }
+
+        unreachable!()
+    }).collect()
+}