Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion crates/wasm-metadata/src/add_metadata.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use crate::{rewrite_wasm, Author, Description, Producers, RegistryMetadata};
use crate::{rewrite_wasm, Author, Description, Licenses, Producers, RegistryMetadata};

use anyhow::Result;

Expand Down Expand Up @@ -34,6 +34,10 @@ pub struct AddMetadata {
#[cfg_attr(feature = "clap", clap(long, value_name = "NAME"))]
pub description: Option<Description>,

/// License(s) under which contained software is distributed as an SPDX License Expression.
#[cfg_attr(feature = "clap", clap(long, value_name = "NAME"))]
pub licenses: Option<Licenses>,

/// Add an registry metadata to the registry-metadata section
#[cfg_attr(feature="clap", clap(long, value_parser = parse_registry_metadata_value, value_name="PATH"))]
pub registry_metadata: Option<RegistryMetadata>,
Expand Down Expand Up @@ -65,6 +69,7 @@ impl AddMetadata {
&Producers::from_meta(self),
&self.author,
&self.description,
&self.licenses,
self.registry_metadata.as_ref(),
input,
)
Expand Down
2 changes: 1 addition & 1 deletion crates/wasm-metadata/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
pub use add_metadata::AddMetadata;
pub use metadata::Metadata;
pub use names::{ComponentNames, ModuleNames};
pub use oci_annotations::{Author, Description};
pub use oci_annotations::{Author, Description, Licenses};
pub use producers::{Producers, ProducersField};
pub use registry::{CustomLicense, Link, LinkType, RegistryMetadata};

Expand Down
17 changes: 16 additions & 1 deletion crates/wasm-metadata/src/metadata.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@ use std::fmt;
use std::ops::Range;
use wasmparser::{KnownCustom, Parser, Payload::*};

use crate::{Author, ComponentNames, Description, ModuleNames, Producers, RegistryMetadata};
use crate::{
Author, ComponentNames, Description, Licenses, ModuleNames, Producers, RegistryMetadata,
};

/// A tree of the metadata found in a WebAssembly binary.
#[derive(Debug, Serialize)]
Expand All @@ -22,6 +24,8 @@ pub enum Metadata {
author: Option<Author>,
/// Human-readable description of the binary
description: Option<Description>,
/// License(s) under which contained software is distributed as an SPDX License Expression.
licenses: Option<Licenses>,
/// All child modules and components inside the component.
children: Vec<Box<Metadata>>,
/// Byte range of the module in the parent binary
Expand All @@ -39,6 +43,8 @@ pub enum Metadata {
author: Option<Author>,
/// Human-readable description of the binary
description: Option<Description>,
/// License(s) under which contained software is distributed as an SPDX License Expression.
licenses: Option<Licenses>,
/// Byte range of the module in the parent binary
range: Range<usize>,
},
Expand Down Expand Up @@ -128,6 +134,13 @@ impl Metadata {
Metadata::Component { description, .. } => *description = Some(a),
}
}
KnownCustom::Unknown if c.name() == "licenses" => {
let a = Licenses::parse_custom_section(&c)?;
match metadata.last_mut().expect("non-empty metadata stack") {
Metadata::Module { licenses, .. } => *licenses = Some(a),
Metadata::Component { licenses, .. } => *licenses = Some(a),
}
}
_ => {}
},
_ => {}
Expand All @@ -144,6 +157,7 @@ impl Metadata {
producers: None,
author: None,
description: None,
licenses: None,
registry_metadata: None,
children: Vec::new(),
range,
Expand All @@ -156,6 +170,7 @@ impl Metadata {
producers: None,
author: None,
description: None,
licenses: None,
registry_metadata: None,
range,
}
Expand Down
122 changes: 122 additions & 0 deletions crates/wasm-metadata/src/oci_annotations/licenses.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
use std::borrow::Cow;
use std::fmt::{self, Display};
use std::str::FromStr;

use anyhow::{ensure, Error, Result};
use serde::Serialize;
use wasm_encoder::{ComponentSection, CustomSection, Encode, Section};
use wasmparser::CustomSectionReader;

/// License(s) under which contained software is distributed as an SPDX License Expression.
#[derive(Debug, Clone, PartialEq)]
pub struct Licenses(CustomSection<'static>);

impl Licenses {
/// Create a new instance of `Licenses`.
pub fn new(s: &str) -> Result<Self> {
Ok(spdx::Expression::parse(s)?.into())
}

/// Parse a `licenses` custom section from a wasm binary.
pub(crate) fn parse_custom_section(reader: &CustomSectionReader<'_>) -> Result<Self> {
ensure!(
reader.name() == "licenses",
"The `licenses` custom section should have a name of 'license'"
);
let data = String::from_utf8(reader.data().to_owned())?;
Self::new(&data)
}
}

impl FromStr for Licenses {
type Err = Error;

fn from_str(s: &str) -> Result<Self, Self::Err> {
Self::new(s)
}
}

impl From<spdx::Expression> for Licenses {
fn from(expression: spdx::Expression) -> Self {
Self(CustomSection {
name: "licenses".into(),
data: Cow::Owned(expression.to_string().into_bytes()),
})
}
}

impl Serialize for Licenses {
fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
serializer.serialize_str(&self.to_string())
}
}

impl Display for Licenses {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
// NOTE: this will never panic since we always guarantee the data is
// encoded as utf8, even if we internally store it as [u8].
let data = String::from_utf8(self.0.data.to_vec()).unwrap();
write!(f, "{data}")
}
}

impl ComponentSection for Licenses {
fn id(&self) -> u8 {
ComponentSection::id(&self.0)
}
}

impl Section for Licenses {
fn id(&self) -> u8 {
Section::id(&self.0)
}
}

impl Encode for Licenses {
fn encode(&self, sink: &mut Vec<u8>) {
self.0.encode(sink);
}
}

#[cfg(test)]
mod test {
use super::*;
use wasm_encoder::Component;
use wasmparser::Payload;

#[test]
fn roundtrip() {
let mut component = Component::new();
component.section(
&Licenses::new("Apache-2.0 WITH LLVM-exception OR Apache-2.0 OR MIT").unwrap(),
);
let component = component.finish();

let mut parsed = false;
for section in wasmparser::Parser::new(0).parse_all(&component) {
if let Payload::CustomSection(reader) = section.unwrap() {
let description = Licenses::parse_custom_section(&reader).unwrap();
assert_eq!(
description.to_string(),
"Apache-2.0 WITH LLVM-exception OR Apache-2.0 OR MIT"
);
parsed = true;
}
}
assert!(parsed);
}

#[test]
fn serialize() {
let description =
Licenses::new("Apache-2.0 WITH LLVM-exception OR Apache-2.0 OR MIT").unwrap();
let json = serde_json::to_string(&description).unwrap();
assert_eq!(
r#""Apache-2.0 WITH LLVM-exception OR Apache-2.0 OR MIT""#,
json
);
}
}
2 changes: 2 additions & 0 deletions crates/wasm-metadata/src/oci_annotations/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@

pub use author::Author;
pub use description::Description;
pub use licenses::Licenses;

mod author;
mod description;
mod licenses;
2 changes: 1 addition & 1 deletion crates/wasm-metadata/src/producers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,7 @@ impl Producers {
/// Merge into an existing wasm module. Rewrites the module with this producers section
/// merged into its existing one, or adds this producers section if none is present.
pub fn add_to_wasm(&self, input: &[u8]) -> Result<Vec<u8>> {
rewrite_wasm(&None, self, &None, &None, None, input)
rewrite_wasm(&None, self, &None, &None, &None, None, input)
}

pub(crate) fn display(&self, f: &mut fmt::Formatter, indent: usize) -> fmt::Result {
Expand Down
10 changes: 9 additions & 1 deletion crates/wasm-metadata/src/registry.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,15 @@ impl RegistryMetadata {
/// Merge into an existing wasm module. Rewrites the module with this registry-metadata section
/// overwriting its existing one, or adds this registry-metadata section if none is present.
pub fn add_to_wasm(&self, input: &[u8]) -> Result<Vec<u8>> {
rewrite_wasm(&None, &Producers::empty(), &None, &None, Some(&self), input)
rewrite_wasm(
&None,
&Producers::empty(),
&None,
&None,
&None,
Some(&self),
input,
)
}

/// Parse a Wasm binary and extract the `Registry` section, if there is any.
Expand Down
15 changes: 14 additions & 1 deletion crates/wasm-metadata/src/rewrite.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
use crate::{Author, ComponentNames, Description, ModuleNames, Producers, RegistryMetadata};
use crate::{
Author, ComponentNames, Description, Licenses, ModuleNames, Producers, RegistryMetadata,
};
use anyhow::Result;
use std::borrow::Cow;
use std::mem;
Expand All @@ -11,6 +13,7 @@ pub(crate) fn rewrite_wasm(
add_producers: &Producers,
add_author: &Option<Author>,
add_description: &Option<Description>,
add_licenses: &Option<Licenses>,
add_registry_metadata: Option<&RegistryMetadata>,
input: &[u8],
) -> Result<Vec<u8>> {
Expand Down Expand Up @@ -106,6 +109,13 @@ pub(crate) fn rewrite_wasm(
continue;
}
}
KnownCustom::Unknown if c.name() == "licenses" => {
if add_licenses.is_none() {
let licenses = Licenses::parse_custom_section(c)?;
licenses.append_to(&mut output);
continue;
}
}
_ => {}
}
}
Expand Down Expand Up @@ -141,6 +151,9 @@ pub(crate) fn rewrite_wasm(
if let Some(description) = add_description {
description.append_to(&mut output);
}
if let Some(licenses) = add_licenses {
licenses.append_to(&mut output);
}
if add_registry_metadata.is_some() {
let registry_metadata = wasm_encoder::CustomSection {
name: Cow::Borrowed("registry-metadata"),
Expand Down
23 changes: 21 additions & 2 deletions crates/wasm-metadata/tests/component.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ fn add_to_empty_component() {
sdk: vec![],
author: Some(Author::new("Chashu Cat")),
description: Some(Description::new("Chashu likes tuna")),
licenses: Some(
Licenses::new("Apache-2.0 WITH LLVM-exception OR Apache-2.0 OR MIT").unwrap(),
),
registry_metadata: Some(RegistryMetadata {
authors: Some(vec!["foo".to_owned()]),
description: Some("foo bar baz".to_owned()),
Expand Down Expand Up @@ -46,6 +49,7 @@ fn add_to_empty_component() {
registry_metadata,
author,
description,
licenses,
children,
range,
} => {
Expand All @@ -63,6 +67,10 @@ fn add_to_empty_component() {

assert_eq!(author.unwrap(), Author::new("Chashu Cat"));
assert_eq!(description.unwrap(), Description::new("Chashu likes tuna"));
assert_eq!(
licenses.unwrap(),
Licenses::new("Apache-2.0 WITH LLVM-exception OR Apache-2.0 OR MIT").unwrap()
);

let registry_metadata = registry_metadata.unwrap();

Expand Down Expand Up @@ -106,7 +114,7 @@ fn add_to_empty_component() {
);

assert_eq!(range.start, 0);
assert_eq!(range.end, 485);
assert_eq!(range.end, 547);
}
_ => panic!("metadata should be component"),
}
Expand All @@ -123,6 +131,9 @@ fn add_to_nested_component() {
sdk: vec![],
author: Some(Author::new("Chashu Cat")),
description: Some(Description::new("Chashu likes tuna")),
licenses: Some(
Licenses::new("Apache-2.0 WITH LLVM-exception OR Apache-2.0 OR MIT").unwrap(),
),
registry_metadata: Some(RegistryMetadata {
authors: Some(vec!["Foo".to_owned()]),
..Default::default()
Expand Down Expand Up @@ -169,6 +180,7 @@ fn add_to_nested_component() {
name,
producers,
author,
licenses,
registry_metadata,
range,
description,
Expand All @@ -186,6 +198,13 @@ fn add_to_nested_component() {

assert_eq!(author, &Some(Author::new("Chashu Cat")));
assert_eq!(description, &Some(Description::new("Chashu likes tuna")));
assert_eq!(
licenses,
&Some(
Licenses::new("Apache-2.0 WITH LLVM-exception OR Apache-2.0 OR MIT")
.unwrap()
)
);

let registry_metadata = registry_metadata.as_ref().unwrap();
assert_eq!(
Expand All @@ -194,7 +213,7 @@ fn add_to_nested_component() {
);

assert_eq!(range.start, 11);
assert_eq!(range.end, 174);
assert_eq!(range.end, 236);
}
_ => panic!("child is a module"),
}
Expand Down
Loading
Loading