Skip to content

Commit 3bef34b

Browse files
authored
[wasm-metadata] add support for OCI version and revision (#1949)
* [wasm-metadata] add support for OCI revision annotation * [wasm-metadata] add support for OCI version annotation
1 parent de978e1 commit 3bef34b

File tree

12 files changed

+328
-9
lines changed

12 files changed

+328
-9
lines changed

crates/wasm-metadata/src/add_metadata.rs

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
use crate::{rewrite_wasm, Author, Description, Homepage, Licenses, Producers, Source};
1+
use crate::{
2+
rewrite_wasm, Author, Description, Homepage, Licenses, Producers, Revision, Source, Version,
3+
};
24

35
use anyhow::Result;
46

@@ -45,6 +47,14 @@ pub struct AddMetadata {
4547
/// URL to find more information on the binary
4648
#[cfg_attr(feature = "clap", clap(long, value_name = "NAME"))]
4749
pub homepage: Option<Homepage>,
50+
51+
/// Source control revision identifier for the packaged software.
52+
#[cfg_attr(feature = "clap", clap(long, value_name = "NAME"))]
53+
pub revision: Option<Revision>,
54+
55+
/// Version of the packaged software
56+
#[cfg_attr(feature = "clap", clap(long, value_name = "NAME"))]
57+
pub version: Option<Version>,
4858
}
4959

5060
#[cfg(feature = "clap")]
@@ -67,6 +77,8 @@ impl AddMetadata {
6777
&self.licenses,
6878
&self.source,
6979
&self.homepage,
80+
&self.revision,
81+
&self.version,
7082
input,
7183
)
7284
}

crates/wasm-metadata/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
pub use add_metadata::AddMetadata;
66
pub use metadata::Metadata;
77
pub use names::{ComponentNames, ModuleNames};
8-
pub use oci_annotations::{Author, Description, Homepage, Licenses, Source};
8+
pub use oci_annotations::{Author, Description, Homepage, Licenses, Revision, Source, Version};
99
pub use payload::Payload;
1010
pub use producers::{Producers, ProducersField};
1111

crates/wasm-metadata/src/metadata.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
use serde_derive::Serialize;
22
use std::ops::Range;
33

4-
use crate::{Author, Description, Homepage, Licenses, Producers, Source};
4+
use crate::{Author, Description, Homepage, Licenses, Producers, Revision, Source, Version};
55

66
/// Metadata associated with a Wasm Component or Module
77
#[derive(Debug, Serialize, Default)]
@@ -21,6 +21,10 @@ pub struct Metadata {
2121
pub source: Option<Source>,
2222
/// URL to find more information on the binary
2323
pub homepage: Option<Homepage>,
24+
/// Source control revision identifier for the packaged software.
25+
pub revision: Option<Revision>,
26+
/// Version of the packaged software
27+
pub version: Option<Version>,
2428
/// Byte range of the module in the parent binary
2529
pub range: Range<usize>,
2630
}

crates/wasm-metadata/src/oci_annotations/mod.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,14 @@ pub use author::Author;
1919
pub use description::Description;
2020
pub use homepage::Homepage;
2121
pub use licenses::Licenses;
22+
pub use revision::Revision;
2223
pub use source::Source;
24+
pub use version::Version;
2325

2426
mod author;
2527
mod description;
2628
mod homepage;
2729
mod licenses;
30+
mod revision;
2831
mod source;
32+
mod version;
Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
use std::borrow::Cow;
2+
use std::fmt::{self, Display};
3+
use std::str::FromStr;
4+
5+
use anyhow::{ensure, Error, Result};
6+
use serde::Serialize;
7+
use wasm_encoder::{ComponentSection, CustomSection, Encode, Section};
8+
use wasmparser::CustomSectionReader;
9+
10+
/// Source control revision identifier for the packaged software.
11+
#[derive(Debug, Clone, PartialEq)]
12+
pub struct Revision(CustomSection<'static>);
13+
14+
impl Revision {
15+
/// Create a new instance of `Desrciption`.
16+
pub fn new<S: Into<Cow<'static, str>>>(s: S) -> Self {
17+
Self(CustomSection {
18+
name: "revision".into(),
19+
data: match s.into() {
20+
Cow::Borrowed(s) => Cow::Borrowed(s.as_bytes()),
21+
Cow::Owned(s) => Cow::Owned(s.into()),
22+
},
23+
})
24+
}
25+
26+
/// Parse an `revision` custom section from a wasm binary.
27+
pub(crate) fn parse_custom_section(reader: &CustomSectionReader<'_>) -> Result<Self> {
28+
ensure!(
29+
reader.name() == "revision",
30+
"The `revision` custom section should have a name of 'revision'"
31+
);
32+
let data = String::from_utf8(reader.data().to_owned())?;
33+
Ok(Self::new(data))
34+
}
35+
}
36+
37+
impl FromStr for Revision {
38+
type Err = Error;
39+
40+
fn from_str(s: &str) -> Result<Self, Self::Err> {
41+
Ok(Self::new(s.to_owned()))
42+
}
43+
}
44+
45+
impl Serialize for Revision {
46+
fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
47+
where
48+
S: serde::Serializer,
49+
{
50+
serializer.serialize_str(&self.to_string())
51+
}
52+
}
53+
54+
impl Display for Revision {
55+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
56+
// NOTE: this will never panic since we always guarantee the data is
57+
// encoded as utf8, even if we internally store it as [u8].
58+
let data = String::from_utf8(self.0.data.to_vec()).unwrap();
59+
write!(f, "{data}")
60+
}
61+
}
62+
63+
impl ComponentSection for Revision {
64+
fn id(&self) -> u8 {
65+
ComponentSection::id(&self.0)
66+
}
67+
}
68+
69+
impl Section for Revision {
70+
fn id(&self) -> u8 {
71+
Section::id(&self.0)
72+
}
73+
}
74+
75+
impl Encode for Revision {
76+
fn encode(&self, sink: &mut Vec<u8>) {
77+
self.0.encode(sink);
78+
}
79+
}
80+
81+
#[cfg(test)]
82+
mod test {
83+
use super::*;
84+
use wasm_encoder::Component;
85+
use wasmparser::Payload;
86+
87+
#[test]
88+
fn roundtrip() {
89+
let mut component = Component::new();
90+
component.section(&Revision::new("de978e17a80c1118f606fce919ba9b7d5a04a5ad"));
91+
let component = component.finish();
92+
93+
let mut parsed = false;
94+
for section in wasmparser::Parser::new(0).parse_all(&component) {
95+
if let Payload::CustomSection(reader) = section.unwrap() {
96+
let revision = Revision::parse_custom_section(&reader).unwrap();
97+
assert_eq!(
98+
revision.to_string(),
99+
"de978e17a80c1118f606fce919ba9b7d5a04a5ad"
100+
);
101+
parsed = true;
102+
}
103+
}
104+
assert!(parsed);
105+
}
106+
107+
#[test]
108+
fn serialize() {
109+
let revision = Revision::new("de978e17a80c1118f606fce919ba9b7d5a04a5ad");
110+
let json = serde_json::to_string(&revision).unwrap();
111+
assert_eq!(r#""de978e17a80c1118f606fce919ba9b7d5a04a5ad""#, json);
112+
}
113+
}
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
use std::borrow::Cow;
2+
use std::fmt::{self, Display};
3+
use std::str::FromStr;
4+
5+
use anyhow::{ensure, Error, Result};
6+
use serde::Serialize;
7+
use wasm_encoder::{ComponentSection, CustomSection, Encode, Section};
8+
use wasmparser::CustomSectionReader;
9+
10+
/// Version of the packaged software
11+
#[derive(Debug, Clone, PartialEq)]
12+
pub struct Version(CustomSection<'static>);
13+
14+
impl Version {
15+
/// Create a new instance of `Desrciption`.
16+
pub fn new<S: Into<Cow<'static, str>>>(s: S) -> Self {
17+
Self(CustomSection {
18+
name: "version".into(),
19+
data: match s.into() {
20+
Cow::Borrowed(s) => Cow::Borrowed(s.as_bytes()),
21+
Cow::Owned(s) => Cow::Owned(s.into()),
22+
},
23+
})
24+
}
25+
26+
/// Parse an `version` custom section from a wasm binary.
27+
pub(crate) fn parse_custom_section(reader: &CustomSectionReader<'_>) -> Result<Self> {
28+
ensure!(
29+
reader.name() == "version",
30+
"The `version` custom section should have a name of 'version'"
31+
);
32+
let data = String::from_utf8(reader.data().to_owned())?;
33+
Ok(Self::new(data))
34+
}
35+
}
36+
37+
impl FromStr for Version {
38+
type Err = Error;
39+
40+
fn from_str(s: &str) -> Result<Self, Self::Err> {
41+
Ok(Self::new(s.to_owned()))
42+
}
43+
}
44+
45+
impl Serialize for Version {
46+
fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
47+
where
48+
S: serde::Serializer,
49+
{
50+
serializer.serialize_str(&self.to_string())
51+
}
52+
}
53+
54+
impl Display for Version {
55+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
56+
// NOTE: this will never panic since we always guarantee the data is
57+
// encoded as utf8, even if we internally store it as [u8].
58+
let data = String::from_utf8(self.0.data.to_vec()).unwrap();
59+
write!(f, "{data}")
60+
}
61+
}
62+
63+
impl ComponentSection for Version {
64+
fn id(&self) -> u8 {
65+
ComponentSection::id(&self.0)
66+
}
67+
}
68+
69+
impl Section for Version {
70+
fn id(&self) -> u8 {
71+
Section::id(&self.0)
72+
}
73+
}
74+
75+
impl Encode for Version {
76+
fn encode(&self, sink: &mut Vec<u8>) {
77+
self.0.encode(sink);
78+
}
79+
}
80+
81+
#[cfg(test)]
82+
mod test {
83+
use super::*;
84+
use wasm_encoder::Component;
85+
use wasmparser::Payload;
86+
87+
#[test]
88+
fn roundtrip() {
89+
let mut component = Component::new();
90+
component.section(&Version::new("1.0.0"));
91+
let component = component.finish();
92+
93+
let mut parsed = false;
94+
for section in wasmparser::Parser::new(0).parse_all(&component) {
95+
if let Payload::CustomSection(reader) = section.unwrap() {
96+
let version = Version::parse_custom_section(&reader).unwrap();
97+
assert_eq!(version.to_string(), "1.0.0");
98+
parsed = true;
99+
}
100+
}
101+
assert!(parsed);
102+
}
103+
104+
#[test]
105+
fn serialize() {
106+
let version = Version::new("1.0.0");
107+
let json = serde_json::to_string(&version).unwrap();
108+
assert_eq!(r#""1.0.0""#, json);
109+
}
110+
}

crates/wasm-metadata/src/payload.rs

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ use wasmparser::{KnownCustom, Parser, Payload::*};
66

77
use crate::{
88
Author, ComponentNames, Description, Homepage, Licenses, Metadata, ModuleNames, Producers,
9-
Source,
9+
Revision, Source,
1010
};
1111

1212
/// Data representing either a Wasm Component or module
@@ -133,6 +133,22 @@ impl Payload {
133133
.metadata_mut();
134134
*homepage = Some(a);
135135
}
136+
KnownCustom::Unknown if c.name() == "revision" => {
137+
let a = Revision::parse_custom_section(&c)?;
138+
let Metadata { revision, .. } = output
139+
.last_mut()
140+
.expect("non-empty metadata stack")
141+
.metadata_mut();
142+
*revision = Some(a);
143+
}
144+
KnownCustom::Unknown if c.name() == "version" => {
145+
let a = crate::Version::parse_custom_section(&c)?;
146+
let Metadata { version, .. } = output
147+
.last_mut()
148+
.expect("non-empty metadata stack")
149+
.metadata_mut();
150+
*version = Some(a);
151+
}
136152
_ => {}
137153
},
138154
_ => {}

crates/wasm-metadata/src/producers.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -147,7 +147,9 @@ impl Producers {
147147
/// Merge into an existing wasm module. Rewrites the module with this producers section
148148
/// merged into its existing one, or adds this producers section if none is present.
149149
pub fn add_to_wasm(&self, input: &[u8]) -> Result<Vec<u8>> {
150-
rewrite_wasm(&None, self, &None, &None, &None, &None, &None, input)
150+
rewrite_wasm(
151+
&None, self, &None, &None, &None, &None, &None, &None, &None, input,
152+
)
151153
}
152154
}
153155

crates/wasm-metadata/src/rewrite.rs

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
use crate::{
2-
Author, ComponentNames, Description, Homepage, Licenses, ModuleNames, Producers, Source,
2+
Author, ComponentNames, Description, Homepage, Licenses, ModuleNames, Producers, Revision,
3+
Source,
34
};
45
use anyhow::Result;
56
use std::mem;
@@ -15,6 +16,8 @@ pub(crate) fn rewrite_wasm(
1516
add_licenses: &Option<Licenses>,
1617
add_source: &Option<Source>,
1718
add_homepage: &Option<Homepage>,
19+
add_revision: &Option<Revision>,
20+
add_version: &Option<crate::Version>,
1821
input: &[u8],
1922
) -> Result<Vec<u8>> {
2023
let mut producers_found = false;
@@ -116,6 +119,20 @@ pub(crate) fn rewrite_wasm(
116119
continue;
117120
}
118121
}
122+
KnownCustom::Unknown if c.name() == "revision" => {
123+
if add_source.is_none() {
124+
let revision = Revision::parse_custom_section(c)?;
125+
revision.append_to(&mut output);
126+
continue;
127+
}
128+
}
129+
KnownCustom::Unknown if c.name() == "version" => {
130+
if add_version.is_none() {
131+
let version = crate::Version::parse_custom_section(c)?;
132+
version.append_to(&mut output);
133+
continue;
134+
}
135+
}
119136
_ => {}
120137
}
121138
}
@@ -160,5 +177,11 @@ pub(crate) fn rewrite_wasm(
160177
if let Some(homepage) = add_homepage {
161178
homepage.append_to(&mut output);
162179
}
180+
if let Some(revision) = add_revision {
181+
revision.append_to(&mut output);
182+
}
183+
if let Some(version) = add_version {
184+
version.append_to(&mut output);
185+
}
163186
Ok(output)
164187
}

0 commit comments

Comments
 (0)