• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 use crate::errors::{
2     BinaryOutputToTty, FailedCopyToStdout, FailedCreateEncodedMetadata, FailedCreateFile,
3     FailedCreateTempdir, FailedWriteError,
4 };
5 use crate::{encode_metadata, EncodedMetadata};
6 
7 use rustc_data_structures::temp_dir::MaybeTempDir;
8 use rustc_hir::def_id::LOCAL_CRATE;
9 use rustc_middle::ty::TyCtxt;
10 use rustc_session::config::{OutFileName, OutputType};
11 use rustc_session::output::filename_for_metadata;
12 use rustc_session::{MetadataKind, Session};
13 use tempfile::Builder as TempFileBuilder;
14 
15 use std::path::{Path, PathBuf};
16 use std::{fs, io};
17 
18 // FIXME(eddyb) maybe include the crate name in this?
19 pub const METADATA_FILENAME: &str = "lib.rmeta";
20 
21 /// We use a temp directory here to avoid races between concurrent rustc processes,
22 /// such as builds in the same directory using the same filename for metadata while
23 /// building an `.rlib` (stomping over one another), or writing an `.rmeta` into a
24 /// directory being searched for `extern crate` (observing an incomplete file).
25 /// The returned path is the temporary file containing the complete metadata.
emit_wrapper_file( sess: &Session, data: &[u8], tmpdir: &MaybeTempDir, name: &str, ) -> PathBuf26 pub fn emit_wrapper_file(
27     sess: &Session,
28     data: &[u8],
29     tmpdir: &MaybeTempDir,
30     name: &str,
31 ) -> PathBuf {
32     let out_filename = tmpdir.as_ref().join(name);
33     let result = fs::write(&out_filename, data);
34 
35     if let Err(err) = result {
36         sess.emit_fatal(FailedWriteError { filename: out_filename, err });
37     }
38 
39     out_filename
40 }
41 
encode_and_write_metadata(tcx: TyCtxt<'_>) -> (EncodedMetadata, bool)42 pub fn encode_and_write_metadata(tcx: TyCtxt<'_>) -> (EncodedMetadata, bool) {
43     let crate_name = tcx.crate_name(LOCAL_CRATE);
44     let out_filename = filename_for_metadata(tcx.sess, crate_name, tcx.output_filenames(()));
45     // To avoid races with another rustc process scanning the output directory,
46     // we need to write the file somewhere else and atomically move it to its
47     // final destination, with an `fs::rename` call. In order for the rename to
48     // always succeed, the temporary file needs to be on the same filesystem,
49     // which is why we create it inside the output directory specifically.
50     let metadata_tmpdir = TempFileBuilder::new()
51         .prefix("rmeta")
52         .tempdir_in(out_filename.parent().unwrap_or_else(|| Path::new("")))
53         .unwrap_or_else(|err| tcx.sess.emit_fatal(FailedCreateTempdir { err }));
54     let metadata_tmpdir = MaybeTempDir::new(metadata_tmpdir, tcx.sess.opts.cg.save_temps);
55     let metadata_filename = metadata_tmpdir.as_ref().join(METADATA_FILENAME);
56 
57     // Always create a file at `metadata_filename`, even if we have nothing to write to it.
58     // This simplifies the creation of the output `out_filename` when requested.
59     let metadata_kind = tcx.sess.metadata_kind();
60     match metadata_kind {
61         MetadataKind::None => {
62             std::fs::File::create(&metadata_filename).unwrap_or_else(|err| {
63                 tcx.sess.emit_fatal(FailedCreateFile { filename: &metadata_filename, err });
64             });
65         }
66         MetadataKind::Uncompressed | MetadataKind::Compressed => {
67             encode_metadata(tcx, &metadata_filename);
68         }
69     };
70 
71     let _prof_timer = tcx.sess.prof.generic_activity("write_crate_metadata");
72 
73     // If the user requests metadata as output, rename `metadata_filename`
74     // to the expected output `out_filename`. The match above should ensure
75     // this file always exists.
76     let need_metadata_file = tcx.sess.opts.output_types.contains_key(&OutputType::Metadata);
77     let (metadata_filename, metadata_tmpdir) = if need_metadata_file {
78         let filename = match out_filename {
79             OutFileName::Real(ref path) => {
80                 if let Err(err) = non_durable_rename(&metadata_filename, path) {
81                     tcx.sess.emit_fatal(FailedWriteError { filename: path.to_path_buf(), err });
82                 }
83                 path.clone()
84             }
85             OutFileName::Stdout => {
86                 if out_filename.is_tty() {
87                     tcx.sess.emit_err(BinaryOutputToTty);
88                 } else if let Err(err) = copy_to_stdout(&metadata_filename) {
89                     tcx.sess
90                         .emit_err(FailedCopyToStdout { filename: metadata_filename.clone(), err });
91                 }
92                 metadata_filename
93             }
94         };
95         if tcx.sess.opts.json_artifact_notifications {
96             tcx.sess
97                 .parse_sess
98                 .span_diagnostic
99                 .emit_artifact_notification(&out_filename.as_path(), "metadata");
100         }
101         (filename, None)
102     } else {
103         (metadata_filename, Some(metadata_tmpdir))
104     };
105 
106     // Load metadata back to memory: codegen may need to include it in object files.
107     let metadata = EncodedMetadata::from_path(metadata_filename.clone(), metadata_tmpdir)
108         .unwrap_or_else(|err| {
109             tcx.sess.emit_fatal(FailedCreateEncodedMetadata { err });
110         });
111 
112     let need_metadata_module = metadata_kind == MetadataKind::Compressed;
113 
114     (metadata, need_metadata_module)
115 }
116 
117 #[cfg(not(target_os = "linux"))]
non_durable_rename(src: &Path, dst: &Path) -> std::io::Result<()>118 pub fn non_durable_rename(src: &Path, dst: &Path) -> std::io::Result<()> {
119     std::fs::rename(src, dst)
120 }
121 
122 /// This function attempts to bypass the auto_da_alloc heuristic implemented by some filesystems
123 /// such as btrfs and ext4. When renaming over a file that already exists then they will "helpfully"
124 /// write back the source file before committing the rename in case a developer forgot some of
125 /// the fsyncs in the open/write/fsync(file)/rename/fsync(dir) dance for atomic file updates.
126 ///
127 /// To avoid triggering this heuristic we delete the destination first, if it exists.
128 /// The cost of an extra syscall is much lower than getting descheduled for the sync IO.
129 #[cfg(target_os = "linux")]
non_durable_rename(src: &Path, dst: &Path) -> std::io::Result<()>130 pub fn non_durable_rename(src: &Path, dst: &Path) -> std::io::Result<()> {
131     let _ = std::fs::remove_file(dst);
132     std::fs::rename(src, dst)
133 }
134 
copy_to_stdout(from: &Path) -> io::Result<()>135 pub fn copy_to_stdout(from: &Path) -> io::Result<()> {
136     let file = fs::File::open(from)?;
137     let mut reader = io::BufReader::new(file);
138     let mut stdout = io::stdout();
139     io::copy(&mut reader, &mut stdout)?;
140     Ok(())
141 }
142