-
Notifications
You must be signed in to change notification settings - Fork 266
Expand file tree
/
Copy pathwit-bindgen.rs
More file actions
179 lines (161 loc) · 5.58 KB
/
wit-bindgen.rs
File metadata and controls
179 lines (161 loc) · 5.58 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
use anyhow::{bail, Context, Result};
use clap::Parser;
use std::path::PathBuf;
use std::{fs, str};
use wit_bindgen_core::{wit_parser, Files, WorldGenerator};
use wit_parser::{Resolve, UnresolvedPackage};
/// Helper for passing VERSION to opt.
/// If CARGO_VERSION_INFO is set, use it, otherwise use CARGO_PKG_VERSION.
fn version() -> &'static str {
option_env!("CARGO_VERSION_INFO").unwrap_or(env!("CARGO_PKG_VERSION"))
}
#[derive(Debug, Parser)]
#[command(version = version())]
enum Opt {
/// This generator outputs a Markdown file describing an interface.
#[cfg(feature = "markdown")]
Markdown {
#[clap(flatten)]
opts: wit_bindgen_markdown::Opts,
#[clap(flatten)]
args: Common,
},
/// Generates bindings for Rust guest modules.
#[cfg(feature = "rust")]
Rust {
#[clap(flatten)]
opts: wit_bindgen_rust::Opts,
#[clap(flatten)]
args: Common,
},
/// Generates bindings for C/CPP guest modules.
#[cfg(feature = "c")]
C {
#[clap(flatten)]
opts: wit_bindgen_c::Opts,
#[clap(flatten)]
args: Common,
},
/// Generates bindings for TeaVM-based Java guest modules.
#[cfg(feature = "teavm-java")]
TeavmJava {
#[clap(flatten)]
opts: wit_bindgen_teavm_java::Opts,
#[clap(flatten)]
args: Common,
},
/// Generates bindings for TinyGo-based Go guest modules.
#[cfg(feature = "go")]
TinyGo {
#[clap(flatten)]
opts: wit_bindgen_go::Opts,
#[clap(flatten)]
args: Common,
},
}
#[derive(Debug, Parser)]
struct Common {
/// Where to place output files
#[clap(long = "out-dir")]
out_dir: Option<PathBuf>,
/// WIT document to generate bindings for.
#[clap(value_name = "DOCUMENT", index = 1)]
wit: PathBuf,
/// World within the WIT document specified to generate bindings for.
///
/// This can either be `foo` which is the default world in document `foo` or
/// it's `foo.bar` which is the world named `bar` within document `foo`.
#[clap(short, long)]
world: Option<String>,
/// Indicates that no files are written and instead files are checked if
/// they're up-to-date with the source files.
#[clap(long)]
check: bool,
/// Path to template substitutions for expansion.
#[clap(long)]
expand: Option<PathBuf>,
}
fn main() -> Result<()> {
let mut files = Files::default();
let (generator, opt) = match Opt::parse() {
#[cfg(feature = "markdown")]
Opt::Markdown { opts, args } => (opts.build(), args),
#[cfg(feature = "c")]
Opt::C { opts, args } => (opts.build(), args),
#[cfg(feature = "rust")]
Opt::Rust { opts, args } => (opts.build(), args),
#[cfg(feature = "teavm-java")]
Opt::TeavmJava { opts, args } => (opts.build(), args),
#[cfg(feature = "go")]
Opt::TinyGo { opts, args } => (opts.build(), args),
};
gen_world(generator, &opt, &mut files)?;
for (name, contents) in files.iter() {
let dst = match &opt.out_dir {
Some(path) => path.join(name),
None => name.into(),
};
println!("Generating {:?}", dst);
if opt.check {
let prev = std::fs::read(&dst).with_context(|| format!("failed to read {:?}", dst))?;
if prev != contents {
// The contents differ. If it looks like textual contents, do a
// line-by-line comparison so that we can tell users what the
// problem is directly.
if let (Ok(utf8_prev), Ok(utf8_contents)) =
(str::from_utf8(&prev), str::from_utf8(contents))
{
if !utf8_prev
.chars()
.any(|c| c.is_control() && !matches!(c, '\n' | '\r' | '\t'))
&& utf8_prev.lines().eq(utf8_contents.lines())
{
bail!("{} differs only in line endings (CRLF vs. LF). If this is a text file, configure git to mark the file as `text eol=lf`.", dst.display());
}
}
// The contents are binary or there are other differences; just
// issue a generic error.
bail!("not up to date: {}", dst.display());
}
continue;
}
if let Some(parent) = dst.parent() {
std::fs::create_dir_all(parent)
.with_context(|| format!("failed to create {:?}", parent))?;
}
std::fs::write(&dst, contents).with_context(|| format!("failed to write {:?}", dst))?;
}
Ok(())
}
fn gen_world(
mut generator: Box<dyn WorldGenerator>,
opts: &Common,
files: &mut Files,
) -> Result<()> {
let substitutions = match &opts.expand {
Some(path) => {
let input =
fs::read_to_string(path).context("failed to read substitutions from file")?;
toml::from_str(&input).context("failed to parse substitutions from TOML")?
}
None => Default::default(),
};
let mut resolve = Resolve::default();
let pkg = if opts.wit.is_dir() {
resolve.push_dir(&opts.wit)?.0
} else {
resolve.push(
UnresolvedPackage::parse_file(&opts.wit)?,
&Default::default(),
)?
};
wit_parser::expand(&mut resolve, substitutions)?;
let world = resolve.select_world(pkg, opts.world.as_deref())?;
generator.generate(&resolve, world, files);
Ok(())
}
#[test]
fn verify_cli() {
use clap::CommandFactory;
Opt::command().debug_assert()
}