mirror of
https://github.com/cargo-bins/cargo-binstall.git
synced 2025-04-24 22:30:03 +00:00
added zip support, pkg-fmt override
swapped CI to build zips for windows
This commit is contained in:
parent
94ad0db41d
commit
1c25b1346f
7 changed files with 160 additions and 24 deletions
27
.github/workflows/rust.yml
vendored
27
.github/workflows/rust.yml
vendored
|
@ -22,15 +22,19 @@ jobs:
|
||||||
- target: x86_64-unknown-linux-gnu
|
- target: x86_64-unknown-linux-gnu
|
||||||
os: ubuntu-latest
|
os: ubuntu-latest
|
||||||
output: cargo-binstall
|
output: cargo-binstall
|
||||||
|
archive: tgz
|
||||||
- target: x86_64-apple-darwin
|
- target: x86_64-apple-darwin
|
||||||
os: macos-latest
|
os: macos-latest
|
||||||
output: cargo-binstall
|
output: cargo-binstall
|
||||||
|
archive: tgz
|
||||||
- target: armv7-unknown-linux-gnueabihf
|
- target: armv7-unknown-linux-gnueabihf
|
||||||
os: ubuntu-20.04
|
os: ubuntu-20.04
|
||||||
output: cargo-binstall
|
output: cargo-binstall
|
||||||
|
archive: tgz
|
||||||
- target: x86_64-pc-windows-msvc
|
- target: x86_64-pc-windows-msvc
|
||||||
os: windows-latest
|
os: windows-latest
|
||||||
output: cargo-binstall.exe
|
output: cargo-binstall.exe
|
||||||
|
archive: zip
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
|
@ -93,28 +97,33 @@ jobs:
|
||||||
command: build
|
command: build
|
||||||
args: --target ${{ matrix.target }} --release
|
args: --target ${{ matrix.target }} --release
|
||||||
|
|
||||||
- name: Copy / Rename utility
|
- name: Copy and rename utility
|
||||||
run: |
|
run: cp target/${{ matrix.target }}/release/${{ matrix.output }} ${{ matrix.output }}
|
||||||
cp target/${{ matrix.target }}/release/${{ matrix.output }} ${{ matrix.output }}
|
|
||||||
tar -czvf cargo-binstall-${{ matrix.target }}.tgz ${{ matrix.output }}
|
- name: Create archive (tgz)
|
||||||
|
if: ${{ matrix.target != 'x86_64-pc-windows-msvc' }}
|
||||||
|
run: tar -czvf cargo-binstall-${{ matrix.target }}.tgz ${{ matrix.output }}
|
||||||
|
|
||||||
|
- name: Create archive (zip)
|
||||||
|
if: ${{ matrix.target == 'x86_64-pc-windows-msvc' }}
|
||||||
|
run: tar.exe -a -c -f cargo-binstall-${{ matrix.target }}.zip ${{ matrix.output }}
|
||||||
|
|
||||||
- name: Upload artifacts
|
- name: Upload artifacts
|
||||||
uses: actions/upload-artifact@v1
|
uses: actions/upload-artifact@v1
|
||||||
with:
|
with:
|
||||||
name: cargo-binstall-${{ matrix.target }}.tgz
|
name: cargo-binstall-${{ matrix.target }}.${{ matrix.archive }}
|
||||||
path: cargo-binstall-${{ matrix.target }}.tgz
|
path: cargo-binstall-${{ matrix.target }}.${{ matrix.archive }}
|
||||||
|
|
||||||
- name: Upload binary to release
|
- name: Upload binary to release
|
||||||
if: ${{ startsWith(github.ref, 'refs/tags/v') }}
|
if: ${{ startsWith(github.ref, 'refs/tags/v') }}
|
||||||
uses: svenstaro/upload-release-action@v2
|
uses: svenstaro/upload-release-action@v2
|
||||||
with:
|
with:
|
||||||
repo_token: ${{ secrets.GITHUB_TOKEN }}
|
repo_token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
file: cargo-binstall-${{ matrix.target }}.tgz
|
file: cargo-binstall-${{ matrix.target }}.${{ matrix.archive }}
|
||||||
asset_name: cargo-binstall-${{ matrix.target }}.tgz
|
asset_name: cargo-binstall-${{ matrix.target }}.${{ matrix.archive }}
|
||||||
tag: ${{ github.ref }}
|
tag: ${{ github.ref }}
|
||||||
overwrite: true
|
overwrite: true
|
||||||
|
|
||||||
|
|
||||||
test:
|
test:
|
||||||
name: Test
|
name: Test
|
||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
|
|
83
Cargo.lock
generated
83
Cargo.lock
generated
|
@ -1,10 +1,12 @@
|
||||||
# This file is automatically @generated by Cargo.
|
# This file is automatically @generated by Cargo.
|
||||||
# It is not intended for manual editing.
|
# It is not intended for manual editing.
|
||||||
|
version = 3
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "adler"
|
name = "adler32"
|
||||||
version = "0.2.3"
|
version = "1.2.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "ee2a4ec343196209d6594e19543ae87a39f96d5534d7174822a3ad825dd6ed7e"
|
checksum = "aae1277d39aeec15cb388266ecc24b11c80469deae6067e17a1a7aa9e5c1f234"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "aho-corasick"
|
name = "aho-corasick"
|
||||||
|
@ -88,12 +90,39 @@ version = "3.4.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "2e8c087f005730276d1096a652e92a8bacee2e2472bcc9715a74d2bec38b5820"
|
checksum = "2e8c087f005730276d1096a652e92a8bacee2e2472bcc9715a74d2bec38b5820"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "byteorder"
|
||||||
|
version = "1.4.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bytes"
|
name = "bytes"
|
||||||
version = "0.5.6"
|
version = "0.5.6"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "0e4cec68f03f32e44924783795810fa50a7035d8c8ebe78580ad7e6c703fba38"
|
checksum = "0e4cec68f03f32e44924783795810fa50a7035d8c8ebe78580ad7e6c703fba38"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bzip2"
|
||||||
|
version = "0.3.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "42b7c3cbf0fa9c1b82308d57191728ca0256cb821220f4e2fd410a72ade26e3b"
|
||||||
|
dependencies = [
|
||||||
|
"bzip2-sys",
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bzip2-sys"
|
||||||
|
version = "0.1.10+1.0.8"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "17fa3d1ac1ca21c5c4e36a97f3c3eb25084576f6fc47bf0139c1123434216c6c"
|
||||||
|
dependencies = [
|
||||||
|
"cc",
|
||||||
|
"libc",
|
||||||
|
"pkg-config",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cargo-binstall"
|
name = "cargo-binstall"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
|
@ -120,6 +149,7 @@ dependencies = [
|
||||||
"tinytemplate",
|
"tinytemplate",
|
||||||
"tokio",
|
"tokio",
|
||||||
"xz2",
|
"xz2",
|
||||||
|
"zip",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -338,11 +368,11 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "flate2"
|
name = "flate2"
|
||||||
version = "1.0.19"
|
version = "1.0.14"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7411863d55df97a419aa64cb4d2f167103ea9d767e2c54a1868b7ac3f6b47129"
|
checksum = "2cfff41391129e0a856d6d822600b8d71179d46879e310417eb9c762eb178b42"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"cfg-if 1.0.0",
|
"cfg-if 0.1.10",
|
||||||
"crc32fast",
|
"crc32fast",
|
||||||
"libc",
|
"libc",
|
||||||
"miniz_oxide",
|
"miniz_oxide",
|
||||||
|
@ -838,12 +868,11 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "miniz_oxide"
|
name = "miniz_oxide"
|
||||||
version = "0.4.3"
|
version = "0.3.7"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "0f2d26ec3309788e423cfbf68ad1800f061638098d76a83681af979dc4eda19d"
|
checksum = "791daaae1ed6889560f8c4359194f56648355540573244a5448a83ba1ecc7435"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"adler",
|
"adler32",
|
||||||
"autocfg",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -1541,6 +1570,26 @@ dependencies = [
|
||||||
"unicode-width",
|
"unicode-width",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "thiserror"
|
||||||
|
version = "1.0.24"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e0f4a65597094d4483ddaed134f409b2cb7c1beccf25201a9f73c719254fa98e"
|
||||||
|
dependencies = [
|
||||||
|
"thiserror-impl",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "thiserror-impl"
|
||||||
|
version = "1.0.24"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7765189610d8241a44529806d6fd1f2e0a08734313a35d5b3a556f92b381f3c0"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "thread_local"
|
name = "thread_local"
|
||||||
version = "1.0.1"
|
version = "1.0.1"
|
||||||
|
@ -1952,3 +2001,17 @@ checksum = "c179869f34fc7c01830d3ce7ea2086bc3a07e0d35289b667d0a8bf910258926c"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"lzma-sys",
|
"lzma-sys",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "zip"
|
||||||
|
version = "0.5.11"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8264fcea9b7a036a4a5103d7153e988dbc2ebbafb34f68a3c2d404b6b82d74b6"
|
||||||
|
dependencies = [
|
||||||
|
"byteorder",
|
||||||
|
"bzip2",
|
||||||
|
"crc32fast",
|
||||||
|
"flate2",
|
||||||
|
"thiserror",
|
||||||
|
"time",
|
||||||
|
]
|
||||||
|
|
|
@ -10,9 +10,11 @@ license = "GPL-3.0"
|
||||||
|
|
||||||
|
|
||||||
[package.metadata.binstall]
|
[package.metadata.binstall]
|
||||||
pkg-url = "{ repo }/releases/download/v{ version }/{ name }-{ target }.tgz"
|
pkg-url = "{ repo }/releases/download/v{ version }/{ name }-{ target }.{ format }"
|
||||||
bin-dir = "{ bin }{ format }"
|
bin-dir = "{ bin }{ format }"
|
||||||
|
|
||||||
|
[package.metadata.binstall.overrides.x86_64-pc-windows-msvc]
|
||||||
|
pkg-fmt = "zip"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
crates_io_api = "0.6.1"
|
crates_io_api = "0.6.1"
|
||||||
|
@ -25,7 +27,7 @@ simplelog = "0.9.0"
|
||||||
anyhow = "1.0.40"
|
anyhow = "1.0.40"
|
||||||
reqwest = "0.10.10"
|
reqwest = "0.10.10"
|
||||||
tempdir = "0.3.7"
|
tempdir = "0.3.7"
|
||||||
flate2 = "1.0.19"
|
flate2 = "1.0.14"
|
||||||
tar = "0.4.30"
|
tar = "0.4.30"
|
||||||
cargo_toml = "0.8.1"
|
cargo_toml = "0.8.1"
|
||||||
serde = { version = "1.0.119", features = [ "derive" ] }
|
serde = { version = "1.0.119", features = [ "derive" ] }
|
||||||
|
@ -36,6 +38,7 @@ serde_derive = "1.0.118"
|
||||||
crates-index = "0.16.2"
|
crates-index = "0.16.2"
|
||||||
semver = "0.11.0"
|
semver = "0.11.0"
|
||||||
xz2 = "0.1.6"
|
xz2 = "0.1.6"
|
||||||
|
zip = "0.5.11"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
env_logger = "0.8.2"
|
env_logger = "0.8.2"
|
||||||
|
|
|
@ -41,6 +41,7 @@ yes
|
||||||
- [x] Tgz
|
- [x] Tgz
|
||||||
- [x] Txz
|
- [x] Txz
|
||||||
- [x] Tar
|
- [x] Tar
|
||||||
|
- [x] Zip
|
||||||
- [x] Bin
|
- [x] Bin
|
||||||
- Extraction / Transformation
|
- Extraction / Transformation
|
||||||
- [x] Extract from subdirectory in archive (ie. support archives with platform or target subdirectories)
|
- [x] Extract from subdirectory in archive (ie. support archives with platform or target subdirectories)
|
||||||
|
@ -79,12 +80,18 @@ Template variables use the format `{ VAR }` where `VAR` is the name of the varia
|
||||||
- `bin` is the name of a specific binary, inferred from the crate configuration
|
- `bin` is the name of a specific binary, inferred from the crate configuration
|
||||||
- `target` is the rust target name (defaults to your architecture, but can be overridden using the `--target` command line option if required().
|
- `target` is the rust target name (defaults to your architecture, but can be overridden using the `--target` command line option if required().
|
||||||
|
|
||||||
|
Package format can be overridden on a per-target basis, for example, if your `x86_64-pc-windows-msvc` builds use `zip` archives this can be set via:
|
||||||
|
|
||||||
|
```
|
||||||
|
[package.metadata.binstall.overrides.x86_64-pc-windows-msvc]
|
||||||
|
pkg-fmt = "zip"
|
||||||
|
```
|
||||||
|
|
||||||
### Defaults
|
### Defaults
|
||||||
|
|
||||||
By default `binstall` is setup to work with github releases, and expects to find:
|
By default `binstall` is setup to work with github releases, and expects to find:
|
||||||
|
|
||||||
- an archive named `{ name }-{ target }-v{ version }.tgz`
|
- an archive named `{ name }-{ target }-v{ version }.{ format }`
|
||||||
- so that this does not overwrite different targets or versions when manually downloaded
|
- so that this does not overwrite different targets or versions when manually downloaded
|
||||||
- located at `{ repo }/releases/download/v{ version }/`
|
- located at `{ repo }/releases/download/v{ version }/`
|
||||||
- compatible with github tags / releases
|
- compatible with github tags / releases
|
||||||
|
|
|
@ -7,7 +7,7 @@ use cargo_toml::{Manifest};
|
||||||
use flate2::read::GzDecoder;
|
use flate2::read::GzDecoder;
|
||||||
use tar::Archive;
|
use tar::Archive;
|
||||||
use xz2::read::XzDecoder;
|
use xz2::read::XzDecoder;
|
||||||
|
use zip::read::ZipArchive;
|
||||||
|
|
||||||
use crate::{Meta};
|
use crate::{Meta};
|
||||||
|
|
||||||
|
@ -77,6 +77,15 @@ pub fn extract<S: AsRef<Path>, P: AsRef<Path>>(source: S, fmt: PkgFmt, path: P)
|
||||||
|
|
||||||
txz.unpack(path)?;
|
txz.unpack(path)?;
|
||||||
},
|
},
|
||||||
|
PkgFmt::Zip => {
|
||||||
|
// Extract to install dir
|
||||||
|
debug!("Decompressing from archive '{:?}' to `{:?}`", source.as_ref(), path.as_ref());
|
||||||
|
|
||||||
|
let dat = std::fs::File::open(source)?;
|
||||||
|
let mut zip = ZipArchive::new(dat)?;
|
||||||
|
|
||||||
|
zip.extract(path)?;
|
||||||
|
},
|
||||||
PkgFmt::Bin => {
|
PkgFmt::Bin => {
|
||||||
debug!("Copying data from archive '{:?}' to `{:?}`", source.as_ref(), path.as_ref());
|
debug!("Copying data from archive '{:?}' to `{:?}`", source.as_ref(), path.as_ref());
|
||||||
// Copy to install dir
|
// Copy to install dir
|
||||||
|
|
38
src/lib.rs
38
src/lib.rs
|
@ -1,3 +1,6 @@
|
||||||
|
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
use serde::{Serialize, Deserialize};
|
use serde::{Serialize, Deserialize};
|
||||||
use strum_macros::{Display, EnumString, EnumVariantNames};
|
use strum_macros::{Display, EnumString, EnumVariantNames};
|
||||||
use tinytemplate::TinyTemplate;
|
use tinytemplate::TinyTemplate;
|
||||||
|
@ -21,6 +24,7 @@ pub const DEFAULT_BIN_PATH: &'static str = "{ name }-{ target }-v{ version }/{ b
|
||||||
|
|
||||||
|
|
||||||
/// Binary format enumeration
|
/// Binary format enumeration
|
||||||
|
/// This defaults to .zip on windows and .tgz on all other platforms
|
||||||
#[derive(Debug, Copy, Clone, PartialEq, Serialize, Deserialize)]
|
#[derive(Debug, Copy, Clone, PartialEq, Serialize, Deserialize)]
|
||||||
#[derive(Display, EnumString, EnumVariantNames)]
|
#[derive(Display, EnumString, EnumVariantNames)]
|
||||||
#[strum(serialize_all = "snake_case")]
|
#[strum(serialize_all = "snake_case")]
|
||||||
|
@ -32,6 +36,8 @@ pub enum PkgFmt {
|
||||||
Tgz,
|
Tgz,
|
||||||
/// Download format is TAR + XZ
|
/// Download format is TAR + XZ
|
||||||
Txz,
|
Txz,
|
||||||
|
/// Download format is Zip
|
||||||
|
Zip,
|
||||||
/// Download format is raw / binary
|
/// Download format is raw / binary
|
||||||
Bin,
|
Bin,
|
||||||
}
|
}
|
||||||
|
@ -68,6 +74,9 @@ pub struct PkgMeta {
|
||||||
|
|
||||||
/// Public key for package verification (base64 encoded)
|
/// Public key for package verification (base64 encoded)
|
||||||
pub pub_key: Option<String>,
|
pub pub_key: Option<String>,
|
||||||
|
|
||||||
|
/// Target specific overrides
|
||||||
|
pub overrides: HashMap<String, PkgOverride>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for PkgMeta {
|
impl Default for PkgMeta {
|
||||||
|
@ -77,10 +86,39 @@ impl Default for PkgMeta {
|
||||||
pkg_fmt: PkgFmt::default(),
|
pkg_fmt: PkgFmt::default(),
|
||||||
bin_dir: DEFAULT_BIN_PATH.to_string(),
|
bin_dir: DEFAULT_BIN_PATH.to_string(),
|
||||||
pub_key: None,
|
pub_key: None,
|
||||||
|
overrides: HashMap::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl PkgMeta {
|
||||||
|
/// Merge configuration overrides into object
|
||||||
|
pub fn merge(&mut self, pkg_override: &PkgOverride) {
|
||||||
|
if let Some(o) = pkg_override.pkg_fmt {
|
||||||
|
self.pkg_fmt = o;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Target specific overrides for binary installation
|
||||||
|
///
|
||||||
|
/// Exposed via `[package.metadata.TARGET]` in `Cargo.toml`
|
||||||
|
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||||
|
#[serde(rename_all = "kebab-case", default)]
|
||||||
|
pub struct PkgOverride {
|
||||||
|
/// Format for package downloads
|
||||||
|
pub pkg_fmt: Option<PkgFmt>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for PkgOverride {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
pkg_fmt: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||||
#[serde(rename_all = "kebab-case")]
|
#[serde(rename_all = "kebab-case")]
|
||||||
pub struct BinMeta {
|
pub struct BinMeta {
|
||||||
|
|
|
@ -97,11 +97,18 @@ async fn main() -> Result<(), anyhow::Error> {
|
||||||
let manifest = load_manifest_path(manifest_path.join("Cargo.toml"))?;
|
let manifest = load_manifest_path(manifest_path.join("Cargo.toml"))?;
|
||||||
let package = manifest.package.unwrap();
|
let package = manifest.package.unwrap();
|
||||||
|
|
||||||
let (meta, binaries) = (
|
let (mut meta, binaries) = (
|
||||||
package.metadata.map(|m| m.binstall ).flatten().unwrap_or(PkgMeta::default()),
|
package.metadata.map(|m| m.binstall ).flatten().unwrap_or(PkgMeta::default()),
|
||||||
manifest.bin,
|
manifest.bin,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Merge any overrides
|
||||||
|
if let Some(o) = meta.overrides.remove(&opts.target) {
|
||||||
|
meta.merge(&o);
|
||||||
|
}
|
||||||
|
|
||||||
|
debug!("Found metadata: {:?}", meta);
|
||||||
|
|
||||||
// Generate context for URL interpolation
|
// Generate context for URL interpolation
|
||||||
let ctx = Context {
|
let ctx = Context {
|
||||||
name: opts.name.clone(),
|
name: opts.name.clone(),
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue