summaryrefslogtreecommitdiffstats
path: root/compiler/rustc_incremental/src/persist/file_format.rs
diff options
context:
space:
mode:
Diffstat (limited to 'compiler/rustc_incremental/src/persist/file_format.rs')
-rw-r--r--compiler/rustc_incremental/src/persist/file_format.rs195
1 files changed, 195 insertions, 0 deletions
diff --git a/compiler/rustc_incremental/src/persist/file_format.rs b/compiler/rustc_incremental/src/persist/file_format.rs
new file mode 100644
index 000000000..2dbd4b6bc
--- /dev/null
+++ b/compiler/rustc_incremental/src/persist/file_format.rs
@@ -0,0 +1,195 @@
+//! This module defines a generic file format that allows to check if a given
+//! file generated by incremental compilation was generated by a compatible
+//! compiler version. This file format is used for the on-disk version of the
+//! dependency graph and the exported metadata hashes.
+//!
+//! In practice "compatible compiler version" means "exactly the same compiler
+//! version", since the header encodes the git commit hash of the compiler.
+//! Since we can always just ignore the incremental compilation cache and
+//! compiler versions don't change frequently for the typical user, being
+//! conservative here practically has no downside.
+
+use std::env;
+use std::fs;
+use std::io::{self, Read};
+use std::path::{Path, PathBuf};
+
+use rustc_data_structures::memmap::Mmap;
+use rustc_serialize::opaque::{FileEncodeResult, FileEncoder};
+use rustc_serialize::Encoder;
+use rustc_session::Session;
+
+/// The first few bytes of files generated by incremental compilation.
+const FILE_MAGIC: &[u8] = b"RSIC";
+
+/// Change this if the header format changes.
+const HEADER_FORMAT_VERSION: u16 = 0;
+
+/// A version string that hopefully is always different for compiler versions
+/// with different encodings of incremental compilation artifacts. Contains
+/// the Git commit hash.
+const RUSTC_VERSION: Option<&str> = option_env!("CFG_VERSION");
+
+pub(crate) fn write_file_header(stream: &mut FileEncoder, nightly_build: bool) {
+ stream.emit_raw_bytes(FILE_MAGIC);
+ stream
+ .emit_raw_bytes(&[(HEADER_FORMAT_VERSION >> 0) as u8, (HEADER_FORMAT_VERSION >> 8) as u8]);
+
+ let rustc_version = rustc_version(nightly_build);
+ assert_eq!(rustc_version.len(), (rustc_version.len() as u8) as usize);
+ stream.emit_raw_bytes(&[rustc_version.len() as u8]);
+ stream.emit_raw_bytes(rustc_version.as_bytes());
+}
+
+pub(crate) fn save_in<F>(sess: &Session, path_buf: PathBuf, name: &str, encode: F)
+where
+ F: FnOnce(FileEncoder) -> FileEncodeResult,
+{
+ debug!("save: storing data in {}", path_buf.display());
+
+ // Delete the old file, if any.
+ // Note: It's important that we actually delete the old file and not just
+ // truncate and overwrite it, since it might be a shared hard-link, the
+ // underlying data of which we don't want to modify.
+ //
+ // We have to ensure we have dropped the memory maps to this file
+ // before performing this removal.
+ match fs::remove_file(&path_buf) {
+ Ok(()) => {
+ debug!("save: remove old file");
+ }
+ Err(err) if err.kind() == io::ErrorKind::NotFound => (),
+ Err(err) => {
+ sess.err(&format!(
+ "unable to delete old {} at `{}`: {}",
+ name,
+ path_buf.display(),
+ err
+ ));
+ return;
+ }
+ }
+
+ let mut encoder = match FileEncoder::new(&path_buf) {
+ Ok(encoder) => encoder,
+ Err(err) => {
+ sess.err(&format!("failed to create {} at `{}`: {}", name, path_buf.display(), err));
+ return;
+ }
+ };
+
+ write_file_header(&mut encoder, sess.is_nightly_build());
+
+ match encode(encoder) {
+ Ok(position) => {
+ sess.prof.artifact_size(
+ &name.replace(' ', "_"),
+ path_buf.file_name().unwrap().to_string_lossy(),
+ position as u64,
+ );
+ debug!("save: data written to disk successfully");
+ }
+ Err(err) => {
+ sess.err(&format!("failed to write {} to `{}`: {}", name, path_buf.display(), err));
+ }
+ }
+}
+
+/// Reads the contents of a file with a file header as defined in this module.
+///
+/// - Returns `Ok(Some(data, pos))` if the file existed and was generated by a
+/// compatible compiler version. `data` is the entire contents of the file
+/// and `pos` points to the first byte after the header.
+/// - Returns `Ok(None)` if the file did not exist or was generated by an
+/// incompatible version of the compiler.
+/// - Returns `Err(..)` if some kind of IO error occurred while reading the
+/// file.
+pub fn read_file(
+ report_incremental_info: bool,
+ path: &Path,
+ nightly_build: bool,
+) -> io::Result<Option<(Mmap, usize)>> {
+ let file = match fs::File::open(path) {
+ Ok(file) => file,
+ Err(err) if err.kind() == io::ErrorKind::NotFound => return Ok(None),
+ Err(err) => return Err(err),
+ };
+ // SAFETY: This process must not modify nor remove the backing file while the memory map lives.
+ // For the dep-graph and the work product index, it is as soon as the decoding is done.
+ // For the query result cache, the memory map is dropped in save_dep_graph before calling
+ // save_in and trying to remove the backing file.
+ //
+ // There is no way to prevent another process from modifying this file.
+ let mmap = unsafe { Mmap::map(file) }?;
+
+ let mut file = io::Cursor::new(&*mmap);
+
+ // Check FILE_MAGIC
+ {
+ debug_assert!(FILE_MAGIC.len() == 4);
+ let mut file_magic = [0u8; 4];
+ file.read_exact(&mut file_magic)?;
+ if file_magic != FILE_MAGIC {
+ report_format_mismatch(report_incremental_info, path, "Wrong FILE_MAGIC");
+ return Ok(None);
+ }
+ }
+
+ // Check HEADER_FORMAT_VERSION
+ {
+ debug_assert!(::std::mem::size_of_val(&HEADER_FORMAT_VERSION) == 2);
+ let mut header_format_version = [0u8; 2];
+ file.read_exact(&mut header_format_version)?;
+ let header_format_version =
+ (header_format_version[0] as u16) | ((header_format_version[1] as u16) << 8);
+
+ if header_format_version != HEADER_FORMAT_VERSION {
+ report_format_mismatch(report_incremental_info, path, "Wrong HEADER_FORMAT_VERSION");
+ return Ok(None);
+ }
+ }
+
+ // Check RUSTC_VERSION
+ {
+ let mut rustc_version_str_len = [0u8; 1];
+ file.read_exact(&mut rustc_version_str_len)?;
+ let rustc_version_str_len = rustc_version_str_len[0] as usize;
+ let mut buffer = vec![0; rustc_version_str_len];
+ file.read_exact(&mut buffer)?;
+
+ if buffer != rustc_version(nightly_build).as_bytes() {
+ report_format_mismatch(report_incremental_info, path, "Different compiler version");
+ return Ok(None);
+ }
+ }
+
+ let post_header_start_pos = file.position() as usize;
+ Ok(Some((mmap, post_header_start_pos)))
+}
+
+fn report_format_mismatch(report_incremental_info: bool, file: &Path, message: &str) {
+ debug!("read_file: {}", message);
+
+ if report_incremental_info {
+ eprintln!(
+ "[incremental] ignoring cache artifact `{}`: {}",
+ file.file_name().unwrap().to_string_lossy(),
+ message
+ );
+ }
+}
+
+fn rustc_version(nightly_build: bool) -> String {
+ if nightly_build {
+ if let Some(val) = env::var_os("RUSTC_FORCE_RUSTC_VERSION") {
+ return val.to_string_lossy().into_owned();
+ }
+ }
+
+ RUSTC_VERSION
+ .expect(
+ "Cannot use rustc without explicit version for \
+ incremental compilation",
+ )
+ .to_string()
+}