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 / elixir_libraries.rs
4.5 kB 134 lines
1// SPDX-License-Identifier: Apache-2.0 2// SPDX-FileCopyrightText: 2023 The Gleam contributors 3 4use crate::{ 5 Error, 6 error::ShellCommandFailureReason, 7 io::{Command, CommandExecutor, FileSystemReader, FileSystemWriter, Stdio}, 8}; 9use camino::Utf8PathBuf; 10 11#[cfg(not(target_os = "windows"))] 12const ELIXIR_EXECUTABLE: &str = "elixir"; 13#[cfg(target_os = "windows")] 14const ELIXIR_EXECUTABLE: &str = "elixir.bat"; 15 16// These Elixir core libs will be loaded with the current project 17const ELIXIR_LIBS: [&str; 4] = ["eex", "elixir", "logger", "mix"]; 18 19pub struct ElixirLibraries<'a, IO> { 20 io: &'a IO, 21 build_dir: &'a Utf8PathBuf, 22 subprocess_stdio: Stdio, 23} 24 25impl<'a, IO> ElixirLibraries<'a, IO> { 26 fn new(io: &'a IO, build_dir: &'a Utf8PathBuf, subprocess_stdio: Stdio) -> Self { 27 Self { 28 io, 29 build_dir, 30 subprocess_stdio, 31 } 32 } 33} 34 35impl<'a, IO> ElixirLibraries<'a, IO> 36where 37 IO: CommandExecutor + FileSystemReader + FileSystemWriter + Clone, 38{ 39 pub fn make_available( 40 io: &'a IO, 41 build_dir: &'a Utf8PathBuf, 42 subprocess_stdio: Stdio, 43 ) -> Result<(), Error> { 44 let it = Self::new(io, build_dir, subprocess_stdio); 45 let result = it.run(); 46 47 if result.is_err() { 48 it.cleanup(); 49 } 50 51 result 52 } 53 54 fn cleanup(&self) { 55 self.io 56 .delete_file(&self.paths_cache_path()) 57 .expect("deleting paths cache in cleanup"); 58 } 59 60 fn paths_cache_filename(&self) -> &'static str { 61 "gleam_elixir_paths" 62 } 63 64 fn paths_cache_path(&self) -> Utf8PathBuf { 65 self.build_dir.join(self.paths_cache_filename()) 66 } 67 68 fn run(&self) -> Result<(), Error> { 69 // The pathfinder is a file in build/{target}/erlang 70 // It contains the full path for each Elixir core lib we need, new-line delimited 71 // The pathfinder saves us from repeatedly loading Elixir to get this info 72 let mut update_links = false; 73 let cache = self.paths_cache_path(); 74 if !self.io.is_file(&cache) { 75 // The pathfinder must be written 76 // Any existing core lib links will get updated 77 update_links = true; 78 // TODO: test 79 let env = vec![("TERM".to_string(), "dumb".to_string())]; 80 // Prepare the libs for Erlang's code:lib_dir function 81 let elixir_atoms: Vec<String> = 82 ELIXIR_LIBS.iter().map(|lib| format!(":{}", lib)).collect(); 83 // Use Elixir to find its core lib paths and write the pathfinder file 84 let args = vec![ 85 "--eval".to_string(), 86 format!( 87 ":ok = File.write(~s({}), [{}] |> Stream.map(fn(lib) -> lib |> :code.lib_dir |> Path.expand end) |> Enum.join(~s(\\n)))", 88 self.paths_cache_filename(), 89 elixir_atoms.join(", "), 90 ), 91 ]; 92 tracing::debug!("writing_elixir_paths_to_build"); 93 let status = self.io.exec(Command { 94 program: ELIXIR_EXECUTABLE.into(), 95 args, 96 env, 97 cwd: Some(self.build_dir.clone()), 98 stdio: self.subprocess_stdio, 99 })?; 100 if status != 0 { 101 return Err(Error::ShellCommand { 102 program: "elixir".into(), 103 reason: ShellCommandFailureReason::Unknown, 104 }); 105 } 106 } 107 108 // Each pathfinder line is a system path for an Elixir core library 109 let read_pathfinder = self.io.read(&cache)?; 110 for lib_path in read_pathfinder.split('\n') { 111 let source = Utf8PathBuf::from(lib_path); 112 let name = source.as_path().file_name().expect(&format!( 113 "Unexpanded path in {}", 114 self.paths_cache_filename() 115 )); 116 let dest = self.build_dir.join(name); 117 let ebin = dest.join("ebin"); 118 if !update_links || self.io.is_directory(&ebin) { 119 // Either links don't need updating 120 // Or this library is already linked 121 continue; 122 } 123 // TODO: unit test 124 if self.io.is_directory(&dest) { 125 // Delete the existing link 126 self.io.delete_directory(&dest)?; 127 } 128 tracing::debug!("linking_{}_to_build", name,); 129 self.io.symlink_dir(&source, &dest)?; 130 } 131 132 Ok(()) 133 } 134}