From b14ff611c48dd9a3e319c16949cd3f8585e4cdb7 Mon Sep 17 00:00:00 2001 From: Crestwave <35413013+Crestwave@users.noreply.github.com> Date: Fri, 19 Jan 2024 19:56:39 +0800 Subject: [PATCH] cargo-to-recipe.sh: new tool (#176) This tool allows to partially automate the generation of a recipe from a Cargo dependency file. It reduces the amount of work needed to package Rust software. --- tools/cargo-to-recipe.sh | 329 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 329 insertions(+) create mode 100755 tools/cargo-to-recipe.sh diff --git a/tools/cargo-to-recipe.sh b/tools/cargo-to-recipe.sh new file mode 100755 index 00000000..fa1eaee4 --- /dev/null +++ b/tools/cargo-to-recipe.sh @@ -0,0 +1,329 @@ +#!/usr/bin/env bash + +die() { + printf %s "${*+$'\e[31merror: \e[0m'$*$'\n'}" + (( usage )) && usage + exit 1 +} >&2 + +usage() { + cat <<-EOF + Usage: $0 [options] [uri://to/source-tarball] category/port + + Creates a recipe template for a crates.io package, filled with + information at hand. Requires awk, coreutils, sed, tar (with the + appropriate decompression utility), and wget. + + Options: + -h, --help show this help message and exit + -k, --keep keep the generated temporary directory + -nc, --no-clobber + do not overwrite existing files + -psd, --print-source-directories + also print SOURCE_DIRs + -b, --bump + bump the crates.io dependencies of the port's highest + versioned recipe instead (overrides --no-clobber) + -c CMD, --cmd=CMD + specify the command runtime + EOF +} + +temp() { rm -rf "$tempdir"; } + +. "$(finddir B_USER_SETTINGS_DIRECTORY)"/haikuports.conf +(( $# )) || usage=1 die +while (( $# )); do + case $1 in + -h|--help) + usage + exit 0 + ;; + -k|--keep) + temp() { printf '%s\n' "Kept $tempdir"; } + ;; + -nc|--no-clobber) + nc=1 + shopt -s expand_aliases + alias mv='mv -n' + alias cp='cp -n' + ;; + -psd|--print-source-directories) + psd=3 + ;; + -b|--bump) + bump=1 + ;; + -c) + shift + ;& + --cmd=*) + cmd=${1#*=} + ;; + *://*) + SOURCE_URI=$1 + ;; + *-*/*) + directory=$TREE_PATH/$1 + : "${1//-/_}" + portName=${_#*/} + ;; + -?*) + usage=1 die "Invalid option." + ;; + *) + usage=1 die "Invalid argument." + ;; + esac + shift +done + +mkdir -p "$directory"/download +cd "$directory" || die "Invalid port directory." + +if (( bump )); then + eval "recipe=$( + ls -v --quoting-style=shell-escape \ + "$portName"-[0-9]*.[0-9]*.[0-9]*.recipe | tail -n 1 + )" + + portVersionedName=${recipe%.recipe} + portVersion=${portVersionedName#$portName-} + + defineDebugInfoPackage() { :; } + getPackagePrefix() { :; } + + . "$recipe" || die "Sourcing the recipe file failed." +fi + +: "${cmd:=$portName}" "${psd:=2}" "${SOURCE_URI%/}" +source_file=${_##*/} +: "${SOURCE_FILENAME:=$source_file}" + +case "" in + $directory) + usage=1 die "No category and/or port specified." + ;;& + $SOURCE_URI) + usage=1 die "SOURCE_URI is empty or unset." + ;;& + $CHECKSUM_SHA256) + if [[ nc -eq 0 || ! -e download/$SOURCE_FILENAME ]]; then + wget -O download/"$SOURCE_FILENAME" "$SOURCE_URI" || + die "Failed to download the source file." + fi + CHECKSUM_SHA256=1 + ;; +esac + +if [[ $CHECKSUM_SHA256 != 1 ]]; then + for (( i = 0; i < 3; ++i )); do + printf '%s\n' "$CHECKSUM_SHA256 download/$SOURCE_FILENAME" | + sha256sum -c && break + (( i < 2 )) && wget -O download/"$SOURCE_FILENAME" \ + "$( (( i < 1 )) && printf -- "-c")" "$SOURCE_URI" + done || die "Checksum verification failed." +else + : "$(sha256sum download/"$SOURCE_FILENAME")" + CHECKSUM_SHA256=${_::64} +fi + +: "$(tar --exclude=*/* -tf download/"$SOURCE_FILENAME")" +SOURCE_DIR=${_%/} +if test "$SOURCE_FILENAME" != "$SOURCE_DIR.tar.${source_file##*.}"; then + mv download/{"$SOURCE_FILENAME","$_"} + SOURCE_FILENAME=${_##*/} +fi + +tempdir=$(mktemp -d -t "$SOURCE_DIR".XXXXXX) +trap temp 0 +tar --transform "s|$SOURCE_DIR|${tempdir##*/}|" -C /tmp \ + -xf download/"$SOURCE_FILENAME" --wildcards "$SOURCE_DIR/Cargo.*" || + die "Failed to extract the necessary files." + +info=$( + if grep -q '\[metadata\]' "$tempdir"/Cargo.lock; then + sed -e '0,/\[metadata\]/d + s/checksum // + s/(.*)// + s/ /-/ + s/ = // + s/"//g' "$tempdir"/Cargo.lock + else + awk -F \" ' + /name|version|checksum/ { + if ($1 ~ "name") + i += 1 + data[i] = data[i] "," $2 + } + + END { + for (j in data) { + split(data[j], f, ",") + if (length(f[4]) == 64) + print f[2] " " f[3] " " f[4] + } + }' "$tempdir"/Cargo.lock | sort | sed 's/ /-/' + fi +) + +mapfile -t crates < <(awk '{ print $1".crate" }' <<< "$info") +mapfile -t checksums < <(awk '{ print $2 }' <<< "$info") +echo "$info" +for crate in "${crates[@]}"; do + uris+=("https://static.crates.io/crates/${crate%%-[0-9]*.[0-9]*.[0-9]*}/$crate") + (( psd == 3 )) && dirs+=("${crate%.crate}") +done + +for (( i = 0; j = i+2, i < ${#crates[@]}; i++ )); do + source_uris+=("SOURCE_URI_$j=\"${uris[i]}\"") + checksums_sha256+=("CHECKSUM_SHA256_$j=\"${checksums[i]}\"") + merged+=("${source_uris[i]}" "${checksums_sha256[i]}") + (( psd == 3 )) && { + source_dirs+=("SOURCE_DIR_$j=\"${dirs[i]}\"") + merged+=("${source_dirs[i]}") + } +done + +if (( bump )); then + sed -i \ + -e '/SOURCE_URI_2/,/ARCHITECTURES/ { /^A/!d }' \ + -e "/^ARCHITECTURES/i $(printf '%s\n' "${merged[@]}" | + sed '0~'"$psd"' a\\' | head -n -1 | + sed -z 's/\n/\\n/g')" \ + -e "s/{2\.\.[0-9]\+}/{2..$(( "${#crates[@]}" + 1 ))}/" \ + "$recipe" + exit +fi + +eval "$( + sed -n '/\[package\]/,/^$/ { + /"""\|\[/d + s/-\(.*=\)/_\1/ + s/ = /=/p + }' "$tempdir"/Cargo.toml +)" +cat <"$tempdir/$portName-$version.recipe" +SUMMARY="${description%.}" +DESCRIPTION="$( + extended=$( + grep -q extended-description "$tempdir"/Cargo.toml && + printf "extended-" + ) + + sed -n "/${extended}description"' = """/,/"""/ { + s/.*description = // + s/"""//g + /^$/d + p + }' "$tempdir"/Cargo.toml +)" +HOMEPAGE="$homepage" +COPYRIGHT="" +LICENSE="$( + IFS=$'\n' read -d "" -ra licenses <<< "$( + sed -e 's,/\| AND \| OR ,\n,g + s,-\([0-9]\)\.0, v\1,g' <<< "$license" | sort + )" + printf %s "${licenses[0]}" + (( ${#licenses[@]} > 1 )) && printf '\n\t%s' "${licenses[@]:1}" +)" +REVISION="1" +SOURCE_URI="$( + : "${SOURCE_URI//$version/\$portVersion}" + printf '%s\n' "${_/$homepage/\$HOMEPAGE}" +)" +CHECKSUM_SHA256="$CHECKSUM_SHA256" +$( + if [[ $source_file != "$SOURCE_FILENAME" ]]; then + : "${SOURCE_FILENAME/$version/\$portVersion}" + printf '%s\n' "SOURCE_FILENAME=\"$_\"" + fi + printf '\n' + printf '%s\n' "${merged[@]}" | sed '0~'"$psd"' a\\' +) + +ARCHITECTURES="!x86_gcc2 ?x86 ?x86_64" +commandBinDir=\$binDir +if [ "\$targetArchitecture" = x86_gcc2 ]; then +SECONDARY_ARCHITECTURES="?x86" +commandBinDir=\$prefix/bin +fi + +PROVIDES=" + $portName\$secondaryArchSuffix = \$portVersion + cmd:$cmd + " +REQUIRES=" + haiku\$secondaryArchSuffix + " + +BUILD_REQUIRES=" + haiku\${secondaryArchSuffix}_devel + " +BUILD_PREREQUIRES=" + cmd:cargo\$secondaryArchSuffix + cmd:gcc\$secondaryArchSuffix + " + +defineDebugInfoPackage $portName\$secondaryArchSuffix \\ + "\$commandBinDir"/$cmd + +BUILD() +{ + export CARGO_HOME=\$sourceDir/../cargo + vendor=\$CARGO_HOME/haiku + mkdir -p "\$vendor" + for i in \$(seq 2 $(( ${#crates[@]} + 1 ))); do + eval "srcDir=\\\$sourceDir\$i" + eval "sha256sum=\\\$CHECKSUM_SHA256_\$i" + set -- "\$srcDir"$( + (( psd == 2 )) && printf "/*" + ) + ln -sf "\$1" "\$vendor" + cat <<-EOF >"\$vendor/\${1##*/}/.cargo-checksum.json" + { + "package": "\$sha256sum", + "files": {} + } + EOF + done + + cat <<-EOF >"\$CARGO_HOME"/config + [source.haiku] + directory = "\$vendor" + + [source.crates-io] + replace-with = "haiku" + EOF + + cargo build --release --frozen +} + +INSTALL() +{ + install -m 755 -d "\$commandBinDir" "\$docDir" + install -m 755 target/release/$cmd "\$commandBinDir" + install -m 644 README.md "\$docDir" +} + +TEST() +{ + export CARGO_HOME=\$sourceDir/../cargo + cargo test --release --frozen +} +EOF +cp "$tempdir/$portName-$version.recipe" . + +if [[ -v license_file ]]; then + cat <<-EOF + ----------------------------------------------------------------------- + This port uses a custom license file. + It will be installed to the port's license directory; please rename it + as appropriate and add it to the recipe. + EOF + tar --transform "s|$SOURCE_DIR|${tempdir##*/}|" -C /tmp \ + -xf download/"$SOURCE_FILENAME" "${license_file/#./$SOURCE_DIR}" + mkdir -p licenses + cp "$tempdir"/"$license_file" licenses +fi