Repository: bytecodealliance/wasm-tools
Pull request #1926 (2024-12-02 15:05:59)

[wasm-metadata] allow languages to be versioned
I was looking at the [webassembly/tool-conventions: producer section](https://github.com/WebAssembly/tool-conventions/blob/5bf631d999954490c3d78e324c0e46784f514b1d/ProducersSection.md#source-languages) and noticed that all examples are versioning the programming languages. That makes sense to me, since languages typically are versioned. However `wasm-metadata` does not expect a language version, which leads to string-encodings of the version in the key.

For example, I've seen the use of `C11` in the wild. According to tool-conventions this should have used the known programming language `C` as the key, and `11` as the version. This PR fixes that inconsistency by allowing the language to be versioned. Thanks!

## Example

An example copied from `webassembly/tool-conventions`:
```wat
(module
  (@producers
    (language "C" "18.1.2")
    (processed-by "LLVM" "18.1.2")
    (sdk "Emscripten" "3.1.60")
  )
)
```


Patch:
diff --git a/crates/wasm-metadata/src/add_metadata.rs b/crates/wasm-metadata/src/add_metadata.rs
index d8c52da4..c2b997e4 100644
--- a/crates/wasm-metadata/src/add_metadata.rs
+++ b/crates/wasm-metadata/src/add_metadata.rs
@@ -14,8 +14,8 @@ pub struct AddMetadata {
     pub name: Option<String>,
 
     /// Add a programming language to the producers section
-    #[cfg_attr(feature = "clap", clap(long, value_name = "NAME"))]
-    pub language: Vec<String>,
+    #[cfg_attr(feature = "clap", clap(long, value_parser = parse_key_value, value_name = "NAME=VERSION"))]
+    pub language: Vec<(String, String)>,
 
     /// Add a tool and its version to the producers section
     #[cfg_attr(feature = "clap", clap(long = "processed-by", value_parser = parse_key_value, value_name="NAME=VERSION"))]
diff --git a/crates/wasm-metadata/src/producers.rs b/crates/wasm-metadata/src/producers.rs
index 3ad47326..ccec57e4 100644
--- a/crates/wasm-metadata/src/producers.rs
+++ b/crates/wasm-metadata/src/producers.rs
@@ -113,8 +113,8 @@ impl Producers {
     /// Construct the fields specified by [`AddMetadata`]
     pub(crate) fn from_meta(add: &AddMetadata) -> Self {
         let mut s = Self::empty();
-        for lang in add.language.iter() {
-            s.add("language", &lang, "");
+        for (lang, version) in add.language.iter() {
+            s.add("language", &lang, &version);
         }
         for (name, version) in add.processed_by.iter() {
             s.add("processed-by", &name, &version);


Test patch:
diff --git a/crates/wasm-metadata/tests/component.rs b/crates/wasm-metadata/tests/component.rs
index 6c2c3f3e..beda10a3 100644
--- a/crates/wasm-metadata/tests/component.rs
+++ b/crates/wasm-metadata/tests/component.rs
@@ -8,7 +8,7 @@ fn add_to_empty_component() {
     let component = Component::new().finish();
     let add = AddMetadata {
         name: Some("foo".to_owned()),
-        language: vec!["bar".to_owned()],
+        language: vec![("bar".to_owned(), "1.0".to_owned())],
         processed_by: vec![("baz".to_owned(), "1.0".to_owned())],
         sdk: vec![],
         registry_metadata: Some(RegistryMetadata {
@@ -48,7 +48,10 @@ fn add_to_empty_component() {
             assert!(children.is_empty());
             assert_eq!(name, Some("foo".to_owned()));
             let producers = producers.expect("some producers");
-            assert_eq!(producers.get("language").unwrap().get("bar").unwrap(), "");
+            assert_eq!(
+                producers.get("language").unwrap().get("bar").unwrap(),
+                "1.0"
+            );
             assert_eq!(
                 producers.get("processed-by").unwrap().get("baz").unwrap(),
                 "1.0"
@@ -96,7 +99,7 @@ fn add_to_empty_component() {
             );
 
             assert_eq!(range.start, 0);
-            assert_eq!(range.end, 432);
+            assert_eq!(range.end, 435);
         }
         _ => panic!("metadata should be component"),
     }
@@ -108,7 +111,7 @@ fn add_to_nested_component() {
     let module = Module::new().finish();
     let add = AddMetadata {
         name: Some("foo".to_owned()),
-        language: vec!["bar".to_owned()],
+        language: vec![("bar".to_owned(), "1.0".to_owned())],
         processed_by: vec![("baz".to_owned(), "1.0".to_owned())],
         sdk: vec![],
         registry_metadata: Some(RegistryMetadata {
@@ -161,7 +164,10 @@ fn add_to_nested_component() {
                 } => {
                     assert_eq!(name, &Some("foo".to_owned()));
                     let producers = producers.as_ref().expect("some producers");
-                    assert_eq!(producers.get("language").unwrap().get("bar").unwrap(), "");
+                    assert_eq!(
+                        producers.get("language").unwrap().get("bar").unwrap(),
+                        "1.0"
+                    );
                     assert_eq!(
                         producers.get("processed-by").unwrap().get("baz").unwrap(),
                         "1.0"
@@ -174,7 +180,7 @@ fn add_to_nested_component() {
                     );
 
                     assert_eq!(range.start, 10);
-                    assert_eq!(range.end, 120);
+                    assert_eq!(range.end, 123);
                 }
                 _ => panic!("child is a module"),
             }
diff --git a/crates/wasm-metadata/tests/module.rs b/crates/wasm-metadata/tests/module.rs
index d25014fc..a1b95bc9 100644
--- a/crates/wasm-metadata/tests/module.rs
+++ b/crates/wasm-metadata/tests/module.rs
@@ -8,7 +8,7 @@ fn add_to_empty_module() {
     let module = Module::new().finish();
     let add = AddMetadata {
         name: Some("foo".to_owned()),
-        language: vec!["bar".to_owned()],
+        language: vec![("bar".to_owned(), "1.0".to_owned())],
         processed_by: vec![("baz".to_owned(), "1.0".to_owned())],
         sdk: vec![],
         registry_metadata: Some(RegistryMetadata {
@@ -46,7 +46,10 @@ fn add_to_empty_module() {
         } => {
             assert_eq!(name, Some("foo".to_owned()));
             let producers = producers.expect("some producers");
-            assert_eq!(producers.get("language").unwrap().get("bar").unwrap(), "");
+            assert_eq!(
+                producers.get("language").unwrap().get("bar").unwrap(),
+                "1.0"
+            );
             assert_eq!(
                 producers.get("processed-by").unwrap().get("baz").unwrap(),
                 "1.0"
@@ -94,7 +97,7 @@ fn add_to_empty_module() {
             );
 
             assert_eq!(range.start, 0);
-            assert_eq!(range.end, 422);
+            assert_eq!(range.end, 425);
         }
         _ => panic!("metadata should be module"),
     }
diff --git a/tests/cli/add-metadata-merge-sections.wat b/tests/cli/add-metadata-merge-sections.wat
index b5a8115c..7ad17943 100644
--- a/tests/cli/add-metadata-merge-sections.wat
+++ b/tests/cli/add-metadata-merge-sections.wat
@@ -1,2 +1,2 @@
-;; RUN: metadata add --language foo % | metadata add --language bar | metadata add --sdk foo=2 | metadata show
+;; RUN: metadata add --language foo=3 % | metadata add --language bar=1 | metadata add --sdk foo=2 | metadata show
 (module)
diff --git a/tests/cli/add-metadata-merge-sections.wat.stdout b/tests/cli/add-metadata-merge-sections.wat.stdout
index 4ad91a0d..7ce7a97b 100644
--- a/tests/cli/add-metadata-merge-sections.wat.stdout
+++ b/tests/cli/add-metadata-merge-sections.wat.stdout
@@ -1,6 +1,6 @@
 module:
     language:
-        foo
-        bar
+        foo: 3
+        bar: 1
     sdk:
         foo: 2
diff --git a/tests/cli/add-metadata.wat b/tests/cli/add-metadata.wat
index 857097f3..c3130e96 100644
--- a/tests/cli/add-metadata.wat
+++ b/tests/cli/add-metadata.wat
@@ -1,2 +1,2 @@
-;; RUN: metadata add % --name foo --language bar --processed-by baz=1 --sdk my-sdk=2 | metadata show
+;; RUN: metadata add % --name foo --language bar=1 --processed-by baz=1 --sdk my-sdk=2 | metadata show
 (module)
diff --git a/tests/cli/add-metadata.wat.stdout b/tests/cli/add-metadata.wat.stdout
index 681eadab..775d0001 100644
--- a/tests/cli/add-metadata.wat.stdout
+++ b/tests/cli/add-metadata.wat.stdout
@@ -1,6 +1,6 @@
 module foo:
     language:
-        bar
+        bar: 1
     processed-by:
         baz: 1
     sdk:
diff --git a/tests/cli/metadata-add-component.wat b/tests/cli/metadata-add-component.wat
index b7ba818b..c1ad93c6 100644
--- a/tests/cli/metadata-add-component.wat
+++ b/tests/cli/metadata-add-component.wat
@@ -1,4 +1,4 @@
-;; RUN: metadata add --language foo % | metadata show
+;; RUN: metadata add --language foo=1 % | metadata show
 (component $foo
   (core module
     (func $foo)
diff --git a/tests/cli/metadata-add-component.wat.stdout b/tests/cli/metadata-add-component.wat.stdout
index d98df53e..d099bd0b 100644
--- a/tests/cli/metadata-add-component.wat.stdout
+++ b/tests/cli/metadata-add-component.wat.stdout
@@ -1,4 +1,4 @@
 component foo:
     language:
-        foo
+        foo: 1
     module: