Repository: cross-rs/cross (rust)

Issue:
Config in Cargo.toml: invalid type: map, expected a string for key `target.x86_64-unknown-freebsd.image`
### Checklist

- [X] I've looked through the [issues and pull requests](https://github.com/cross-rs/cross/issues?q=) for similar reports

### Describe your issue

Related: https://github.com/cross-rs/cross/issues/657

### What target(s) are you cross-compiling for?

x86_64-unknown-freebsd

### Which operating system is the host (e.g computer cross is on) running?

- [X] macOS
- [ ] Windows
- [ ] Linux / BSD
- [ ] other OS (specify in description)

### What architecture is the host?

- [ ] x86_64 / AMD64
- [ ] arm32
- [X] arm64 (including Mac M1)

### What container engine is cross using?

- [X] docker
- [ ] podman
- [ ] other container engine (specify in description)

### cross version

cross 0.2.4

### Example

```console
$ cargo new foo && cd $_
$ cat <<'EOF' >> Cargo.toml
﻿﻿﻿[package.metadata.cross.target.x86_64-unknown-freebsd]
image.name = "ghcr.io/cross-rs/x86_64-unknown-freebsd:local"
image.toolchain = ["linux/arm64/v8=aarch64-unknown-linux-gnu"]
EOF
$ cross build --target x86_64-unknown-freebsd --release
```

Using cross 0.2.4 from nixpkgs or crates.io, I get:

```
Error:
   0: invalid type: map, expected a string for key `target.x86_64-unknown-freebsd.image`
```

Using cross from `main`, I get a different error:

```console
$ cargo install cross --git https://github.com/cross-rs/cross
$ cross --version
cross 0.2.4 (1d9d310 2023-01-05)
$ cross build --target x86_64-unknown-freebsd --release
Error:
   0: invalid type: string "linux/arm64/v8=aarch64-unknown-linux-gnu", expected a borrowed string for key `target.x86_64-unknown-freebsd.image.toolchain`

Location:
   src/cross_toml.rs:181
```

If I instead put (what I believe to be) the analogous table into `Cross.toml`, I get the same error with nixpkgs and crates.io but it works fine from `main`:

```console
$ cat <<'EOF' > Cross.toml
[target.x86_64-unknown-freebsd]
image.name = "ghcr.io/cross-rs/x86_64-unknown-freebsd:local"
image.toolchain = ["linux/arm64/v8=aarch64-unknown-linux-gnu"]
EOF
$ cross --version
cross 0.2.4 (1d9d310 2023-01-05)
[cross] warning: `cross` does not provide a Docker image for target aarch64-apple-darwin, specify a custom image in `Cross.toml`.
[cross] note: Falling back to `cargo` on the host.
cargo 1.66.0 (d65d197ad 2022-11-15)
$ cross build --target x86_64-unknown-freebsd --release
   Compiling foo v0.1.0 (/private/var/folders/kb/tw_lp_xd2_bbv0hqk4m0bvt80000gn/T/tmp.ililnMNd9D/foo)
    Finished release [optimized] target(s) in 8.52s
```

Using the current `main`, as recommended in the README, is it expected that this table works from `Cross.toml` but not from `Cargo.toml`?

Thanks for your work on a cool project!

### Additional information / notes

_No response_

Pull request description:
Fix error when using certain config values in Cargo.toml
`pre-build = "file"` and `image.toolchain` had a bad serialization, this fixes that

Closes #1182


Patch:
diff --git a/.changes/1183.json b/.changes/1183.json
new file mode 100644
index 0000000..1613ea5
--- /dev/null
+++ b/.changes/1183.json
@@ -0,0 +1,5 @@
+{
+    "description": "resolve issue when using `pre-build` and `image.toolchain` in `Cargo.toml`",
+    "type": "fixed",
+    "issues": [1182]
+}
diff --git a/src/cross_toml.rs b/src/cross_toml.rs
index 1f71964..0b90e8f 100644
--- a/src/cross_toml.rs
+++ b/src/cross_toml.rs
@@ -855,6 +855,29 @@ mod tests {
         Ok(())
     }
 
+    #[test]
+    pub fn fully_populated_roundtrip() -> Result<()> {
+        let cfg = r#"
+            [target.a]
+            xargo = false
+            build-std = true
+            image.name = "local"
+            image.toolchain = ["x86_64-unknown-linux-gnu"]
+            dockerfile.file = "Dockerfile"
+            dockerfile.context = ".."
+            pre-build = ["sh"]
+            zig = true
+
+            [target.b]
+            pre-build = "sh"
+            zig = "2.17"
+        "#;
+
+        let (cfg, _) = CrossToml::parse_from_cross(cfg, &mut m!())?;
+        serde_json::from_value::<CrossToml>(serde_json::to_value(cfg)?)?;
+        Ok(())
+    }
+
     #[test]
     pub fn merge() -> Result<()> {
         let cfg1_str = r#"
diff --git a/src/docker/custom.rs b/src/docker/custom.rs
index 9bbc16f..d9aaa7a 100644
--- a/src/docker/custom.rs
+++ b/src/docker/custom.rs
@@ -25,7 +25,7 @@ pub enum Dockerfile<'a> {
     },
 }
 
-#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
+#[derive(Debug, Clone, PartialEq, Eq, serde::Deserialize)]
 pub enum PreBuild {
     /// A path to a file to copy or a single line to `RUN` if line comes from env
     Single { line: String, env: bool },
@@ -33,6 +33,21 @@ pub enum PreBuild {
     Lines(Vec<String>),
 }
 
+impl serde::Serialize for PreBuild {
+    fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
+        match self {
+            PreBuild::Single { line, .. } => serializer.serialize_str(line),
+            PreBuild::Lines(lines) => {
+                use serde::ser::SerializeSeq;
+                let mut seq = serializer.serialize_seq(Some(lines.len()))?;
+                for line in lines {
+                    seq.serialize_element(line)?;
+                }
+                seq.end()
+            }
+        }
+    }
+}
 impl FromStr for PreBuild {
     type Err = std::convert::Infallible;
 
@@ -247,12 +262,11 @@ fn docker_tag_name(file_name: &str) -> String {
         }
     }
 
-    // in case our result ends in an invalid last char `-` or `.`
-    // we remove
-    result = result.trim_end_matches(&['.', '-']).to_owned();
-
     // in case all characters were invalid or we had all non-ASCII
-    // characters followed by a `-` or `.`, we use a non-empty filename
+    // characters followed by a `-`, we use a non-empty filename
+    if result.ends_with('-') {
+        result.pop();
+    }
     if result.is_empty() {
         result = "empty".to_owned();
     }
diff --git a/src/docker/image.rs b/src/docker/image.rs
index 188afb8..a6f11be 100644
--- a/src/docker/image.rs
+++ b/src/docker/image.rs
@@ -104,8 +104,8 @@ impl std::fmt::Display for PossibleImage {
 /// The architecture/platform to use in the image
 ///
 /// https://github.com/containerd/containerd/blob/release/1.6/platforms/platforms.go#L63
-#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
-#[serde(try_from = "&str")]
+#[derive(Debug, Clone, PartialEq, Eq, serde::Deserialize)]
+#[serde(try_from = "String")]
 pub struct ImagePlatform {
     /// CPU architecture, x86_64, aarch64 etc
     pub architecture: Architecture,
@@ -141,14 +141,20 @@ impl Default for ImagePlatform {
     }
 }
 
-impl TryFrom<&str> for ImagePlatform {
+impl TryFrom<String> for ImagePlatform {
     type Error = <Self as std::str::FromStr>::Err;
 
-    fn try_from(value: &str) -> Result<Self, Self::Error> {
+    fn try_from(value: String) -> Result<Self, Self::Error> {
         value.parse()
     }
 }
 
+impl Serialize for ImagePlatform {
+    fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
+        serializer.serialize_str(&format!("{}={}", self.docker_platform(), self.target))
+    }
+}
+
 impl std::str::FromStr for ImagePlatform {
     type Err = eyre::Report;
     // [os/arch[/variant]=]toolchain
diff --git a/src/lib.rs b/src/lib.rs
index e3db8d4..d28c7c1 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -70,8 +70,8 @@ pub use self::rustc::{TargetList, VersionMetaExt};
 pub const CROSS_LABEL_DOMAIN: &str = "org.cross-rs";
 
 #[allow(non_camel_case_types)]
-#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Hash, Serialize)]
-#[serde(from = "&str")]
+#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Hash)]
+#[serde(from = "&str", into = "String")]
 #[serde(rename_all = "snake_case")]
 pub enum TargetTriple {
     Other(String),
@@ -261,6 +261,12 @@ impl From<String> for TargetTriple {
     }
 }
 
+impl Serialize for TargetTriple {
+    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
+        serializer.serialize_str(self.triple())
+    }
+}
+
 #[derive(Debug, Clone, PartialEq, Eq, Hash, Deserialize)]
 #[serde(from = "String")]
 pub enum Target {


Test patch:
diff --git a/src/tests/toml.rs b/src/tests/toml.rs
index fb4f9c1..0cf013e 100644
--- a/src/tests/toml.rs
+++ b/src/tests/toml.rs
@@ -61,14 +61,17 @@ fn toml_check() -> Result<(), Box<dyn std::error::Error>> {
                 text_line_no(&contents, fence.range().start),
             );
             let mut msg_info = crate::shell::MessageInfo::default();
-            assert!(if !cargo {
+            let toml = if !cargo {
                 crate::cross_toml::CrossToml::parse_from_cross(&fence_content, &mut msg_info)?
             } else {
                 crate::cross_toml::CrossToml::parse_from_cargo(&fence_content, &mut msg_info)?
                     .unwrap_or_default()
-            }
-            .1
-            .is_empty());
+            };
+            assert!(toml.1.is_empty());
+
+            // TODO: Add serde_path_to_error
+            // Check if roundtrip works, needed for merging Cross.toml and Cargo.toml
+            serde_json::from_value::<crate::cross_toml::CrossToml>(serde_json::to_value(toml.0)?)?;
         }
     }
     Ok(())