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.
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.
11 //! Tidy check to enforce rules about platform-specific code in std
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".
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.
25 //! Following are the basic rules, though there are currently
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
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.
49 use std
::iter
::Iterator
;
51 // Paths that may contain platform-specific code
52 const EXCEPTION_PATHS
: &'
static [&'
static str] = &[
54 "src/liballoc_jemalloc",
55 "src/liballoc_system",
58 "src/libpanic_unwind",
60 "src/libstd/sys/", // Platform-specific code for std lives here.
61 // This has the trailing slash so that sys_common is not excepted.
62 "src/libstd/os", // Platform-specific public interfaces
63 "src/rtstartup", // Not sure what to do about this. magic stuff for mingw
65 // temporary exceptions
66 "src/libstd/rtdeps.rs", // Until rustbuild replaces make
70 "src/libstd/sys_common/mod.rs",
71 "src/libstd/sys_common/net.rs",
72 "src/libterm", // Not sure how to make this crate portable, but test needs it
73 "src/libtest", // Probably should defer to unstable std::sys APIs
75 // std testing crates, ok for now at least
87 pub fn check(path
: &Path
, bad
: &mut bool
) {
88 let ref mut contents
= String
::new();
89 // Sanity check that the complex parsing here works
90 let ref mut saw_target_arch
= false;
91 let ref mut saw_cfg_bang
= false;
92 super::walk(path
, &mut super::filter_dirs
, &mut |file
| {
93 let filestr
= file
.to_string_lossy().replace("\\", "/");
94 if !filestr
.ends_with(".rs") { return }
96 let is_exception_path
= EXCEPTION_PATHS
.iter().any(|s
| filestr
.contains(&**s
));
97 if is_exception_path { return }
99 check_cfgs(contents
, &file
, bad
, saw_target_arch
, saw_cfg_bang
);
102 assert
!(*saw_target_arch
);
103 assert
!(*saw_cfg_bang
);
106 fn check_cfgs(contents
: &mut String
, file
: &Path
,
107 bad
: &mut bool
, saw_target_arch
: &mut bool
, saw_cfg_bang
: &mut bool
) {
108 contents
.truncate(0);
109 t
!(t
!(File
::open(file
), file
).read_to_string(contents
));
111 // For now it's ok to have platform-specific code after 'mod tests'.
112 let mod_tests_idx
= find_test_mod(contents
);
113 let contents
= &contents
[..mod_tests_idx
];
114 // Pull out all "cfg(...)" and "cfg!(...)" strings
115 let cfgs
= parse_cfgs(contents
);
117 let mut line_numbers
: Option
<Vec
<usize>> = None
;
118 let mut err
= |idx
: usize, cfg
: &str| {
119 if line_numbers
.is_none() {
120 line_numbers
= Some(contents
.match_indices('
\n'
).map(|(i
, _
)| i
).collect());
122 let line_numbers
= line_numbers
.as_ref().expect("");
123 let line
= match line_numbers
.binary_search(&idx
) {
124 Ok(_
) => unreachable
!(),
127 println
!("{}:{}: platform-specific cfg: {}", file
.display(), line
, cfg
);
131 for (idx
, cfg
) in cfgs
.into_iter() {
132 // Sanity check that the parsing here works
133 if !*saw_target_arch
&& cfg
.contains("target_arch") { *saw_target_arch = true }
134 if !*saw_cfg_bang
&& cfg
.contains("cfg!") { *saw_cfg_bang = true }
136 let contains_platform_specific_cfg
=
137 cfg
.contains("target_os")
138 || cfg
.contains("target_env")
139 || cfg
.contains("target_vendor")
140 || cfg
.contains("unix")
141 || cfg
.contains("windows");
143 if !contains_platform_specific_cfg { continue }
145 let preceeded_by_doc_comment
= {
146 let pre_contents
= &contents
[..idx
];
147 let pre_newline
= pre_contents
.rfind('
\n'
);
148 let pre_doc_comment
= pre_contents
.rfind("///");
149 match (pre_newline
, pre_doc_comment
) {
150 (Some(n
), Some(c
)) => n
< c
,
151 (None
, Some(_
)) => true,
156 if preceeded_by_doc_comment { continue }
162 fn find_test_mod(contents
: &str) -> usize {
163 if let Some(mod_tests_idx
) = contents
.find("mod tests") {
164 // Also capture a previos line indicating "mod tests" in cfg-ed out
165 let prev_newline_idx
= contents
[..mod_tests_idx
].rfind('
\n'
).unwrap_or(mod_tests_idx
);
166 let prev_newline_idx
= contents
[..prev_newline_idx
].rfind('
\n'
);
167 if let Some(nl
) = prev_newline_idx
{
168 let prev_line
= &contents
[nl
+ 1 .. mod_tests_idx
];
169 let emcc_cfg
= "cfg(all(test, not(target_os";
170 if prev_line
.contains(emcc_cfg
) {
183 fn parse_cfgs
<'a
>(contents
: &'a
str) -> Vec
<(usize, &'a
str)> {
184 let candidate_cfgs
= contents
.match_indices("cfg");
185 let candidate_cfg_idxs
= candidate_cfgs
.map(|(i
, _
)| i
);
186 // This is puling out the indexes of all "cfg" strings
187 // that appear to be tokens succeeded by a paren.
188 let cfgs
= candidate_cfg_idxs
.filter(|i
| {
189 let pre_idx
= i
.saturating_sub(*i
);
190 let succeeds_non_ident
= !contents
.as_bytes().get(pre_idx
)
193 .map(char::is_alphanumeric
)
195 let contents_after
= &contents
[*i
..];
196 let first_paren
= contents_after
.find('
('
);
197 let paren_idx
= first_paren
.map(|ip
| i
+ ip
);
198 let preceeds_whitespace_and_paren
= paren_idx
.map(|ip
| {
199 let maybe_space
= &contents
[*i
+ "cfg".len() .. ip
];
200 maybe_space
.chars().all(|c
| char::is_whitespace(c
) || c
== '
!'
)
203 succeeds_non_ident
&& preceeds_whitespace_and_paren
208 let contents_from
= &contents
[i
..];
209 for (j
, byte
) in contents_from
.bytes().enumerate() {
217 return (i
, &contents_from
[.. j
+ 1]);