Fork of daniellemaywood.uk/gleam — Wasm codegen work
2

Configure Feed

Select the types of activity you want to include in your feed.

gleam / compiler-core / src / build / module_loader.rs
5.9 kB 180 lines
1// SPDX-License-Identifier: Apache-2.0 2// SPDX-FileCopyrightText: 2023 The Gleam contributors 3 4#[cfg(test)] 5mod tests; 6 7use std::{collections::HashSet, time::SystemTime}; 8 9use camino::{Utf8Path, Utf8PathBuf}; 10 11use ecow::EcoString; 12use serde::{Deserialize, Serialize}; 13 14use super::{ 15 Mode, Origin, SourceFingerprint, Target, 16 package_compiler::{CacheMetadata, CachedModule, Input, UncompiledModule}, 17 package_loader::{CodegenRequired, GleamFile}, 18}; 19use crate::{ 20 Error, Result, 21 error::{FileIoAction, FileKind}, 22 io::{CommandExecutor, FileSystemReader, FileSystemWriter}, 23 warning::{TypeWarningEmitter, WarningEmitter}, 24}; 25 26#[derive(Debug)] 27pub(crate) struct ModuleLoader<'a, IO> { 28 pub io: IO, 29 pub warnings: &'a WarningEmitter, 30 pub mode: Mode, 31 pub target: Target, 32 pub codegen: CodegenRequired, 33 pub package_name: &'a EcoString, 34 pub artefact_directory: &'a Utf8Path, 35 pub origin: Origin, 36 /// The set of modules that have had partial compilation done since the last 37 /// successful compilation. 38 pub incomplete_modules: &'a HashSet<EcoString>, 39} 40 41impl<'a, IO> ModuleLoader<'a, IO> 42where 43 IO: FileSystemReader + FileSystemWriter + CommandExecutor + Clone, 44{ 45 /// Load a module from the given path. 46 /// 47 /// If the module has been compiled before and the source file has not been 48 /// changed since then, load the precompiled data instead. 49 /// 50 /// Whether the module has changed or not is determined by comparing the 51 /// modification time of the source file with the value recorded in the 52 /// `.timestamp` file in the artefact directory. 53 pub fn load(&self, file: GleamFile) -> Result<Input> { 54 let name = file.module_name.clone(); 55 let source_mtime = self.io.modification_time(&file.path)?; 56 57 let read_source = |name| self.read_source(file.path.clone(), name, source_mtime); 58 59 let meta = match self.read_cache_metadata(&file)? { 60 Some(meta) => meta, 61 None => return read_source(name).map(Input::New), 62 }; 63 64 // The cache currently does not contain enough data to perform codegen, 65 // so if codegen is required in this compiler run then we must check 66 // that codegen has already been performed before using a cache. 67 if self.codegen.is_required() && !meta.codegen_performed { 68 tracing::debug!(?name, "codegen_required_cache_insufficient"); 69 return read_source(name).map(Input::New); 70 } 71 72 // If the timestamp of the source is newer than the cache entry and 73 // the hash of the source differs from the one in the cache entry, 74 // then we need to recompile. 75 if meta.mtime < source_mtime { 76 let source_module = read_source(name.clone())?; 77 if meta.fingerprint != SourceFingerprint::new(&source_module.code) { 78 tracing::debug!(?name, "cache_stale"); 79 return Ok(Input::New(source_module)); 80 } else if self.mode == Mode::Lsp && self.incomplete_modules.contains(&name) { 81 // Since the lsp can have valid but incorrect intermediate code states between 82 // successful compilations, we need to invalidate the cache even if the fingerprint matches 83 tracing::debug!(?name, "cache_stale for lsp"); 84 return Ok(Input::New(source_module)); 85 } 86 } 87 88 Ok(Input::Cached(self.cached(file, meta))) 89 } 90 91 /// Read the cache metadata file from the artefact directory for the given 92 /// source file. If the file does not exist, return `None`. 93 fn read_cache_metadata(&self, source_file: &GleamFile) -> Result<Option<CacheMetadata>> { 94 let meta_path = source_file.cache_files(&self.artefact_directory).meta_path; 95 96 if !self.io.is_file(&meta_path) { 97 return Ok(None); 98 } 99 100 let binary = self.io.read_bytes(&meta_path)?; 101 let cache_metadata = CacheMetadata::from_binary(&binary).map_err(|e| -> Error { 102 Error::FileIo { 103 action: FileIoAction::Parse, 104 kind: FileKind::File, 105 path: meta_path, 106 err: Some(e), 107 } 108 })?; 109 Ok(Some(cache_metadata)) 110 } 111 112 fn read_source( 113 &self, 114 path: Utf8PathBuf, 115 name: EcoString, 116 mtime: SystemTime, 117 ) -> Result<UncompiledModule, Error> { 118 read_source( 119 self.io.clone(), 120 self.target, 121 self.origin, 122 path, 123 name, 124 self.package_name.clone(), 125 mtime, 126 self.warnings.clone(), 127 ) 128 } 129 130 fn cached(&self, file: GleamFile, meta: CacheMetadata) -> CachedModule { 131 CachedModule { 132 dependencies: meta.dependencies, 133 source_path: file.path, 134 origin: self.origin, 135 name: file.module_name, 136 line_numbers: meta.line_numbers, 137 } 138 } 139} 140 141pub(crate) fn read_source<IO>( 142 io: IO, 143 target: Target, 144 origin: Origin, 145 path: Utf8PathBuf, 146 name: EcoString, 147 package_name: EcoString, 148 mtime: SystemTime, 149 emitter: WarningEmitter, 150) -> Result<UncompiledModule> 151where 152 IO: FileSystemReader + FileSystemWriter + CommandExecutor + Clone, 153{ 154 let code: EcoString = io.read(&path)?.into(); 155 156 let parsed = crate::parse::parse_module(path.clone(), &code, &emitter).map_err(|error| { 157 Error::Parse { 158 path: path.clone(), 159 src: code.clone(), 160 error: Box::new(error), 161 } 162 })?; 163 let mut ast = parsed.module; 164 let extra = parsed.extra; 165 let dependencies = ast.dependencies(target); 166 167 ast.name = name.clone(); 168 let module = UncompiledModule { 169 package: package_name, 170 dependencies, 171 origin, 172 extra, 173 mtime, 174 path, 175 name, 176 code, 177 ast, 178 }; 179 Ok(module) 180}