Skip to content

Commit

Permalink
fix(compile): output contents of embedded file system (denoland#27302)
Browse files Browse the repository at this point in the history
  • Loading branch information
dsherret authored Dec 11, 2024
1 parent 9df6be8 commit c6fa628
Show file tree
Hide file tree
Showing 10 changed files with 344 additions and 136 deletions.
59 changes: 31 additions & 28 deletions cli/standalone/binary.rs
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ use super::serialization::DenoCompileModuleData;
use super::serialization::DeserializedDataSection;
use super::serialization::RemoteModulesStore;
use super::serialization::RemoteModulesStoreBuilder;
use super::virtual_fs::output_vfs;
use super::virtual_fs::FileBackedVfs;
use super::virtual_fs::VfsBuilder;
use super::virtual_fs::VfsFileSubDataKind;
Expand Down Expand Up @@ -367,6 +368,16 @@ pub fn extract_standalone(
}))
}

pub struct WriteBinOptions<'a> {
pub writer: File,
pub display_output_filename: &'a str,
pub graph: &'a ModuleGraph,
pub root_dir_url: StandaloneRelativeFileBaseUrl<'a>,
pub entrypoint: &'a ModuleSpecifier,
pub include_files: &'a [ModuleSpecifier],
pub compile_flags: &'a CompileFlags,
}

pub struct DenoCompileBinaryWriter<'a> {
cjs_tracker: &'a CjsTracker,
cli_options: &'a CliOptions,
Expand Down Expand Up @@ -407,18 +418,14 @@ impl<'a> DenoCompileBinaryWriter<'a> {

pub async fn write_bin(
&self,
writer: File,
graph: &ModuleGraph,
root_dir_url: StandaloneRelativeFileBaseUrl<'_>,
entrypoint: &ModuleSpecifier,
include_files: &[ModuleSpecifier],
compile_flags: &CompileFlags,
options: WriteBinOptions<'_>,
) -> Result<(), AnyError> {
// Select base binary based on target
let mut original_binary = self.get_base_binary(compile_flags).await?;
let mut original_binary =
self.get_base_binary(options.compile_flags).await?;

if compile_flags.no_terminal {
let target = compile_flags.resolve_target();
if options.compile_flags.no_terminal {
let target = options.compile_flags.resolve_target();
if !target.contains("windows") {
bail!(
"The `--no-terminal` flag is only available when targeting Windows (current: {})",
Expand All @@ -428,26 +435,16 @@ impl<'a> DenoCompileBinaryWriter<'a> {
set_windows_binary_to_gui(&mut original_binary)
.context("Setting windows binary to GUI.")?;
}
if compile_flags.icon.is_some() {
let target = compile_flags.resolve_target();
if options.compile_flags.icon.is_some() {
let target = options.compile_flags.resolve_target();
if !target.contains("windows") {
bail!(
"The `--icon` flag is only available when targeting Windows (current: {})",
target,
)
}
}
self
.write_standalone_binary(
writer,
original_binary,
graph,
root_dir_url,
entrypoint,
include_files,
compile_flags,
)
.await
self.write_standalone_binary(options, original_binary).await
}

async fn get_base_binary(
Expand Down Expand Up @@ -552,14 +549,18 @@ impl<'a> DenoCompileBinaryWriter<'a> {
#[allow(clippy::too_many_arguments)]
async fn write_standalone_binary(
&self,
writer: File,
options: WriteBinOptions<'_>,
original_bin: Vec<u8>,
graph: &ModuleGraph,
root_dir_url: StandaloneRelativeFileBaseUrl<'_>,
entrypoint: &ModuleSpecifier,
include_files: &[ModuleSpecifier],
compile_flags: &CompileFlags,
) -> Result<(), AnyError> {
let WriteBinOptions {
writer,
display_output_filename,
graph,
root_dir_url,
entrypoint,
include_files,
compile_flags,
} = options;
let ca_data = match self.cli_options.ca_data() {
Some(CaData::File(ca_file)) => Some(
std::fs::read(ca_file).with_context(|| format!("Reading {ca_file}"))?,
Expand Down Expand Up @@ -784,6 +785,8 @@ impl<'a> DenoCompileBinaryWriter<'a> {
otel_config: self.cli_options.otel_config(),
};

output_vfs(&vfs, display_output_filename);

write_binary_bytes(
writer,
original_bin,
Expand Down
168 changes: 168 additions & 0 deletions cli/standalone/virtual_fs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ use serde::Serialize;
use thiserror::Error;

use crate::util;
use crate::util::display::DisplayTreeNode;
use crate::util::fs::canonicalize_path;

#[derive(Debug, Copy, Clone)]
Expand All @@ -51,6 +52,7 @@ pub struct StripRootError {
target: PathBuf,
}

#[derive(Debug)]
pub struct VfsBuilder {
root_path: PathBuf,
root_dir: VirtualDirectory,
Expand Down Expand Up @@ -364,6 +366,125 @@ impl VfsBuilder {
}
}

pub fn output_vfs(builder: &VfsBuilder, executable_name: &str) {
if !log::log_enabled!(log::Level::Info) {
return; // no need to compute if won't output
}

if builder.root_dir.entries.is_empty() {
return; // nothing to output
}

let mut text = String::new();
let display_tree = vfs_as_display_tree(builder, executable_name);
display_tree.print(&mut text).unwrap(); // unwrap ok because it's writing to a string
log::info!(
"\n{}\n",
deno_terminal::colors::bold("Embedded File System")
);
log::info!("{}\n", text.trim());
}

fn vfs_as_display_tree(
builder: &VfsBuilder,
executable_name: &str,
) -> DisplayTreeNode {
enum EntryOutput<'a> {
All,
Subset(Vec<DirEntryOutput<'a>>),
File,
Symlink(&'a [String]),
}

impl<'a> EntryOutput<'a> {
pub fn as_display_tree(&self, name: String) -> DisplayTreeNode {
DisplayTreeNode {
text: match self {
EntryOutput::All | EntryOutput::Subset(_) | EntryOutput::File => name,
EntryOutput::Symlink(parts) => {
format!("{} --> {}", name, parts.join("/"))
}
},
children: match self {
EntryOutput::All => vec![DisplayTreeNode::from_text("*".to_string())],
EntryOutput::Subset(vec) => vec
.iter()
.map(|e| e.output.as_display_tree(e.name.to_string()))
.collect(),
EntryOutput::File | EntryOutput::Symlink(_) => vec![],
},
}
}
}

pub struct DirEntryOutput<'a> {
name: &'a str,
output: EntryOutput<'a>,
}

fn include_all_entries<'a>(
dir: &Path,
vfs_dir: &'a VirtualDirectory,
) -> EntryOutput<'a> {
EntryOutput::Subset(
vfs_dir
.entries
.iter()
.map(|entry| DirEntryOutput {
name: entry.name(),
output: analyze_entry(&dir.join(entry.name()), entry),
})
.collect(),
)
}

fn analyze_entry<'a>(path: &Path, entry: &'a VfsEntry) -> EntryOutput<'a> {
match entry {
VfsEntry::Dir(virtual_directory) => analyze_dir(path, virtual_directory),
VfsEntry::File(_) => EntryOutput::File,
VfsEntry::Symlink(virtual_symlink) => {
EntryOutput::Symlink(&virtual_symlink.dest_parts)
}
}
}

fn analyze_dir<'a>(
dir: &Path,
vfs_dir: &'a VirtualDirectory,
) -> EntryOutput<'a> {
let real_entry_count = std::fs::read_dir(dir)
.ok()
.map(|entries| entries.flat_map(|e| e.ok()).count())
.unwrap_or(0);
if real_entry_count == vfs_dir.entries.len() {
let children = vfs_dir
.entries
.iter()
.map(|entry| DirEntryOutput {
name: entry.name(),
output: analyze_entry(&dir.join(entry.name()), entry),
})
.collect::<Vec<_>>();
if children
.iter()
.all(|c| !matches!(c.output, EntryOutput::Subset(_)))
{
EntryOutput::All
} else {
EntryOutput::Subset(children)
}
} else {
include_all_entries(dir, vfs_dir)
}
}

// always include all the entries for the root directory, otherwise the
// user might not have context about what's being shown
let output = include_all_entries(&builder.root_path, &builder.root_dir);
output
.as_display_tree(deno_terminal::colors::italic(executable_name).to_string())
}

#[derive(Debug)]
enum VfsEntryRef<'a> {
Dir(&'a VirtualDirectory),
Expand Down Expand Up @@ -1013,6 +1134,7 @@ impl FileBackedVfs {

#[cfg(test)]
mod test {
use console_static_text::ansi::strip_ansi_codes;
use std::io::Write;
use test_util::TempDir;

Expand Down Expand Up @@ -1299,4 +1421,50 @@ mod test {
.unwrap();
assert_eq!(all_buf.to_vec(), b"123456789");
}

#[test]
fn test_vfs_as_display_tree() {
let temp_dir = TempDir::new();
temp_dir.write("root.txt", "");
temp_dir.create_dir_all("a");
temp_dir.write("a/a.txt", "");
temp_dir.write("a/b.txt", "");
temp_dir.create_dir_all("b");
temp_dir.write("b/a.txt", "");
temp_dir.write("b/b.txt", "");
temp_dir.create_dir_all("c");
temp_dir.write("c/a.txt", "contents");
temp_dir.symlink_file("c/a.txt", "c/b.txt");
assert_eq!(temp_dir.read_to_string("c/b.txt"), "contents"); // ensure the symlink works
let mut vfs_builder =
VfsBuilder::new(temp_dir.path().to_path_buf()).unwrap();
// full dir
vfs_builder
.add_dir_recursive(temp_dir.path().join("a").as_path())
.unwrap();
// part of the dir
vfs_builder
.add_file_at_path(temp_dir.path().join("b/a.txt").as_path())
.unwrap();
// symlink
vfs_builder
.add_dir_recursive(temp_dir.path().join("c").as_path())
.unwrap();
temp_dir.write("c/c.txt", ""); // write an extra file so it shows the whole directory
let node = vfs_as_display_tree(&vfs_builder, "executable");
let mut text = String::new();
node.print(&mut text).unwrap();
assert_eq!(
strip_ansi_codes(&text),
r#"executable
├─┬ a
│ └── *
├─┬ b
│ └── a.txt
└─┬ c
├── a.txt
└── b.txt --> c/a.txt
"#
);
}
}
Loading

0 comments on commit c6fa628

Please sign in to comment.