1#!/bin/bash
2#
3# Copyright (c) 2009-2010 Haiku, Inc.
4# Distributed under the terms of the MIT License.
5#
6# Authors:
7#		Matt Madia, mattmadia@gmail.com
8#
9# Synopsis:
10#	Provides a controlled mechanism for end-users to install certain pre-built
11#	OptionalPackages. The script will determine the host information: the
12#	default GCC, availability of secondary GCC libs, and revision. Using this
13#	information, the user will be limited to the appropriate OptionalPackages
14#	that were available for that specific revision.
15#
16DISCLAIMER="\
17Disclaimer:\n\
18  This is a temporary solution for installing OptionalPackages.\n\
19  In time, there will be an official package manager.\n\
20  See these URL's for information on the in-development package manager.\n\
21    http://dev.haiku-os.org/wiki/PackageManagerIdeas\n\
22    http://dev.haiku-os.org/wiki/PackageFormat\n\
23	"
24
25USAGE="\
26Usage: ./installoptionalpackage [<pkg> [<pkg> ...]]\n\
27  or   ./installoptionalpackage [-a|-s <pkg> [<pkg> ...]]\n\
28  or   ./installoptionalpackage [-f|-h|-l]\n\
29\n\
30Options:\n\
31-a     Add one or more packages and all dependencies\n\
32-s     Show the final list of packages that would be installed\n\
33-f     Remove cached data and list installable packages\n\
34-h     Print this help.\n\
35-l     List installable packages\n\
36	"
37
38declare -A availablePackages
39declare availablePackagesKeys=""
40declare wantsToInstall=""
41declare alreadyInstalled=""
42# Some Packages cannot be installed,
43# as they require either the source code or compiled binaries
44declare packageIgnoreList="Bluetooth Development DevelopmentMin \
45DevelopmentBase ICU-devel ICU MandatoryPackages UserlandFS \
46WebPositive Welcome WifiFirmwareScriptData "
47
48
49function CreateInstallerScript()
50{
51	# This function will create a secondary script, containing all of the
52	# information needed to install the optional package and its dependencies
53
54#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
55	cat << EOF > ${tmpDir}/install-optpkg.sh
56#!/bin/bash
57
58tmpDir=${tmpDir}
59HAIKU_GCC_VERSION[1]=${HAIKU_GCC_VERSION[1]}
60isHybridBuild=${isHybridBuild}
61TARGET_ARCH=${TARGET_ARCH}
62HAIKU_IMAGE_HOST_NAME=`uname -n`
63#TODO: possibly add a CLI option to execute InstallSourceArchive
64HAIKU_INCLUDE_SOURCES=0
65$urlLine
66$sslPkgLine
67$sslUrlLine
68$webkitFileLine
69declare -a functionArgs
70expanderRulesFile=`finddir B_COMMON_DATA_DIRECTORY`/expander.rules
71if [ -f \${expanderRulesFile} ] ; then
72	expanderRulesFileExists=1
73fi
74
75
76function ParseFunctionArguments()
77{
78	# ParseFunctionArguments <args>
79	# Parse arguments for Jam wrapper functions into an array.
80	IN="\$@"
81 	OIFS=\$IFS
82 	IFS=":"
83
84 	local count=0
85	functionArgs=( )
86	for x in \$IN
87	do
88		functionArgs[\${count}]="\${x}"
89		((count++))
90	done
91 	IFS=\$OIFS
92}
93
94
95function TrimLeadingSpace()
96{
97	# TrimLeadingSpace <variable name>
98	eval local text='\$'"\$1"
99	local _outvar="\$1"
100
101 	local length=\${#text}
102 	((length--))
103 	if [ "\${text:0:1}" == ' ' ] ; then
104 		text=\${text#' '}
105 	fi
106
107	eval \$_outvar="'\$text'"
108}
109
110
111function TrimEndingSpace()
112{
113	# TrimEndingSpace <variable name>
114	eval local text='\$'"\$1"
115	local _outvar="\$1"
116
117 	local length=\${#text}
118 	((length--))
119 	if [ "\${text:\$length}" == ' ' ] ; then
120 		text=\${text%' '}
121 	fi
122
123	eval \$_outvar="'\$text'"
124}
125
126
127function Exit()
128{
129	# Exit <message>
130	# Wrapper for Jam rule
131	echo "\$@"
132	exit 1
133}
134
135
136function InstallOptionalHaikuImagePackage()
137{
138	# InstallOptionalHaikuImagePackage package : url : dirTokens : isCDPackage
139
140	# Wrapper for Jam rule
141	echo "Installing \$1 ..."
142	cd \$tmpDir
143
144	archiveFile=\`echo \$3 | sed -s "s/http.*\///"\`
145	if ! [ -f \$archiveFile ] ; then
146		echo "Downloading \$3 ..."
147		# TODO : add some error handling for downloads
148		local attempt=1
149		while [ \`wget -nv \$3 ; echo \$? \` -ne 0 ]; do
150			if [ \$attempt -eq 5 ]; then
151				break
152			fi
153			(( attempt++ ))
154			echo "Download attempt #\$attempt failed. Retrying ..."
155			if [ -e \$archiveFile ]; then
156				rm \$archiveFile
157			fi
158		done
159		if [ \$attempt -ge 5 ]; then
160			if [ -e \$archiveFile ]; then
161				rm \$archiveFile
162			fi
163			Exit "Max download retries exceeded. Halting installation."
164		fi
165	fi
166
167	local dirTokens='/boot'
168	local count=4
169	local i=0
170	for possibleToken in "\$@" ; do
171		if [ \$i -lt \$count ] ; then
172			((i++))
173		else
174			((i++))
175			if [ "\$possibleToken" != ':' ] ; then
176				dirTokens=\${dirTokens}/\$possibleToken
177			else
178				break
179			fi
180		fi
181	done
182	echo "Extracting \$archiveFile ..."
183	extractDir="\${dirTokens}"
184
185	local errorMessage="
186...Failed while extracting \$archiveFile
187You may need to manually clean up the partially extracted files.
188	"
189	case "\$archiveFile" in
190		*.zip)
191			unzip -q -o -d "\$extractDir" "\$archiveFile" \
192			|| Exit "\$errorMessage"
193			;;
194		*.tgz|*.tar.gz)
195			tar -C "\$extractDir" -xf "\$archiveFile" \
196			|| Exit "\$errorMessage"
197			;;
198		*)
199			echo "Unhandled archive extension in InstallOptionalHaikuImagePackage()"
200			exit 1
201			;;
202	esac
203
204	if [ -f '/boot/.OptionalPackageDescription' ] ; then
205		rm '/boot/.OptionalPackageDescription'
206	fi
207	rm "\$archiveFile"
208}
209
210
211function InstallSourceArchive()
212{
213	if [ \$HAIKU_INCLUDE_SOURCES -gt 0 ]; then
214		echo "InstallSourceArchive is not implemented."
215	fi
216}
217
218
219function AddSymlinkToHaikuImage()
220{
221	# AddSymlinkToHaikuImage <dir tokens> : <link target> [ : <link name> ]
222	# Wrapper for Jam rule
223 	ParseFunctionArguments "\$@"
224
225 	local dirTokens="/boot/\${functionArgs[0]}"
226 	TrimLeadingSpace dirTokens
227	TrimEndingSpace dirTokens
228 	dirTokens=\${dirTokens//' '/\/}
229
230 	local linkTarget="\${functionArgs[1]}"
231 	TrimLeadingSpace linkTarget
232	TrimEndingSpace linkTarget
233
234 	local linkName="\${functionArgs[2]}"
235 	TrimLeadingSpace linkName
236	TrimEndingSpace linkName
237
238	mkdir -p "\${dirTokens}"
239
240	if [ "\${linkName}" == '' ] ; then
241		ln -sf "\${linkTarget}" -t "\${dirTokens}"
242	else
243		ln -sf "\${linkTarget}" "\${dirTokens}/\${linkName}"
244	fi
245}
246
247
248function AddUserToHaikuImage()
249{
250	# AddUserToHaikuImage user : uid : gid : home : shell : realName
251	# Wrapper for Jam rule
252	ParseFunctionArguments "\$@"
253
254	local user=\${functionArgs[0]}
255 	local uid=\${functionArgs[1]}
256 	local gid=\${functionArgs[2]}
257 	local home=\${functionArgs[3]}
258 	local shell=\${functionArgs[4]}
259 	local realName=\${functionArgs[5]}
260
261 	passwdLine="\${user}:x:\${uid}:\${gid}:\${realName}:\${home}:\${shell}"
262 	passwdLine=\${passwdLine//' :'/':'}
263 	passwdLine=\${passwdLine//': '/':'}
264
265 	local length=\${#passwdLine}
266 	((length--))
267 	if [ "\${passwdLine:\$length}" == ' ' ] ; then
268 		passwdLine=\${passwdLine%' '}
269 	fi
270
271 	passwdFile="\`finddir B_COMMON_ETC_DIRECTORY\`/passwd"
272 	touch \${passwdFile}
273
274 	local userExists=1
275 	while read line ; do
276		if [ "\${passwdLine}" == "\${line}" ] ; then
277			userExists=0
278		fi
279	done < \${passwdFile}
280
281	if [ \$userExists -ge 1 ] ; then
282		echo "\${passwdLine}" >> \${passwdFile}
283	fi
284}
285
286
287function AddExpanderRuleToHaikuImage()
288{
289	# AddExpanderRuleToHaikuImage <mimetype> : <extension> : <list> : <extract>
290	# Wrapper for Jam rule
291	ParseFunctionArguments "\$@"
292
293	local mimetype=\${functionArgs[0]}
294	local extension=\${functionArgs[1]}
295	local list=\${functionArgs[2]}
296	local extract=\${functionArgs[3]}
297
298	# clean up the variables
299	TrimLeadingSpace mimetype
300	TrimEndingSpace mimetype
301	TrimLeadingSpace extension
302	TrimEndingSpace extension
303	TrimLeadingSpace list
304	TrimEndingSpace list
305	TrimLeadingSpace extract
306	TrimEndingSpace extract
307	local rule_raw="\${mimetype}\\t\${extension}\\t\${list}\\t\${extract}"
308
309	# reset this at every invocation
310	ruleFound=
311
312	if [ \${expanderRulesFileExists} ] ; then
313		# Check if a rule for the mimetype & extension exists.
314		while read line ; do
315			existing_rule=`echo \$line | awk '{ print \$1\$2 }'`
316			if [ "\${mimetype}\${extension}" == "\${existing_rule}" ] ; then
317				ruleFound=1
318				break
319			fi
320		done < "\${expanderRulesFile}"
321	fi
322	if ! [ \${expanderRulesFileExists} ] || ! [ \${ruleFound} ] ; then
323		# Either expander.rules does not exist or a rule for mimetype &
324		# extension does not exist. Output the new rule directly to it.
325		echo -e \${rule_raw} >> \${expanderRulesFile}
326	fi
327}
328
329
330EOF
331#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
332	cat ${tmpDir}/optpkg.stage2 >> ${tmpDir}/install-optpkg.sh
333	rm ${tmpDir}/optpkg.stage2
334}
335
336
337function ContainsSubstring()
338{
339	# ContainsSubstring <stringToLookIn> <stringToLookFor>
340	local string="$1"
341	local substring="$2"
342	local newString=${string/${substring}/''}
343	if [ ${#string} -eq $((${#newString} + ${#substring})) ] ; then
344		return 0
345	fi
346	return 1
347}
348
349
350function ErrorExit()
351{
352	echo $1
353	exit 1
354}
355
356
357function Init()
358{
359
360	# Set up some directory paths
361	baseDir=`finddir B_COMMON_DATA_DIRECTORY`/optional-packages
362	tmpDir=`finddir B_COMMON_TEMP_DIRECTORY`
363	libDir=`finddir B_SYSTEM_LIB_DIRECTORY`
364
365	installedPackagesFile="${baseDir}/InstalledPackages"
366
367	# Make sure these files are empty.
368	echo "" > ${tmpDir}/optpkg.jam
369	echo "" > ${tmpDir}/optpkg.stage1
370
371	if ! [ -d ${baseDir} ] ; then
372		mkdir -p ${baseDir}
373	fi
374
375	DetectSystemConfiguration
376	DownloadAllBuildFiles
377	ReadInstalledPackagesIntoMemory
378	ReadPackageNamesIntoMemory
379}
380
381
382function DownloadAllBuildFiles()
383{
384	# DownloadAllBuildFiles
385	# Retreive the necessary jam files from svn.
386	local buildFiles="BuildFeatures OptionalPackageDependencies \
387		OptionalPackages OptionalLibPackages"
388	for file in ${buildFiles} ; do
389		GetBuildFile ${file}
390	done
391
392}
393
394
395function GetBuildFile()
396{
397	# GetBuildFile <file>
398	# Downloads files from Haiku's svn
399	local buildfile="$1"
400	if ! [ -f ${baseDir}/${buildfile} ] ; then
401		echo "Fetching ${buildfile} ..."
402		cd ${baseDir}
403		local baseURL=http://cgit.haiku-os.org/haiku/plain
404		local revisionTag=`uname -v | awk '{print $1}' | sed -e 's/-.*//'`
405			# the sed invocation above drops potential dirty markers off the
406			# revision tag
407		local url="${baseURL}/build/jam/${buildfile}?id=${revisionTag}"
408		wget -q ${url} -O ${buildfile} \
409			|| ErrorExit "...failed to download $buildfile"
410	fi
411}
412
413
414function DetectSystemConfiguration()
415{
416
417	# Determine which GCC we're running
418	if [ -f "$libDir"/libsupc++.so ] ; then
419		HAIKU_GCC_VERSION[1]=4
420	else
421		HAIKU_GCC_VERSION[1]=2
422	fi
423
424	# Test for hybrid
425	if [ -d "$libDir"/gcc4 -a -d "$libDir"/gcc2 ]; then
426		echo "Sorry, but your build appears to be broken ..."
427		echo "Both gcc2 and gcc4 subdirs exist."
428		exit 1
429	elif [ -d "$libDir"/gcc4 -o -d "$libDir"/gcc2 ]; then
430		isHybridBuild=1
431	else
432		isHybridBuild=""
433	fi
434
435	# Determine the Architecture.
436	if [ `uname -m` == "BePC" ] ; then
437		TARGET_ARCH='x86'
438	elif [ `uname -m` == "x86_64" ] ; then
439		TARGET_ARCH='x86_64'
440	else
441		echo "Sorry, x86 only for now."
442		exit 1
443	fi
444}
445
446
447function ReadInstalledPackagesIntoMemory()
448{
449	while read line ; do
450		alreadyInstalled="${alreadyInstalled} $line"
451		packageIgnoreList=${packageIgnoreList/"${line} "/' '}
452	done < ${installedPackagesFile}
453}
454
455
456function ReadPackageNamesIntoMemory()
457{
458	local file="${baseDir}/OptionalPackageNames"
459	if ! [ -f ${file} ] ; then
460		GeneratePackageNames
461	fi
462
463	# read list into associative array
464	while read line ; do
465		local pkg=`echo ${line} | awk '{print $1}'`
466		local pkgDeps=${line/"${pkg} :"/}
467		availablePackages[${pkg}]="${pkgDeps}"
468		availablePackagesKeys="${availablePackagesKeys} ${pkg}"
469	done < ${file}
470}
471
472
473function GeneratePackageNames()
474{
475	# GeneratePackageNames
476	# Creates a file containing available package names
477	# Each line shows a pakage and all of its recrusive dependencies
478	# "<pkg> : <dep1> <dep2> ..."
479	echo "Generating a list of Package Names ..."
480
481	local file="${baseDir}/OptionalPackageNames"
482	if [ -e "${file}" ]; then
483		rm "${file}"
484	fi
485
486	local regExp='/^if\ \[\ IsOptionalHaikuImagePackageAdded/p'
487	sed -n -e "$regExp" ${baseDir}/OptionalPackages > ${file}.temp
488	sed -n -e "$regExp" ${baseDir}/OptionalLibPackages >> ${file}.temp
489	while read line ; do
490		# in each non-filtered line, the 4th word is the optional package
491		local pkg=`echo ${line} | awk '{print $4}'`
492
493		nonRepeatingDeps=""
494		GetPackageDependencies "$pkg"
495		local lowerCasePkg=`echo ${pkg} | tr '[A-Z]' '[a-z]'`
496		if ! ContainsSubstring "${alreadyInstalled} " "${pkg} " ; then
497			if IsPackageAndDepsOkToInstall ${pkg} ; then
498				echo "${lowerCasePkg} : ${pkg} ${nonRepeatingDeps}"  >> ${file}
499			fi
500		fi
501
502	done < ${file}.temp
503	rm ${file}.temp
504}
505
506
507function GetPackageDependencies()
508{
509	# GetPackageDependencies <pkg>
510
511	# parse OptionalPackageDependencies for the single line that defines
512	# this optional package's dependencies.
513	local regExp="^OptionalPackageDependencies\ ${1}\ \:"
514	local inputFile="${baseDir}/OptionalPackageDependencies"
515
516	# print that single line
517	sed -n -e "/${regExp}\ /p" ${inputFile} > ${tmpDir}/optpkg.temp
518
519	# strip out "OptionalPackageDependencies PackageName :"
520	# this leaves "<dep1> .... ;"
521	tempDeps=`sed -e "s/${regExp}\ //" ${tmpDir}/optpkg.temp`
522
523	for foo in ${tempDeps%' ;'} ; do
524		# Prevent duplicate entries of the same dependency package.
525		if ! ContainsSubstring "${nonRepeatingDeps} " "${foo} " ; then
526			nonRepeatingDeps="$foo $nonRepeatingDeps "
527			nonRepeatingDeps="${nonRepeatingDeps//  / }"
528		fi
529	done
530
531	# Recursively get the dependencies of these dependencies.
532	for dep in ${tempDeps%' ;'} ; do
533		GetPackageDependencies "$dep"
534	done
535
536}
537
538
539function IsPackageAndDepsOkToInstall()
540{
541	# IsPackageAndDepsOkToInstall <pkg>
542	if ContainsSubstring "${packageIgnoreList}" "${1}"; then
543		#echo "...warning: ${1} cannot be installed"
544		return 1
545	fi
546	for foo in ${nonRepeatingDeps} ; do
547		if ContainsSubstring "${packageIgnoreList}" "${foo}"; then
548			#echo "...warning: ${1} cannot be installed because of ${foo}"
549			return 1
550		fi
551	done
552	return 0
553}
554
555
556function BuildListOfRequestedPackages()
557{
558	if [ "$1" = '-a' ] || [ "$1" = '-s' ]; then
559		shift
560	fi
561	while [ $# -gt 0 ]; do
562		local lowerCase=`echo $1 | tr '[A-Z]' '[a-z]'`
563		wantsToInstall="${wantsToInstall} $lowerCase"
564		shift
565	done
566}
567
568
569function AddPackages()
570{
571	# AddPackages
572
573	# If one or more packages can be installed, do it.
574	if BuildFinalListOfPackagesToInstall ; then
575
576		for package in ${packagesToInstall} ; do
577			# output the "if [ IsOptionalHaikuImagePackageAdded..." code block
578			local regExp="if\ \[\ IsOptionalHaikuImagePackageAdded\ ${package}\ "
579			for inputFile in OptionalPackages OptionalLibPackages ; do
580				sed -n "/^$regExp/,/^\}/p" "${baseDir}/${inputFile}" >> ${tmpDir}/optpkg.jam
581			done
582		done
583
584		ConvertJamToBash "${tmpDir}/optpkg.jam"
585		rm "${tmpDir}/optpkg.jam"
586		CreateInstallerScript
587		sh ${tmpDir}/install-optpkg.sh
588		exitcode=$?
589		if [ $exitcode -gt 0 ]; then
590			ErrorExit "... something went wrong when installing packages."
591		fi
592		rm ${tmpDir}/install-optpkg.sh
593
594		# update files to account for the newly installed packages
595		alreadyInstalled="${alreadyInstalled} ${packagesToInstall} "
596		RecordInstalledPackages
597		GeneratePackageNames
598		echo "... done."
599	fi
600}
601
602
603function BuildFinalListOfPackagesToInstall()
604{
605	# BuildFinalListOfPackagesToInstall
606
607	packagesToInstall=""
608	proceedWithInstallation=false
609
610	for desiredPackage in ${wantsToInstall}; do
611		if IsPackageNameValid $desiredPackage  ; then
612			for item in ${availablePackages[${desiredPackage}]} ; do
613				if ! ContainsSubstring "${packagesToInstall}" "${item}" ; then
614					packagesToInstall="${packagesToInstall} ${item}"
615				fi
616			done
617			proceedWithInstallation=true
618		fi
619	done
620	# pad the variable
621	packagesToInstall="${packagesToInstall} "
622	# remove entries that are already installed
623	for skip in ${alreadyInstalled}; do
624		packagesToInstall=${packagesToInstall/"${skip} "/}
625	done
626	# strip double spaces
627	packagesToInstall=${packagesToInstall/"  "/" "}
628
629	if ! [ ${#packagesToInstall} -gt 1 ]; then
630		echo "... no packages need to be installed."
631		echo ""
632		echo "If you wish to re-install a package, run these two commands"
633		echo "    rm ${baseDir}/OptionalPackageNames"
634		echo "    open $installedPackagesFile"
635		echo "and delete the line containing the package name(s)."
636		echo ""
637		proceedWithInstallation=false
638	fi
639	if ! $proceedWithInstallation ; then
640		echo 'Not proceeding with installation.'
641		return 1
642	fi
643	echo "To be installed: ${packagesToInstall}"
644	return 0
645}
646
647
648function IsPackageNameValid()
649{
650	# IsPackageNameValid <name>
651	for name in ${availablePackagesKeys} ; do
652		if [ "$1" == "$name" ] ; then
653			return 0
654		fi
655	done
656	return 1
657}
658
659
660function RecordInstalledPackages()
661{
662	echo -e ${alreadyInstalled} | tr '\ ' '\n' | sort > ${installedPackagesFile}
663}
664
665
666function ConvertJamToBash()
667{
668	# ConvertJamToBash <input file>
669	# The main Jam-to-Bash conversion function.
670	local inputFile=$1
671	declare -a generatedBash
672	countGenBashLine=0
673
674	# Parse out some variable declarations
675
676	# TODO : add these following variables to the CreateInstallerScript
677	# TODO : parse HAIKU_ICU_GCC_2_PACKAGE
678	#local regExp='/^HAIKU_ICU_GCC_2_PACKAGE/p'
679	#icuGcc2PkgLine=`sed -n -e "$regExp" ${baseDir}/BuildFeatures`
680	#ConvertVariableDeclarationLines "$regExp" 'icuGcc2PkgLine'
681
682	# TODO : parse HAIKU_ICU_GCC_4_PACKAGE
683	#local regExp='/^HAIKU_ICU_GCC_4_PACKAGE/p'
684	#icuGcc4PkgLine=`sed -n -e "$regExp" ${baseDir}/BuildFeatures`
685	#ConvertVariableDeclarationLines "$regExp" 'icuGcc4PkgLine'
686
687	# TODO : parse HAIKU_ICU_DEVEL_PACKAGE
688	#local regExp='/^HAIKU_ICU_DEVEL_PACKAGE/p'
689	#icuDevelPkgLine=`sed -n -e "$regExp" ${baseDir}/BuildFeatures`
690	#ConvertVariableDeclarationLines "$regExp" 'icuDevelPkgLine'
691
692	# TODO : fix the regex
693	local regExp="/^\s*HAIKU_OPENSSL_PACKAGE = .*-gcc${HAIKU_GCC_VERSION[1]}-/p"
694	sslPkgLine=`sed -n -e "$regExp" ${baseDir}/BuildFeatures`
695	ConvertVariableDeclarationLines "$regExp" 'sslPkgLine'
696
697	# TODO : fix the regex
698	local regExp='/^HAIKU_OPENSSL_URL/p'
699	sslUrlLine=`sed -n -e "$regExp" ${baseDir}/BuildFeatures`
700	ConvertVariableDeclarationLines "$regExp" 'sslUrlLine'
701
702	# TODO : fix the regex
703	local regExp='/^HAIKU_WEBKIT_FILE/p'
704	webkitFileLine=`sed -n -e "$regExp" ${baseDir}/BuildFeatures`
705	ConvertVariableDeclarationLines "$regExp" 'webkitFileLine'
706
707	local regExp='/^local\ baseURL/p'
708	urlLine=`sed -n -e "$regExp" ${baseDir}/OptionalPackages`
709	urlLine=${urlLine/local\ /''}
710	ConvertVariableDeclarationLines "$regExp" 'urlLine'
711
712	# Convert the easy bits.
713	while read line ; do
714		line=${line/'Echo'/'echo'}
715
716		# TODO: add support for converting for loops.
717		# 		will need to introduce curly brace counting
718		ConvertIfStatements "$line"
719		ConvertVariables "$line"
720		#ReplaceComparators "$line"
721
722		line=${line/"IsOptionalHaikuImagePackageAdded"/'"SomeText" !='}
723		generatedBash[$countGenBashLine]=${line}
724		((countGenBashLine++))
725	done < ${tmpDir}/optpkg.jam
726
727	# output stage 1 generated code
728	local i=0
729	while [ $i -lt $countGenBashLine ] ; do
730		echo ${generatedBash[$i]} >> ${tmpDir}/optpkg.stage1
731		((i++))
732	done
733
734	# This converts multi-line jam statements into a single line.
735	# --- Start awk ---
736	awk '
737		/InstallOptionalHaikuImagePackage/,/\;/{
738			isRule=1;
739			if($0~/\;/) ORS="\n";
740			else ORS=" "; print
741		}
742		/AddSymlinkToHaikuImage/,/\;/{
743			isRule=1;
744			if($0~/\;/) ORS="\n";
745			else ORS=" "; print
746		}
747		/AddUserToHaikuImage/,/\;/{
748			isRule=1;
749			if($0~/\;/) ORS="\n";
750			else ORS=" "; print
751		}
752		/AddExpanderRuleToHaikuImage/,/\;/{
753			isRule=1;
754			if($0~/\;/) ORS="\n";
755			else ORS=" "; print
756		}
757		/Exit/,/\;/{
758			isRule=1;
759			if($0~/\;/) ORS="\n";
760			else ORS=" "; print
761		}
762		{
763			if($1!='InstallOptionalHaikuImagePackage' && isRule!=1 && $1!="\;")
764		 	print $0
765		}
766		{ isRule=0; }
767		' ${tmpDir}/optpkg.stage1 > ${tmpDir}/optpkg.stage2 2>/dev/null
768	# --- End awk ---
769	rm ${tmpDir}/optpkg.stage1
770}
771
772
773function ConvertVariableDeclarationLines()
774{
775	# ConvertVariableDeclarationLines <regex> <variable>
776	# One of the Jam-to-Bash conversion functions.
777	# Jam lines that define variables need to be parsed differently.
778	eval local input='$'"$2"
779	local regex="$1"
780	local _outvar="$2"
781
782	input=${input/\ =\ /=}
783	input=${input/\;/''}
784	input=${input//\(/'{'}
785	input=${input//\)/'}'}
786
787	eval $_outvar="'$input'"
788}
789
790
791function ConvertIfStatements()
792{
793	# ConvertIfStatements <line>
794	# One of the Jam-to-Bash conversion functions.
795	line=${line//'} else {'/'else '}
796	line=${line//'} else if '/'elif '}
797	if ContainsSubstring "$line" "if " ; then
798		if ! ContainsSubstring "$line" "if [" ; then
799			line=${line/'if '/'if [ '}
800		fi
801
802		if ContainsSubstring "$line" '] {' ; then
803			line=${line/'{'/' ; then'}
804		elif ContainsSubstring "$line" '{' ; then
805			line=${line/'{'/' ] ; then'}
806		fi
807
808		for compound in '&&' '||' ; do
809			if ContainsSubstring "$line" "$compound" ; then
810				line=${line/"$compound"/"] $compound ["}
811			fi
812		done
813		ReplaceComparators "$line"
814	fi
815	# Assume all remaining closing braces are part of if statements
816	line=${line/'}'/'fi'}
817}
818
819
820function ConvertVariables()
821{
822	# ConvertVariables
823	# One of the Jam-to-Bash conversion functions.
824
825	# NOTE: jam's variables are normally '$(VARIABLE)'. \n
826	# 		The issue is with '(' and ')', so let's replace them globally.
827	if ContainsSubstring "$line" '$(' ; then
828		line=${line//'('/'{'}
829		line=${line//')'/'}'}
830	fi
831}
832
833
834function ReplaceComparators()
835{
836	# ReplaceComparators <line>
837	# One of the Jam-to-Bash conversion functions.
838
839	# Preserve string comparators for TARGET_ARCH.
840	if ! ContainsSubstring "$line" 'TARGET_ARCH' ; then
841		line=${line//'>='/'-ge'}
842		line=${line//'<='/'-le'}
843		line=${line//'>'/'-gt'}
844		line=${line//'<'/'-lt'}
845		line=${line//'!='/'-ne'}
846		line=${line//'='/'-eq'}
847	fi
848}
849
850
851function DisplayUsage()
852{
853	echo -e "$DISCLAIMER"
854	echo -e "$USAGE"
855}
856
857
858function RemoveCachedFiles()
859{
860	# RemoveCachedFiles
861	echo "Removing cached files ..."
862	if [ -e ${baseDir}/OptionalPackageNames ]; then
863		rm ${baseDir}/OptionalPackageNames
864	fi
865
866	# Unset variables, which prevents duplicate entries.
867	declare -A availablePackages
868	declare availablePackagesKeys=""
869
870	# Reinitialize
871	Init
872}
873
874
875function ListPackages()
876{
877	# ListPackages
878	echo ""
879	echo "Optional Packages that have been installed:"
880	echo ${alreadyInstalled}
881
882	echo ""
883	echo ""
884	echo "Installable Optional Packages:"
885
886	# single line:
887	echo ${availablePackagesKeys}
888
889	# one per line:
890	#for package in ${availablePackagesKeys} ; do
891	#	echo ${package}
892	#done
893}
894
895
896# If no arguments were passed to the script, display its usage and exit.
897if [ "$#" -lt 1 ] ; then
898	DisplayUsage
899	exit 0
900else
901	Init
902fi
903
904# Support `installoptionalpackage <pkg> <pkg> ...`
905if [ "$1" != '-f' ] && [ "$1" != '-l' ] && [ "$1" != '-h' ] \
906	&& [ "$1" != '-s' ]; then
907	BuildListOfRequestedPackages $@
908	AddPackages
909	exit 0
910fi
911
912# Parse the arguments given to the script.
913while getopts "as:fhl" opt; do
914	case $opt in
915		a)
916			BuildListOfRequestedPackages $@
917			AddPackages
918			exit 0
919			;;
920		f)
921			RemoveCachedFiles
922			ListPackages
923			exit 0
924			;;
925		h)
926			DisplayUsage
927			exit 0
928			;;
929		l)
930			ListPackages
931			exit 0
932			;;
933		s)
934			BuildListOfRequestedPackages $@
935			BuildFinalListOfPackagesToInstall
936			exit 0
937			;;
938		\?)
939			echo "Invalid option: -$OPTARG" >&2
940			exit 1
941			;;
942		:)
943			echo "Option -$OPTARG requires an argument." >&2
944			exit 1
945			;;
946	esac
947done
948