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