Skip to content

Commit 29e0235

Browse files
committed
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.
1 parent 0fb8379 commit 29e0235

File tree

6 files changed

+239
-3
lines changed

6 files changed

+239
-3
lines changed

src/libstd/net/mod.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ mod addr;
3131
mod tcp;
3232
mod udp;
3333
mod parser;
34-
#[cfg(all(test, not(target_os = "emscripten")))]
34+
#[cfg(test)]
3535
mod test;
3636

3737
/// Possible values which can be passed to the [`shutdown`] method of

src/libstd/net/test.rs

+2
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88
// option. This file may not be copied, modified, or distributed
99
// except according to those terms.
1010

11+
#[allow(dead_code)] // not used on emscripten
12+
1113
use env;
1214
use net::{SocketAddr, SocketAddrV4, SocketAddrV6, Ipv4Addr, Ipv6Addr, ToSocketAddrs};
1315
use sync::atomic::{AtomicUsize, Ordering};

src/libstd/sync/mpsc/select.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -366,8 +366,8 @@ impl<'rx, T:Send+'rx> fmt::Debug for Handle<'rx, T> {
366366
}
367367
}
368368

369-
#[cfg(all(test, not(target_os = "emscripten")))]
370369
#[allow(unused_imports)]
370+
#[cfg(all(test, not(target_os = "emscripten")))]
371371
mod tests {
372372
use thread;
373373
use sync::mpsc::*;

src/libstd/sys/common/io.rs

+2-1
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,8 @@ pub unsafe fn read_to_end_uninitialized(r: &mut Read, buf: &mut Vec<u8>) -> io::
5050
}
5151
}
5252

53-
#[cfg(all(test, not(target_os = "emscripten")))]
53+
#[cfg(test)]
54+
#[allow(dead_code)] // not used on emscripten
5455
pub mod test {
5556
use path::{Path, PathBuf};
5657
use env;

src/tools/tidy/src/main.rs

+2
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ mod errors;
3636
mod features;
3737
mod cargo;
3838
mod cargo_lock;
39+
mod pal;
3940

4041
fn main() {
4142
let path = env::args_os().skip(1).next().expect("need an argument");
@@ -48,6 +49,7 @@ fn main() {
4849
cargo::check(&path, &mut bad);
4950
features::check(&path, &mut bad);
5051
cargo_lock::check(&path, &mut bad);
52+
pal::check(&path, &mut bad);
5153

5254
if bad {
5355
panic!("some tidy checks failed");

src/tools/tidy/src/pal.rs

+231
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,231 @@
1+
// Copyright 2016 The Rust Project Developers. See the COPYRIGHT
2+
// file at the top-level directory of this distribution and at
3+
// http://rust-lang.org/COPYRIGHT.
4+
//
5+
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
6+
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
7+
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
8+
// option. This file may not be copied, modified, or distributed
9+
// except according to those terms.
10+
11+
//! Tidy check to enforce rules about platform-specific code in std
12+
//!
13+
//! This is intended to maintain existing standards of code
14+
//! organization in hopes that the standard library will continue to
15+
//! be refactored to isolate platform-specific bits, making porting
16+
//! easier; where "standard library" roughly means "all the
17+
//! dependencies of the std and test crates".
18+
//!
19+
//! This generally means placing restrictions on where `cfg(unix)`,
20+
//! `cfg(windows)`, `cfg(target_os)` and `cfg(target_env)` may appear,
21+
//! the basic objective being to isolate platform-specific code to the
22+
//! platform-specific `std::sys` modules, and to the allocation,
23+
//! unwinding, and libc crates.
24+
//!
25+
//! Following are the basic rules, though there are currently
26+
//! exceptions:
27+
//!
28+
//! - core may not have platform-specific code
29+
//! - liballoc_system may have platform-specific code
30+
//! - liballoc_jemalloc may have platform-specific code
31+
//! - libpanic_abort may have platform-specific code
32+
//! - libpanic_unwind may have platform-specific code
33+
//! - libunwind may have platform-specific code
34+
//! - other crates in the std facade may not
35+
//! - std may have platform-specific code in the following places
36+
//! - sys/unix/
37+
//! - sys/windows/
38+
//! - os/
39+
//!
40+
//! `std/sys_common` should _not_ contain platform-specific code.
41+
//! Finally, because std contains tests with platform-specific
42+
//! `ignore` attributes, once the parser encounters `mod tests`,
43+
//! platform-specific cfgs are allowed. Not sure yet how to deal with
44+
//! this in the long term.
45+
46+
use std::fs::File;
47+
use std::io::Read;
48+
use std::path::Path;
49+
use std::iter::Iterator;
50+
51+
// Paths that may contain platform-specific code
52+
const EXCEPTION_PATHS: &'static [&'static str] = &[
53+
// std crates
54+
"src/liballoc_jemalloc",
55+
"src/liballoc_system",
56+
"src/liblibc",
57+
"src/libpanic_abort",
58+
"src/libpanic_unwind",
59+
"src/libunwind",
60+
"src/libstd/sys/unix", // This is where platform-specific code for std should live
61+
"src/libstd/sys/windows", // Ditto
62+
"src/libstd/os", // Platform-specific public interfaces
63+
"src/rtstartup", // Not sure what to do about this. magic stuff for mingw
64+
65+
// temporary exceptions
66+
"src/libstd/lib.rs", // This could probably be done within the sys directory
67+
"src/libstd/rtdeps.rs", // Until rustbuild replaces make
68+
"src/libstd/path.rs",
69+
"src/libstd/io/stdio.rs",
70+
"src/libstd/num/f32.rs",
71+
"src/libstd/num/f64.rs",
72+
"src/libstd/thread/local.rs",
73+
"src/libstd/sys/common/mod.rs",
74+
"src/libstd/sys/common/args.rs",
75+
"src/libstd/sys/common/net.rs",
76+
"src/libstd/sys/common/util.rs",
77+
"src/libterm", // Not sure how to make this crate portable, but test needs it
78+
"src/libtest", // Probably should defer to unstable std::sys APIs
79+
80+
// std testing crates, ok for now at least
81+
"src/libcoretest",
82+
83+
// non-std crates
84+
"src/test",
85+
"src/tools",
86+
"src/librustc",
87+
"src/librustdoc",
88+
"src/libsyntax",
89+
"src/bootstrap",
90+
];
91+
92+
pub fn check(path: &Path, bad: &mut bool) {
93+
let ref mut contents = String::new();
94+
// Sanity check that the complex parsing here works
95+
let ref mut saw_target_arch = false;
96+
let ref mut saw_cfg_bang = false;
97+
super::walk(path, &mut super::filter_dirs, &mut |file| {
98+
let filestr = file.to_string_lossy().replace("\\", "/");
99+
if !filestr.ends_with(".rs") { return }
100+
101+
let is_exception_path = EXCEPTION_PATHS.iter().any(|s| filestr.contains(&**s));
102+
if is_exception_path { return }
103+
104+
check_cfgs(contents, &file, bad, saw_target_arch, saw_cfg_bang);
105+
});
106+
107+
assert!(*saw_target_arch);
108+
assert!(*saw_cfg_bang);
109+
}
110+
111+
fn check_cfgs(contents: &mut String, file: &Path,
112+
bad: &mut bool, saw_target_arch: &mut bool, saw_cfg_bang: &mut bool) {
113+
contents.truncate(0);
114+
t!(t!(File::open(file), file).read_to_string(contents));
115+
116+
// For now it's ok to have platform-specific code after 'mod tests'.
117+
let mod_tests_idx = find_test_mod(contents);
118+
let contents = &contents[..mod_tests_idx];
119+
// Pull out all "cfg(...)" and "cfg!(...)" strings
120+
let cfgs = parse_cfgs(contents);
121+
122+
let mut line_numbers: Option<Vec<usize>> = None;
123+
let mut err = |idx: usize, cfg: &str| {
124+
if line_numbers.is_none() {
125+
line_numbers = Some(contents.match_indices('\n').map(|(i, _)| i).collect());
126+
}
127+
let line_numbers = line_numbers.as_ref().expect("");
128+
let line = match line_numbers.binary_search(&idx) {
129+
Ok(_) => unreachable!(),
130+
Err(i) => i + 1
131+
};
132+
println!("{}:{}: platform-specific cfg: {}", file.display(), line, cfg);
133+
*bad = true;
134+
};
135+
136+
for (idx, cfg) in cfgs.into_iter() {
137+
// Sanity check that the parsing here works
138+
if !*saw_target_arch && cfg.contains("target_arch") { *saw_target_arch = true }
139+
if !*saw_cfg_bang && cfg.contains("cfg!") { *saw_cfg_bang = true }
140+
141+
let contains_platform_specific_cfg =
142+
cfg.contains("target_os")
143+
|| cfg.contains("target_env")
144+
|| cfg.contains("target_vendor")
145+
|| cfg.contains("unix")
146+
|| cfg.contains("windows");
147+
148+
if !contains_platform_specific_cfg { continue }
149+
150+
let preceeded_by_doc_comment = {
151+
let pre_contents = &contents[..idx];
152+
let pre_newline = pre_contents.rfind('\n');
153+
let pre_doc_comment = pre_contents.rfind("///");
154+
match (pre_newline, pre_doc_comment) {
155+
(Some(n), Some(c)) => n < c,
156+
(None, Some(_)) => true,
157+
(_, None) => false,
158+
}
159+
};
160+
161+
if preceeded_by_doc_comment { continue }
162+
163+
err(idx, cfg);
164+
}
165+
}
166+
167+
fn find_test_mod(contents: &str) -> usize {
168+
if let Some(mod_tests_idx) = contents.find("mod tests") {
169+
// Also capture a previos line indicating "mod tests" in cfg-ed out
170+
let prev_newline_idx = contents[..mod_tests_idx].rfind('\n').unwrap_or(mod_tests_idx);
171+
let prev_newline_idx = contents[..prev_newline_idx].rfind('\n');
172+
if let Some(nl) = prev_newline_idx {
173+
let prev_line = &contents[nl + 1 .. mod_tests_idx];
174+
let emcc_cfg = "cfg(all(test, not(target_os";
175+
if prev_line.contains(emcc_cfg) {
176+
nl
177+
} else {
178+
mod_tests_idx
179+
}
180+
} else {
181+
mod_tests_idx
182+
}
183+
} else {
184+
contents.len()
185+
}
186+
}
187+
188+
fn parse_cfgs<'a>(contents: &'a str) -> Vec<(usize, &'a str)> {
189+
let candidate_cfgs = contents.match_indices("cfg");
190+
let candidate_cfg_idxs = candidate_cfgs.map(|(i, _)| i);
191+
// This is puling out the indexes of all "cfg" strings
192+
// that appear to be tokens succeeded by a paren.
193+
let cfgs = candidate_cfg_idxs.filter(|i| {
194+
let pre_idx = i.saturating_sub(*i);
195+
let succeeds_non_ident = !contents.as_bytes().get(pre_idx)
196+
.cloned()
197+
.map(char::from)
198+
.map(char::is_alphanumeric)
199+
.unwrap_or(false);
200+
let contents_after = &contents[*i..];
201+
let first_paren = contents_after.find('(');
202+
let paren_idx = first_paren.map(|ip| i + ip);
203+
let preceeds_whitespace_and_paren = paren_idx.map(|ip| {
204+
let maybe_space = &contents[*i + "cfg".len() .. ip];
205+
maybe_space.chars().all(|c| char::is_whitespace(c) || c == '!')
206+
}).unwrap_or(false);
207+
208+
succeeds_non_ident && preceeds_whitespace_and_paren
209+
});
210+
211+
cfgs.map(|i| {
212+
let mut depth = 0;
213+
let contents_from = &contents[i..];
214+
for (j, byte) in contents_from.bytes().enumerate() {
215+
match byte {
216+
b'(' => {
217+
depth += 1;
218+
}
219+
b')' => {
220+
depth -= 1;
221+
if depth == 0 {
222+
return (i, &contents_from[.. j + 1]);
223+
}
224+
}
225+
_ => { }
226+
}
227+
}
228+
229+
unreachable!()
230+
}).collect()
231+
}

0 commit comments

Comments
 (0)