1#!/usr/bin/env bash
2
3die() {
4	printf %s "${*+$'\e[31merror: \e[0m'$*$'\n'}"
5	(( usage )) && usage
6	exit 1
7} >&2
8
9usage() {
10	cat <<-EOF
11	Usage: $0 [options] [uri://to/source-tarball] category/port
12
13	Creates a recipe template for a crates.io package, filled with
14	information at hand. Requires awk, coreutils, sed, tar (with the
15	appropriate decompression utility), and wget.
16
17	Options:
18	  -h, --help	show this help message and exit
19	  -k, --keep	keep the generated temporary directory
20	  -nc, --no-clobber
21	 		do not overwrite existing files
22	  -psd, --print-source-directories
23	 		also print SOURCE_DIRs
24	  -b, --bump
25	 		bump the crates.io dependencies of the port's highest
26	 		versioned recipe instead (overrides --no-clobber)
27	  -c CMD, --cmd=CMD
28	 		specify the command runtime
29	EOF
30}
31
32temp() { rm -rf "$tempdir"; }
33
34. "$(finddir B_USER_SETTINGS_DIRECTORY)"/haikuports.conf
35(( $# )) || usage=1 die
36while (( $# )); do
37	case $1 in
38		-h|--help)
39			usage
40			exit 0
41			;;
42		-k|--keep)
43			temp() { printf '%s\n' "Kept $tempdir"; }
44			;;
45		-nc|--no-clobber)
46			nc=1
47			shopt -s expand_aliases
48			alias mv='mv -n'
49			alias cp='cp -n'
50			;;
51		-psd|--print-source-directories)
52			psd=3
53			;;
54		-b|--bump)
55			bump=1
56			;;
57		-c)
58			shift
59			;&
60		--cmd=*)
61			cmd=${1#*=}
62			;;
63		*://*)
64			SOURCE_URI=$1
65			;;
66		*-*/*)
67			directory=$TREE_PATH/$1
68			: "${1//-/_}"
69			portName=${_#*/}
70			;;
71		-?*)
72			usage=1 die "Invalid option."
73			;;
74		*)
75			usage=1 die "Invalid argument."
76			;;
77	esac
78	shift
79done
80
81mkdir -p "$directory"/download
82cd "$directory" || die "Invalid port directory."
83
84if (( bump )); then
85	eval "recipe=$(
86		ls -v --quoting-style=shell-escape \
87			"$portName"-[0-9]*.[0-9]*.[0-9]*.recipe | tail -n 1
88	)"
89
90	portVersionedName=${recipe%.recipe}
91	portVersion=${portVersionedName#$portName-}
92
93	defineDebugInfoPackage() { :; }
94	getPackagePrefix() { :; }
95
96	. "$recipe" || die "Sourcing the recipe file failed."
97fi
98
99: "${cmd:=$portName}" "${psd:=2}" "${SOURCE_URI%/}"
100source_file=${_##*/}
101: "${SOURCE_FILENAME:=$source_file}"
102
103case "" in
104	$directory)
105		usage=1 die "No category and/or port specified."
106		;;&
107	$SOURCE_URI)
108		usage=1 die "SOURCE_URI is empty or unset."
109		;;&
110	$CHECKSUM_SHA256)
111		if [[ nc -eq 0 || ! -e download/$SOURCE_FILENAME ]]; then
112			wget -O download/"$SOURCE_FILENAME" "$SOURCE_URI" ||
113				die "Failed to download the source file."
114		fi
115		CHECKSUM_SHA256=1
116		;;
117esac
118
119if [[ $CHECKSUM_SHA256 != 1 ]]; then
120	for (( i = 0; i < 3; ++i )); do
121		printf '%s\n' "$CHECKSUM_SHA256  download/$SOURCE_FILENAME" |
122			sha256sum -c && break
123		(( i < 2 )) && wget -O download/"$SOURCE_FILENAME" \
124			"$( (( i < 1 )) && printf -- "-c")" "$SOURCE_URI"
125	done || die "Checksum verification failed."
126else
127	: "$(sha256sum download/"$SOURCE_FILENAME")"
128	CHECKSUM_SHA256=${_::64}
129fi
130
131: "$(tar --exclude=*/* -tf download/"$SOURCE_FILENAME")"
132SOURCE_DIR=${_%/}
133if test "$SOURCE_FILENAME" != "$SOURCE_DIR.tar.${source_file##*.}"; then
134	mv download/{"$SOURCE_FILENAME","$_"}
135	SOURCE_FILENAME=${_##*/}
136fi
137
138tempdir=$(mktemp -d -t "$SOURCE_DIR".XXXXXX)
139trap temp 0
140tar --transform "s|$SOURCE_DIR|${tempdir##*/}|" -C /tmp \
141	-xf download/"$SOURCE_FILENAME" --wildcards "$SOURCE_DIR/Cargo.*" ||
142	die "Failed to extract the necessary files."
143
144info=$(
145	if grep -q '\[metadata\]' "$tempdir"/Cargo.lock; then
146		sed -e '0,/\[metadata\]/d
147			s/checksum //
148			s/(.*)//
149			s/ /-/
150			s/ = //
151			s/"//g' "$tempdir"/Cargo.lock
152	else
153		awk -F \" '
154		/name|version|checksum/ {
155			if ($1 ~ "name")
156				i += 1
157			data[i] = data[i] "," $2
158		}
159
160		END {
161			for (j in data) {
162				split(data[j], f, ",")
163				if (length(f[4]) == 64)
164					print f[2] " " f[3] " " f[4]
165			}
166		}' "$tempdir"/Cargo.lock | sort | sed 's/ /-/'
167	fi
168)
169
170mapfile -t crates < <(awk '{ print $1".crate" }' <<< "$info")
171mapfile -t checksums < <(awk '{ print $2 }' <<< "$info")
172echo "$info"
173for crate in "${crates[@]}"; do
174	uris+=("https://static.crates.io/crates/${crate%%-[0-9]*.[0-9]*.[0-9]*}/$crate")
175	(( psd == 3 )) && dirs+=("${crate%.crate}")
176done
177
178for (( i = 0; j = i+2, i < ${#crates[@]}; i++ )); do
179	source_uris+=("SOURCE_URI_$j=\"${uris[i]}\"")
180	checksums_sha256+=("CHECKSUM_SHA256_$j=\"${checksums[i]}\"")
181	merged+=("${source_uris[i]}" "${checksums_sha256[i]}")
182	(( psd == 3 )) && {
183		source_dirs+=("SOURCE_DIR_$j=\"${dirs[i]}\"")
184		merged+=("${source_dirs[i]}")
185	}
186done
187
188if (( bump )); then
189	sed -i \
190		-e '/SOURCE_URI_2/,/ARCHITECTURES/ { /^A/!d }' \
191		-e "/^ARCHITECTURES/i $(printf '%s\n' "${merged[@]}" |
192			sed '0~'"$psd"' a\\' | head -n -1 |
193			sed -z 's/\n/\\n/g')" \
194		-e "s/{2\.\.[0-9]\+}/{2..$(( "${#crates[@]}" + 1 ))}/" \
195		"$recipe"
196	exit
197fi
198
199eval "$(
200	sed -n '/\[package\]/,/^$/ {
201		/"""\|\[/d
202		s/-\(.*=\)/_\1/
203		s/ = /=/p
204	}' "$tempdir"/Cargo.toml
205)"
206cat <<EOF >"$tempdir/$portName-$version.recipe"
207SUMMARY="${description%.}"
208DESCRIPTION="$(
209	extended=$(
210		grep -q extended-description "$tempdir"/Cargo.toml &&
211			printf "extended-"
212	)
213
214	sed -n "/${extended}description"' = """/,/"""/ {
215		s/.*description = //
216		s/"""//g
217		/^$/d
218		p
219	}' "$tempdir"/Cargo.toml
220)"
221HOMEPAGE="$homepage"
222COPYRIGHT=""
223LICENSE="$(
224	IFS=$'\n' read -d "" -ra licenses <<< "$(
225		sed -e 's,/\| AND \| OR ,\n,g
226			s,-\([0-9]\)\.0, v\1,g' <<< "$license" | sort
227	)"
228	printf %s "${licenses[0]}"
229	(( ${#licenses[@]} > 1 )) && printf '\n\t%s' "${licenses[@]:1}"
230)"
231REVISION="1"
232SOURCE_URI="$(
233	: "${SOURCE_URI//$version/\$portVersion}"
234	printf '%s\n' "${_/$homepage/\$HOMEPAGE}"
235)"
236CHECKSUM_SHA256="$CHECKSUM_SHA256"
237$(
238	if [[ $source_file != "$SOURCE_FILENAME" ]]; then
239		: "${SOURCE_FILENAME/$version/\$portVersion}"
240		printf '%s\n' "SOURCE_FILENAME=\"$_\""
241	fi
242	printf '\n'
243	printf '%s\n' "${merged[@]}" | sed '0~'"$psd"' a\\'
244)
245
246ARCHITECTURES="!x86_gcc2 ?x86 ?x86_64"
247commandBinDir=\$binDir
248if [ "\$targetArchitecture" = x86_gcc2 ]; then
249SECONDARY_ARCHITECTURES="?x86"
250commandBinDir=\$prefix/bin
251fi
252
253PROVIDES="
254	$portName\$secondaryArchSuffix = \$portVersion
255	cmd:$cmd
256	"
257REQUIRES="
258	haiku\$secondaryArchSuffix
259	"
260
261BUILD_REQUIRES="
262	haiku\${secondaryArchSuffix}_devel
263	"
264BUILD_PREREQUIRES="
265	cmd:cargo\$secondaryArchSuffix
266	cmd:gcc\$secondaryArchSuffix
267	"
268
269defineDebugInfoPackage $portName\$secondaryArchSuffix \\
270	"\$commandBinDir"/$cmd
271
272BUILD()
273{
274	export CARGO_HOME=\$sourceDir/../cargo
275	vendor=\$CARGO_HOME/haiku
276	mkdir -p "\$vendor"
277	for i in \$(seq 2 $(( ${#crates[@]} + 1 ))); do
278		eval "srcDir=\\\$sourceDir\$i"
279		eval "sha256sum=\\\$CHECKSUM_SHA256_\$i"
280		set -- "\$srcDir"$(
281			(( psd == 2 )) && printf "/*"
282		)
283		ln -sf "\$1" "\$vendor"
284		cat <<-EOF >"\$vendor/\${1##*/}/.cargo-checksum.json"
285		{
286		  "package": "\$sha256sum",
287		  "files": {}
288		}
289		EOF
290	done
291
292	cat <<-EOF >"\$CARGO_HOME"/config
293	[source.haiku]
294	directory = "\$vendor"
295
296	[source.crates-io]
297	replace-with = "haiku"
298	EOF
299
300	cargo build --release --frozen
301}
302
303INSTALL()
304{
305	install -m 755 -d "\$commandBinDir" "\$docDir"
306	install -m 755 target/release/$cmd "\$commandBinDir"
307	install -m 644 README.md "\$docDir"
308}
309
310TEST()
311{
312	export CARGO_HOME=\$sourceDir/../cargo
313	cargo test --release --frozen
314}
315EOF
316cp "$tempdir/$portName-$version.recipe" .
317
318if [[ -v license_file ]]; then
319	cat <<-EOF
320	-----------------------------------------------------------------------
321	This port uses a custom license file.
322	It will be installed to the port's license directory; please rename it
323	as appropriate and add it to the recipe.
324	EOF
325	tar --transform "s|$SOURCE_DIR|${tempdir##*/}|" -C /tmp \
326		-xf download/"$SOURCE_FILENAME" "${license_file/#./$SOURCE_DIR}"
327	mkdir -p licenses
328	cp "$tempdir"/"$license_file" licenses
329fi
330