rustc_session/
output.rs

1//! Related to out filenames of compilation (e.g. binaries).
2
3use std::path::Path;
4
5use rustc_ast::{self as ast, attr};
6use rustc_span::{Span, Symbol, sym};
7
8use crate::Session;
9use crate::config::{self, CrateType, Input, OutFileName, OutputFilenames, OutputType};
10use crate::errors::{
11    self, CrateNameDoesNotMatch, CrateNameEmpty, CrateNameInvalid, FileIsNotWriteable,
12    InvalidCharacterInCrateName, InvalidCrateNameHelp,
13};
14
15pub fn out_filename(
16    sess: &Session,
17    crate_type: CrateType,
18    outputs: &OutputFilenames,
19    crate_name: Symbol,
20) -> OutFileName {
21    let default_filename = filename_for_input(sess, crate_type, crate_name, outputs);
22    let out_filename = outputs
23        .outputs
24        .get(&OutputType::Exe)
25        .and_then(|s| s.to_owned())
26        .or_else(|| outputs.single_output_file.clone())
27        .unwrap_or(default_filename);
28
29    if let OutFileName::Real(ref path) = out_filename {
30        check_file_is_writeable(path, sess);
31    }
32
33    out_filename
34}
35
36/// Make sure files are writeable. Mac, FreeBSD, and Windows system linkers
37/// check this already -- however, the Linux linker will happily overwrite a
38/// read-only file. We should be consistent.
39pub fn check_file_is_writeable(file: &Path, sess: &Session) {
40    if !is_writeable(file) {
41        sess.dcx().emit_fatal(FileIsNotWriteable { file });
42    }
43}
44
45fn is_writeable(p: &Path) -> bool {
46    match p.metadata() {
47        Err(..) => true,
48        Ok(m) => !m.permissions().readonly(),
49    }
50}
51
52pub fn find_crate_name(sess: &Session, attrs: &[ast::Attribute]) -> Symbol {
53    let validate = |s: Symbol, span: Option<Span>| {
54        validate_crate_name(sess, s, span);
55        s
56    };
57
58    // Look in attributes 100% of the time to make sure the attribute is marked
59    // as used. After doing this, however, we still prioritize a crate name from
60    // the command line over one found in the #[crate_name] attribute. If we
61    // find both we ensure that they're the same later on as well.
62    let attr_crate_name =
63        attr::find_by_name(attrs, sym::crate_name).and_then(|at| at.value_str().map(|s| (at, s)));
64
65    if let Some(ref s) = sess.opts.crate_name {
66        let s = Symbol::intern(s);
67        if let Some((attr, name)) = attr_crate_name {
68            if name != s {
69                sess.dcx().emit_err(CrateNameDoesNotMatch { span: attr.span, s, name });
70            }
71        }
72        return validate(s, None);
73    }
74
75    if let Some((attr, s)) = attr_crate_name {
76        return validate(s, Some(attr.span));
77    }
78    if let Input::File(ref path) = sess.io.input {
79        if let Some(s) = path.file_stem().and_then(|s| s.to_str()) {
80            if s.starts_with('-') {
81                sess.dcx().emit_err(CrateNameInvalid { s });
82            } else {
83                return validate(Symbol::intern(&s.replace('-', "_")), None);
84            }
85        }
86    }
87
88    sym::rust_out
89}
90
91pub fn validate_crate_name(sess: &Session, s: Symbol, sp: Option<Span>) {
92    let mut guar = None;
93    {
94        if s.is_empty() {
95            guar = Some(sess.dcx().emit_err(CrateNameEmpty { span: sp }));
96        }
97        for c in s.as_str().chars() {
98            if c.is_alphanumeric() {
99                continue;
100            }
101            if c == '_' {
102                continue;
103            }
104            guar = Some(sess.dcx().emit_err(InvalidCharacterInCrateName {
105                span: sp,
106                character: c,
107                crate_name: s,
108                crate_name_help: if sp.is_none() {
109                    Some(InvalidCrateNameHelp::AddCrateName)
110                } else {
111                    None
112                },
113            }));
114        }
115    }
116
117    if let Some(guar) = guar {
118        guar.raise_fatal();
119    }
120}
121
122pub fn filename_for_metadata(sess: &Session, outputs: &OutputFilenames) -> OutFileName {
123    let out_filename = outputs.path(OutputType::Metadata);
124    if let OutFileName::Real(ref path) = out_filename {
125        check_file_is_writeable(path, sess);
126    }
127    out_filename
128}
129
130pub fn filename_for_input(
131    sess: &Session,
132    crate_type: CrateType,
133    crate_name: Symbol,
134    outputs: &OutputFilenames,
135) -> OutFileName {
136    let libname = format!("{}{}", crate_name, sess.opts.cg.extra_filename);
137
138    match crate_type {
139        CrateType::Rlib => {
140            OutFileName::Real(outputs.out_directory.join(&format!("lib{libname}.rlib")))
141        }
142        CrateType::Cdylib | CrateType::ProcMacro | CrateType::Dylib => {
143            let (prefix, suffix) = (&sess.target.dll_prefix, &sess.target.dll_suffix);
144            OutFileName::Real(outputs.out_directory.join(&format!("{prefix}{libname}{suffix}")))
145        }
146        CrateType::Staticlib => {
147            let (prefix, suffix) = (&sess.target.staticlib_prefix, &sess.target.staticlib_suffix);
148            OutFileName::Real(outputs.out_directory.join(&format!("{prefix}{libname}{suffix}")))
149        }
150        CrateType::Executable => {
151            let suffix = &sess.target.exe_suffix;
152            let out_filename = outputs.path(OutputType::Exe);
153            if let OutFileName::Real(ref path) = out_filename {
154                if suffix.is_empty() {
155                    out_filename
156                } else {
157                    OutFileName::Real(path.with_extension(&suffix[1..]))
158                }
159            } else {
160                out_filename
161            }
162        }
163    }
164}
165
166/// Returns default crate type for target
167///
168/// Default crate type is used when crate type isn't provided neither
169/// through cmd line arguments nor through crate attributes
170///
171/// It is CrateType::Executable for all platforms but iOS as there is no
172/// way to run iOS binaries anyway without jailbreaking and
173/// interaction with Rust code through static library is the only
174/// option for now
175pub fn default_output_for_target(sess: &Session) -> CrateType {
176    if !sess.target.executables { CrateType::Staticlib } else { CrateType::Executable }
177}
178
179/// Checks if target supports crate_type as output
180pub fn invalid_output_for_target(sess: &Session, crate_type: CrateType) -> bool {
181    if let CrateType::Cdylib | CrateType::Dylib | CrateType::ProcMacro = crate_type {
182        if !sess.target.dynamic_linking {
183            return true;
184        }
185        if sess.crt_static(Some(crate_type)) && !sess.target.crt_static_allows_dylibs {
186            return true;
187        }
188    }
189    if let CrateType::ProcMacro | CrateType::Dylib = crate_type
190        && sess.target.only_cdylib
191    {
192        return true;
193    }
194    if let CrateType::Executable = crate_type
195        && !sess.target.executables
196    {
197        return true;
198    }
199
200    false
201}
202
203pub const CRATE_TYPES: &[(Symbol, CrateType)] = &[
204    (sym::rlib, CrateType::Rlib),
205    (sym::dylib, CrateType::Dylib),
206    (sym::cdylib, CrateType::Cdylib),
207    (sym::lib, config::default_lib_output()),
208    (sym::staticlib, CrateType::Staticlib),
209    (sym::proc_dash_macro, CrateType::ProcMacro),
210    (sym::bin, CrateType::Executable),
211];
212
213pub fn categorize_crate_type(s: Symbol) -> Option<CrateType> {
214    Some(CRATE_TYPES.iter().find(|(key, _)| *key == s)?.1)
215}
216
217pub fn collect_crate_types(session: &Session, attrs: &[ast::Attribute]) -> Vec<CrateType> {
218    // If we're generating a test executable, then ignore all other output
219    // styles at all other locations
220    if session.opts.test {
221        return vec![CrateType::Executable];
222    }
223
224    // Only check command line flags if present. If no types are specified by
225    // command line, then reuse the empty `base` Vec to hold the types that
226    // will be found in crate attributes.
227    // JUSTIFICATION: before wrapper fn is available
228    #[allow(rustc::bad_opt_access)]
229    let mut base = session.opts.crate_types.clone();
230    if base.is_empty() {
231        let attr_types = attrs.iter().filter_map(|a| {
232            if a.has_name(sym::crate_type)
233                && let Some(s) = a.value_str()
234            {
235                categorize_crate_type(s)
236            } else {
237                None
238            }
239        });
240        base.extend(attr_types);
241        if base.is_empty() {
242            base.push(default_output_for_target(session));
243        } else {
244            base.sort();
245            base.dedup();
246        }
247    }
248
249    base.retain(|crate_type| {
250        if invalid_output_for_target(session, *crate_type) {
251            session.dcx().emit_warn(errors::UnsupportedCrateTypeForTarget {
252                crate_type: *crate_type,
253                target_triple: &session.opts.target_triple,
254            });
255            false
256        } else {
257            true
258        }
259    });
260
261    base
262}