]> git.lizzy.rs Git - rust.git/blob - compiler/rustc_incremental/src/persist/file_format.rs
Auto merge of #98655 - nnethercote:dont-derive-PartialEq-ne, r=dtolnay
[rust.git] / compiler / rustc_incremental / src / persist / file_format.rs
1 //! This module defines a generic file format that allows to check if a given
2 //! file generated by incremental compilation was generated by a compatible
3 //! compiler version. This file format is used for the on-disk version of the
4 //! dependency graph and the exported metadata hashes.
5 //!
6 //! In practice "compatible compiler version" means "exactly the same compiler
7 //! version", since the header encodes the git commit hash of the compiler.
8 //! Since we can always just ignore the incremental compilation cache and
9 //! compiler versions don't change frequently for the typical user, being
10 //! conservative here practically has no downside.
11
12 use std::env;
13 use std::fs;
14 use std::io::{self, Read};
15 use std::path::{Path, PathBuf};
16
17 use rustc_data_structures::memmap::Mmap;
18 use rustc_serialize::opaque::{FileEncodeResult, FileEncoder};
19 use rustc_serialize::Encoder;
20 use rustc_session::Session;
21
22 /// The first few bytes of files generated by incremental compilation.
23 const FILE_MAGIC: &[u8] = b"RSIC";
24
25 /// Change this if the header format changes.
26 const HEADER_FORMAT_VERSION: u16 = 0;
27
28 /// A version string that hopefully is always different for compiler versions
29 /// with different encodings of incremental compilation artifacts. Contains
30 /// the Git commit hash.
31 const RUSTC_VERSION: Option<&str> = option_env!("CFG_VERSION");
32
33 pub(crate) fn write_file_header(stream: &mut FileEncoder, nightly_build: bool) {
34     stream.emit_raw_bytes(FILE_MAGIC);
35     stream
36         .emit_raw_bytes(&[(HEADER_FORMAT_VERSION >> 0) as u8, (HEADER_FORMAT_VERSION >> 8) as u8]);
37
38     let rustc_version = rustc_version(nightly_build);
39     assert_eq!(rustc_version.len(), (rustc_version.len() as u8) as usize);
40     stream.emit_raw_bytes(&[rustc_version.len() as u8]);
41     stream.emit_raw_bytes(rustc_version.as_bytes());
42 }
43
44 pub(crate) fn save_in<F>(sess: &Session, path_buf: PathBuf, name: &str, encode: F)
45 where
46     F: FnOnce(FileEncoder) -> FileEncodeResult,
47 {
48     debug!("save: storing data in {}", path_buf.display());
49
50     // Delete the old file, if any.
51     // Note: It's important that we actually delete the old file and not just
52     // truncate and overwrite it, since it might be a shared hard-link, the
53     // underlying data of which we don't want to modify.
54     //
55     // We have to ensure we have dropped the memory maps to this file
56     // before performing this removal.
57     match fs::remove_file(&path_buf) {
58         Ok(()) => {
59             debug!("save: remove old file");
60         }
61         Err(err) if err.kind() == io::ErrorKind::NotFound => (),
62         Err(err) => {
63             sess.err(&format!(
64                 "unable to delete old {} at `{}`: {}",
65                 name,
66                 path_buf.display(),
67                 err
68             ));
69             return;
70         }
71     }
72
73     let mut encoder = match FileEncoder::new(&path_buf) {
74         Ok(encoder) => encoder,
75         Err(err) => {
76             sess.err(&format!("failed to create {} at `{}`: {}", name, path_buf.display(), err));
77             return;
78         }
79     };
80
81     write_file_header(&mut encoder, sess.is_nightly_build());
82
83     match encode(encoder) {
84         Ok(position) => {
85             sess.prof.artifact_size(
86                 &name.replace(' ', "_"),
87                 path_buf.file_name().unwrap().to_string_lossy(),
88                 position as u64,
89             );
90             debug!("save: data written to disk successfully");
91         }
92         Err(err) => {
93             sess.err(&format!("failed to write {} to `{}`: {}", name, path_buf.display(), err));
94         }
95     }
96 }
97
98 /// Reads the contents of a file with a file header as defined in this module.
99 ///
100 /// - Returns `Ok(Some(data, pos))` if the file existed and was generated by a
101 ///   compatible compiler version. `data` is the entire contents of the file
102 ///   and `pos` points to the first byte after the header.
103 /// - Returns `Ok(None)` if the file did not exist or was generated by an
104 ///   incompatible version of the compiler.
105 /// - Returns `Err(..)` if some kind of IO error occurred while reading the
106 ///   file.
107 pub fn read_file(
108     report_incremental_info: bool,
109     path: &Path,
110     nightly_build: bool,
111 ) -> io::Result<Option<(Mmap, usize)>> {
112     let file = match fs::File::open(path) {
113         Ok(file) => file,
114         Err(err) if err.kind() == io::ErrorKind::NotFound => return Ok(None),
115         Err(err) => return Err(err),
116     };
117     // SAFETY: This process must not modify nor remove the backing file while the memory map lives.
118     // For the dep-graph and the work product index, it is as soon as the decoding is done.
119     // For the query result cache, the memory map is dropped in save_dep_graph before calling
120     // save_in and trying to remove the backing file.
121     //
122     // There is no way to prevent another process from modifying this file.
123     let mmap = unsafe { Mmap::map(file) }?;
124
125     let mut file = io::Cursor::new(&*mmap);
126
127     // Check FILE_MAGIC
128     {
129         debug_assert!(FILE_MAGIC.len() == 4);
130         let mut file_magic = [0u8; 4];
131         file.read_exact(&mut file_magic)?;
132         if file_magic != FILE_MAGIC {
133             report_format_mismatch(report_incremental_info, path, "Wrong FILE_MAGIC");
134             return Ok(None);
135         }
136     }
137
138     // Check HEADER_FORMAT_VERSION
139     {
140         debug_assert!(::std::mem::size_of_val(&HEADER_FORMAT_VERSION) == 2);
141         let mut header_format_version = [0u8; 2];
142         file.read_exact(&mut header_format_version)?;
143         let header_format_version =
144             (header_format_version[0] as u16) | ((header_format_version[1] as u16) << 8);
145
146         if header_format_version != HEADER_FORMAT_VERSION {
147             report_format_mismatch(report_incremental_info, path, "Wrong HEADER_FORMAT_VERSION");
148             return Ok(None);
149         }
150     }
151
152     // Check RUSTC_VERSION
153     {
154         let mut rustc_version_str_len = [0u8; 1];
155         file.read_exact(&mut rustc_version_str_len)?;
156         let rustc_version_str_len = rustc_version_str_len[0] as usize;
157         let mut buffer = vec![0; rustc_version_str_len];
158         file.read_exact(&mut buffer)?;
159
160         if buffer != rustc_version(nightly_build).as_bytes() {
161             report_format_mismatch(report_incremental_info, path, "Different compiler version");
162             return Ok(None);
163         }
164     }
165
166     let post_header_start_pos = file.position() as usize;
167     Ok(Some((mmap, post_header_start_pos)))
168 }
169
170 fn report_format_mismatch(report_incremental_info: bool, file: &Path, message: &str) {
171     debug!("read_file: {}", message);
172
173     if report_incremental_info {
174         eprintln!(
175             "[incremental] ignoring cache artifact `{}`: {}",
176             file.file_name().unwrap().to_string_lossy(),
177             message
178         );
179     }
180 }
181
182 fn rustc_version(nightly_build: bool) -> String {
183     if nightly_build {
184         if let Some(val) = env::var_os("RUSTC_FORCE_RUSTC_VERSION") {
185             return val.to_string_lossy().into_owned();
186         }
187     }
188
189     RUSTC_VERSION
190         .expect(
191             "Cannot use rustc without explicit version for \
192                           incremental compilation",
193         )
194         .to_string()
195 }