Skip to main content

hyperlight_component_util/
util.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright 2025 The Hyperlight Authors.
3
4//! General utilities for bindgen macros
5use crate::etypes;
6
7/// Input accepted by component bindgen.
8#[derive(Debug)]
9pub enum WitSource {
10    Wasm(std::path::PathBuf),
11    Wat(std::path::PathBuf),
12    Wit(std::path::PathBuf),
13    Inline(String),
14}
15
16impl WitSource {
17    fn encode(self) -> Vec<u8> {
18        match self {
19            Self::Wasm(path) => {
20                let path = manifest_path(&path);
21                let bytes = std::fs::read(&path).unwrap_or_else(|err| {
22                    panic!(
23                        "failed to read wasm-encoded WIT input '{}': {err}",
24                        path.display()
25                    )
26                });
27                if !wasmparser::Parser::is_component(&bytes) {
28                    panic!(
29                        "wasm-encoded WIT input '{}' is not a wasm component",
30                        path.display()
31                    );
32                }
33                bytes
34            }
35            Self::Wat(path) => {
36                let path = manifest_path(&path);
37                let bytes = wat::parse_file(&path).unwrap_or_else(|err| {
38                    panic!("failed to read wat input '{}': {err:#}", path.display());
39                });
40                if !wasmparser::Parser::is_component(&bytes) {
41                    panic!("wat input '{}' is not a wasm component", path.display());
42                }
43                bytes
44            }
45            Self::Wit(path) => {
46                let path = manifest_path(&path);
47                let mut resolve = wit_parser::Resolve::default();
48                let (package, _) = resolve.push_path(&path).unwrap_or_else(|err| {
49                    panic!("failed to parse WIT input '{}': {err:#}", path.display())
50                });
51
52                wit_component::encode(&resolve, package).unwrap_or_else(|err| {
53                    panic!(
54                        "failed to encode WIT input '{}' as a wasm component type: {err:#}",
55                        path.display()
56                    )
57                })
58            }
59            Self::Inline(contents) => {
60                match wat::Detect::from_bytes(&contents) {
61                    wat::Detect::WasmBinary => {
62                        panic!("inline component type looks like a binary!")
63                    }
64                    wat::Detect::WasmText => {
65                        let bytes = wat::parse_str(&contents).unwrap_or_else(|err| {
66                            panic!("failed to read inline wat input: {err:#}")
67                        });
68                        if !wasmparser::Parser::is_component(&bytes) {
69                            panic!("inline wat input is not a wasm component");
70                        }
71                        bytes
72                    }
73                    wat::Detect::Unknown => {
74                        // It's probably WIT
75                        let mut resolve = wit_parser::Resolve::default();
76                        let package =
77                            resolve
78                                .push_str("inline.wit", &contents)
79                                .unwrap_or_else(|err| {
80                                    panic!("failed to parse inline WIT input: {err:#}")
81                                });
82                        wit_component::encode(&resolve, package).unwrap_or_else(|err| {
83                            panic!("failed to encode inline WIT input as a wasm component type: {err:#}")
84                        })
85                    }
86                }
87            }
88        }
89    }
90}
91
92fn manifest_path(path: &std::path::Path) -> std::path::PathBuf {
93    let manifest_dir = std::env::var("CARGO_MANIFEST_DIR").unwrap();
94    std::path::Path::new(&manifest_dir).join(path)
95}
96
97/// Read and parse a WIT type from a supported bindgen input.
98pub fn read_wit_type<R, F: FnMut(String, &etypes::Component) -> R>(
99    source: WitSource,
100    world_name: Option<String>,
101    mut cb: F,
102) -> R {
103    let bytes = source.encode();
104    let i = wasmparser::Parser::new(0).parse_all(&bytes);
105    let ct = crate::component::read_component_single_exported_type(i, world_name);
106
107    // because of the two-level encapsulation scheme, we need to look
108    // for the single export of the component type that we just read
109    if !ct.uvars.is_empty()
110        || !ct.imports.is_empty()
111        || !ct.instance.evars.is_empty()
112        || ct.instance.unqualified.exports.len() != 1
113    {
114        panic!("malformed component type container for wit type");
115    };
116    let export = &ct.instance.unqualified.exports[0];
117    use etypes::ExternDesc;
118    let ExternDesc::Component(ct) = &export.desc else {
119        panic!("malformed component type container: does not contain component type");
120    };
121    tracing::debug!("hcm: considering component type {:?}", ct);
122    cb(export.kebab_name.to_string(), ct)
123}
124
125/// Read and parse a wasm-encoded WIT file, relative to the cargo manifest
126/// directory.
127pub fn read_wit_type_from_file<R, F: FnMut(String, &etypes::Component) -> R>(
128    filename: impl AsRef<std::ffi::OsStr>,
129    world_name: Option<String>,
130    cb: F,
131) -> R {
132    let src = WitSource::Wasm(std::path::PathBuf::from(filename.as_ref()));
133    read_wit_type(src, world_name, cb)
134}
135
136/// Deal with `$HYPERLIGHT_COMPONENT_MACRO_DEBUG`: if it is present,
137/// save the given token stream (representing the result of
138/// macroexpansion) to the debug file and then return the token stream
139pub fn emit_decls(decls: proc_macro2::TokenStream, for_kebab: &str) -> proc_macro2::TokenStream {
140    if let Ok(dbg_out) = std::env::var("HYPERLIGHT_COMPONENT_MACRO_DEBUG") {
141        let fs_safe = for_kebab.replace("/", "_");
142        #[cfg(windows)]
143        let fs_safe = fs_safe.replace(":", "+");
144        let dbg_out = dbg_out.replace("#", &fs_safe);
145        if let Ok(file) = syn::parse2(decls.clone()) {
146            std::fs::write(&dbg_out, prettyplease::unparse(&file)).unwrap();
147        } else {
148            let decls = format!("{}", &decls);
149            std::fs::write(&dbg_out, &decls).unwrap();
150        }
151        quote::quote! { include!(#dbg_out); }
152    } else {
153        decls
154    }
155}