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.rs
9.3 kB 350 lines
1#![allow(warnings)] 2 3mod elixir_libraries; 4mod module_loader; 5mod native_file_copier; 6pub mod package_compiler; 7mod package_loader; 8mod project_compiler; 9mod telemetry; 10 11#[cfg(test)] 12mod tests; 13 14pub use self::package_compiler::PackageCompiler; 15pub use self::package_loader::StaleTracker; 16pub use self::project_compiler::{Built, Options, ProjectCompiler}; 17pub use self::telemetry::{NullTelemetry, Telemetry}; 18 19use crate::ast::{ 20 CustomType, DefinitionLocation, TypedArg, TypedDefinition, TypedExpr, TypedFunction, 21 TypedPattern, TypedStatement, 22}; 23use crate::{ 24 ast::{Definition, SrcSpan, TypedModule}, 25 config::{self, PackageConfig}, 26 erlang, 27 error::{Error, FileIoAction, FileKind}, 28 io::OutputFile, 29 parse::extra::{Comment, ModuleExtra}, 30 type_, 31}; 32use camino::Utf8PathBuf; 33use ecow::EcoString; 34use itertools::Itertools; 35use serde::{Deserialize, Serialize}; 36use std::time::SystemTime; 37use std::{collections::HashMap, ffi::OsString, fs::DirEntry, iter::Peekable, process}; 38use strum::{Display, EnumIter, EnumString, EnumVariantNames, VariantNames}; 39 40#[derive( 41 Debug, 42 Serialize, 43 Deserialize, 44 Display, 45 EnumString, 46 EnumVariantNames, 47 EnumIter, 48 Clone, 49 Copy, 50 PartialEq, 51 Eq, 52)] 53#[strum(serialize_all = "lowercase")] 54pub enum Target { 55 #[strum(serialize = "erlang", serialize = "erl")] 56 #[serde(rename = "erlang", alias = "erl")] 57 Erlang, 58 #[strum(serialize = "javascript", serialize = "js")] 59 #[serde(rename = "javascript", alias = "js")] 60 JavaScript, 61} 62 63impl Target { 64 pub fn variant_strings() -> Vec<EcoString> { 65 Self::VARIANTS.iter().map(|s| (*s).into()).collect() 66 } 67 68 /// Returns `true` if the target is [`JavaScript`]. 69 /// 70 /// [`JavaScript`]: Target::JavaScript 71 #[must_use] 72 pub fn is_javascript(&self) -> bool { 73 matches!(self, Self::JavaScript) 74 } 75 76 /// Returns `true` if the target is [`Erlang`]. 77 /// 78 /// [`Erlang`]: Target::Erlang 79 #[must_use] 80 pub fn is_erlang(&self) -> bool { 81 matches!(self, Self::Erlang) 82 } 83} 84 85#[derive(Debug, PartialEq, Eq, Clone, Copy)] 86pub enum Codegen { 87 All, 88 DepsOnly, 89 None, 90} 91 92impl Codegen { 93 fn should_codegen(&self, is_root_package: bool) -> bool { 94 match self { 95 Codegen::All => true, 96 Codegen::DepsOnly => !is_root_package, 97 Codegen::None => false, 98 } 99 } 100} 101 102#[derive( 103 Debug, Serialize, Deserialize, Display, EnumString, EnumVariantNames, Clone, Copy, PartialEq, Eq, 104)] 105pub enum Runtime { 106 #[strum(serialize = "nodejs", serialize = "node")] 107 #[serde(rename = "nodejs", alias = "node")] 108 NodeJs, 109 #[strum(serialize = "deno")] 110 #[serde(rename = "deno")] 111 Deno, 112 #[strum(serialize = "bun")] 113 #[serde(rename = "bun")] 114 Bun, 115} 116 117impl Default for Runtime { 118 fn default() -> Self { 119 Self::NodeJs 120 } 121} 122 123#[derive(Debug)] 124pub enum TargetCodegenConfiguration { 125 JavaScript { 126 emit_typescript_definitions: bool, 127 prelude_location: Utf8PathBuf, 128 }, 129 Erlang { 130 app_file: Option<ErlangAppCodegenConfiguration>, 131 }, 132} 133 134impl TargetCodegenConfiguration { 135 pub fn target(&self) -> Target { 136 match self { 137 Self::JavaScript { .. } => Target::JavaScript, 138 Self::Erlang { .. } => Target::Erlang, 139 } 140 } 141} 142 143#[derive(Debug)] 144pub struct ErlangAppCodegenConfiguration { 145 pub include_dev_deps: bool, 146 /// Some packages have a different OTP application name than their package 147 /// name, as rebar3 (and Mix?) support this. The .app file must use the OTP 148 /// name, not the package name. 149 pub package_name_overrides: HashMap<EcoString, EcoString>, 150} 151 152#[derive( 153 Debug, 154 Serialize, 155 Deserialize, 156 Display, 157 EnumString, 158 EnumVariantNames, 159 EnumIter, 160 Clone, 161 Copy, 162 PartialEq, 163)] 164#[strum(serialize_all = "lowercase")] 165pub enum Mode { 166 Dev, 167 Prod, 168 Lsp, 169} 170 171impl Mode { 172 /// Returns `true` if the mode includes test code. 173 /// 174 pub fn includes_tests(&self) -> bool { 175 match self { 176 Self::Dev | Self::Lsp => true, 177 Self::Prod => false, 178 } 179 } 180} 181 182#[test] 183fn mode_includes_tests() { 184 assert!(Mode::Dev.includes_tests()); 185 assert!(Mode::Lsp.includes_tests()); 186 assert!(!Mode::Prod.includes_tests()); 187} 188 189#[derive(Debug)] 190pub struct Package { 191 pub config: PackageConfig, 192 pub modules: Vec<Module>, 193} 194 195impl Package { 196 pub fn attach_doc_and_module_comments(&mut self) { 197 for mut module in &mut self.modules { 198 module.attach_doc_and_module_comments(); 199 } 200 } 201 202 pub fn into_modules_hashmap(self) -> HashMap<String, Module> { 203 self.modules 204 .into_iter() 205 .map(|m| (m.name.to_string(), m)) 206 .collect() 207 } 208} 209 210#[derive(Debug)] 211pub struct Module { 212 pub name: EcoString, 213 pub code: EcoString, 214 pub mtime: SystemTime, 215 pub input_path: Utf8PathBuf, 216 pub origin: Origin, 217 pub ast: TypedModule, 218 pub extra: ModuleExtra, 219 pub dependencies: Vec<(EcoString, SrcSpan)>, 220} 221 222impl Module { 223 pub fn compiled_erlang_path(&self) -> Utf8PathBuf { 224 let mut path = self.name.replace("/", "@"); 225 path.push_str(".erl"); 226 Utf8PathBuf::from(path.as_ref()) 227 } 228 229 pub fn is_test(&self) -> bool { 230 self.origin == Origin::Test 231 } 232 233 pub fn find_node(&self, byte_index: u32) -> Option<Located<'_>> { 234 self.ast.find_node(byte_index) 235 } 236 237 pub fn attach_doc_and_module_comments(&mut self) { 238 // Module Comments 239 self.ast.documentation = self 240 .extra 241 .module_comments 242 .iter() 243 .map(|span| Comment::from((span, self.code.as_str())).content.into()) 244 .collect(); 245 246 // Order statements to avoid missociating doc comments after the order 247 // has changed during compilation. 248 let mut statements: Vec<_> = self.ast.definitions.iter_mut().collect(); 249 statements.sort_by(|a, b| a.location().start.cmp(&b.location().start)); 250 251 // Doc Comments 252 let mut doc_comments = self.extra.doc_comments.iter().peekable(); 253 for statement in &mut statements { 254 let docs: Vec<&str> = 255 comments_before(&mut doc_comments, statement.location().start, &self.code); 256 if !docs.is_empty() { 257 let doc = docs.join("\n").into(); 258 statement.put_doc(doc); 259 } 260 261 if let Definition::CustomType(CustomType { constructors, .. }) = statement { 262 for constructor in constructors { 263 let docs: Vec<&str> = 264 comments_before(&mut doc_comments, constructor.location.start, &self.code); 265 if !docs.is_empty() { 266 let doc = docs.join("\n").into(); 267 constructor.put_doc(doc); 268 } 269 270 for argument in constructor.arguments.iter_mut() { 271 let docs: Vec<&str> = 272 comments_before(&mut doc_comments, argument.location.start, &self.code); 273 if !docs.is_empty() { 274 let doc = docs.join("\n").into(); 275 argument.put_doc(doc); 276 } 277 } 278 } 279 } 280 } 281 } 282 283 pub(crate) fn dependencies_list(&self) -> Vec<EcoString> { 284 self.dependencies 285 .iter() 286 .map(|(name, _)| name.clone()) 287 .collect() 288 } 289} 290 291#[derive(Debug, Clone, PartialEq)] 292pub enum Located<'a> { 293 Pattern(&'a TypedPattern), 294 Statement(&'a TypedStatement), 295 Expression(&'a TypedExpr), 296 ModuleStatement(&'a TypedDefinition), 297 FunctionBody(&'a TypedFunction), 298 Arg(&'a TypedArg), 299} 300 301impl<'a> Located<'a> { 302 pub fn definition_location(&self) -> Option<DefinitionLocation<'_>> { 303 match self { 304 Self::Pattern(pattern) => pattern.definition_location(), 305 Self::Statement(statement) => statement.definition_location(), 306 Self::FunctionBody(statement) => None, 307 Self::Expression(expression) => expression.definition_location(), 308 Self::ModuleStatement(statement) => Some(DefinitionLocation { 309 module: None, 310 span: statement.location(), 311 }), 312 Self::Arg(_) => None, 313 } 314 } 315} 316 317#[derive(Debug, Clone, Copy, PartialEq, Eq)] 318pub enum Origin { 319 Src, 320 Test, 321} 322 323impl Origin { 324 /// Returns `true` if the origin is [`Src`]. 325 /// 326 /// [`Src`]: Origin::Src 327 #[must_use] 328 pub fn is_src(&self) -> bool { 329 matches!(self, Self::Src) 330 } 331} 332 333fn comments_before<'a>( 334 comment_spans: &mut Peekable<impl Iterator<Item = &'a SrcSpan>>, 335 byte: u32, 336 src: &'a str, 337) -> Vec<&'a str> { 338 let mut comments = vec![]; 339 while let Some(SrcSpan { start, .. }) = comment_spans.peek() { 340 if start <= &byte { 341 let comment = comment_spans 342 .next() 343 .expect("Comment before accessing next span"); 344 comments.push(Comment::from((comment, src)).content) 345 } else { 346 break; 347 } 348 } 349 comments 350}