qi.in 40 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397
  1. #! /bin/sh -
  2. # Copyright (C) 2016-2018 Matias Fonzo <selk@dragora.org>
  3. #
  4. # This program is free software: you can redistribute it and/or modify
  5. # it under the terms of the GNU General Public License as published by
  6. # the Free Software Foundation, either version 3 of the License, or
  7. # (at your option) any later version.
  8. #
  9. # This program is distributed in the hope that it will be useful,
  10. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  12. # GNU General Public License for more details.
  13. #
  14. # You should have received a copy of the GNU General Public License
  15. # along with this program. If not, see <http://www.gnu.org/licenses/>.
  16. # EXIT STATUS
  17. # 0 = Successful completion
  18. # 1 = Minor common errors (e.g: help usage, support not available)
  19. # 2 = Command execution error
  20. # 3 = Integrity check error for compressed files
  21. # 4 = File empty, not regular, or expected
  22. # 5 = Empty or not defined variable
  23. # 6 = Package already installed
  24. # 10 = Network manager error
  25. #### Functions
  26. usage()
  27. {
  28. printf "%s\n" \
  29. "Qi - A practical and user-friendly package manager." \
  30. "" \
  31. "Usage: $PROGRAM [OPTION...] [FILE]..." \
  32. "" \
  33. "Operation mode:" \
  34. " -b Build packages using recipes" \
  35. " -c Create .tlz package from directory" \
  36. " -d Delete packages" \
  37. " -i Install packages" \
  38. " -o Resolve build order through .order files" \
  39. " -u Update packages (implies -i, -d with -p)" \
  40. " -x Extract packages for debugging purposes" \
  41. "" \
  42. "Common options:" \
  43. " -L Print and exit default directory locations" \
  44. " -N Don't read the configuration file" \
  45. " -P <DIR> Package directory for (de)installations;" \
  46. " only valid for -i, -d, or -u options" \
  47. " -f Force package upgrade or force to proceed" \
  48. " with a recipe" \
  49. " -t <DIR> Target directory for symbolic links;" \
  50. " only valid for -i, -d, or -u options" \
  51. " -k Keep (don't delete) srcdir or destdir" \
  52. " Keep (don't delete) package directory" \
  53. " this is for -b, -d or -u options" \
  54. " -p Prune conflicts on package (de)installation" \
  55. " -r Use the named directory as the root directory" \
  56. " for installing, deleting, or upgrading packages." \
  57. " The target directory, package directory will be" \
  58. " relative to this specific directory" \
  59. " -v Be verbose (a 2nd -v gives more)" \
  60. "" \
  61. "Options for 'build' mode (-b):" \
  62. " -O <DIR> Where the produced packages are written" \
  63. " -W <DIR> Where archives, patches, and recipes are expected" \
  64. " -Z <DIR> Where (compressed) sources will be found" \
  65. " -a Architecture to use [detected]" \
  66. " -j Parallel jobs for the compiler" \
  67. " -1 Increment release number (release + 1)" \
  68. " -3 Resume skipping completed recipes option" \
  69. " -n Don't create a .tlz package" \
  70. "" \
  71. "Other options:" \
  72. " -h Display this help and exit" \
  73. " -V Output version information" \
  74. "" \
  75. "Some influential environment variables:" \
  76. " TMPDIR Temporary directory for sources" \
  77. " QICFLAGS C compiler flags" \
  78. " QICXXFLAGS C++ compiler flags" \
  79. " QILDFLAGS Linker flags" \
  80. "" \
  81. "When FILE is -, read standard input." \
  82. ""
  83. }
  84. version()
  85. {
  86. printf "%s\n" \
  87. "$PROGRAM @VERSION@" \
  88. "Copyright (C) 2016-2018 Matias Andres Fonzo." \
  89. "License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>" \
  90. "This is free software: you are free to change and redistribute it." \
  91. "There is NO WARRANTY, to the extent permitted by law."
  92. }
  93. warn()
  94. {
  95. printf "%s\n" "$@" 1>&2
  96. }
  97. is_readable()
  98. {
  99. if test -e "$1"
  100. then
  101. if ! test -r "$1"
  102. then
  103. echo "${PROGRAM}: cannot read ${1}: Permission denied" 1>&2
  104. return 1
  105. fi
  106. else
  107. echo "${PROGRAM}: cannot access ${1}: No such file or directory" 1>&2
  108. return 1
  109. fi
  110. }
  111. # Portable alternative to the file operator -nt (among shells)
  112. is_newer()
  113. {
  114. if test -n "$(find $1 -prune -newer $2 -print)"
  115. then
  116. return 0
  117. fi
  118. return 1
  119. }
  120. # Determine whether $2 matches pattern $1
  121. fnmatch()
  122. {
  123. case $2 in
  124. $1)
  125. return 0
  126. ;;
  127. *)
  128. return 1
  129. ;;
  130. esac
  131. }
  132. chkstatus_or_exit()
  133. {
  134. status=$?
  135. if test $status -ne 0
  136. then
  137. echo "Return status = $status" 1>&2
  138. exit ${1-2}; # If not given, defaults to 2
  139. fi
  140. unset status
  141. }
  142. readconfig()
  143. {
  144. if test $RC = RC
  145. then
  146. is_readable "$HOME/.qirc" 2> /dev/null && RCFILE="$HOME/.qirc";
  147. echo "Processing \`${RCFILE}' ..."
  148. test -f "$RCFILE" || {
  149. warn "${RCFILE} is not a regular file."
  150. return 1
  151. }
  152. # Parse config file
  153. while IFS='=' read -r variable value
  154. do
  155. case $variable in
  156. \#* | "") # Ignore commented or blank lines
  157. continue
  158. ;;
  159. esac
  160. # Set variable name avoiding code execution
  161. eval "$variable=\${value}"
  162. done < "$RCFILE"
  163. fi
  164. }
  165. #### Mode functions
  166. build_mode()
  167. {
  168. recipe=$1
  169. # A recipe is any valid regular file, the current working directory
  170. # has priority over the working tree (or where the recipes reside).
  171. # The 'worktree' is the second place where to find a recipe. Also,
  172. # we complete the possibility of using the directory name to invoke
  173. # a recipe if it contains "recipe" as valid file name.
  174. if test ! -f "$recipe"
  175. then
  176. if test -f "${recipe}/recipe"
  177. then
  178. recipe="${recipe}/recipe"
  179. elif test -f "${worktree}/recipes/${recipe}/recipe"
  180. then
  181. recipe="${worktree}/recipes/${recipe}/recipe"
  182. fi
  183. fi
  184. test -f "$recipe" || {
  185. warn "\`${recipe}' is not a regular file."
  186. return 4
  187. }
  188. # Complain if the file name is not "recipe"
  189. if test "${recipe##*/}" = recipe
  190. then
  191. true
  192. else
  193. warn "\`${recipe}' is not a valid recipe name."
  194. return 4
  195. fi
  196. # Start preparations to import the recipe
  197. # Separate the directory name from the file name,
  198. # getting its absolute path and base name
  199. CWD=$(CDPATH= cd -P -- $(dirname -- "$recipe") && printf "$PWD")
  200. recipe=$(basename -- "$recipe")
  201. # Check readability for load the recipe on success
  202. is_readable "${CWD}/$recipe" || exit 4
  203. # Find target architecture if 'arch' is not set
  204. if test -z "$arch"
  205. then
  206. arch=$(${CC:-cc} -dumpmachine 2> /dev/null) || arch=unknown
  207. arch="${arch%%-*}" # Get the rid of target triplet.
  208. fi
  209. # Re-create external directories
  210. mkdir -p -- "${worktree}/archive" \
  211. "${worktree}/patches" \
  212. "${worktree}/recipes" \
  213. "$tardir"
  214. # Variables treatment for the current and next recipe.
  215. #
  216. # Unset special variables that can only be predefined on
  217. # the recipe and not coming from ${sysconfdir}/qirc
  218. unset srcdir destdir pkgname pkgversion program version release \
  219. fetch description homepage license replace full_pkgname \
  220. CFLAGS CXXFLAGS LDFLAGS
  221. # The following variables must be saved and restored
  222. save_arch="${save_arch:=$arch}"
  223. save_jobs="${save_jobs:=$jobs}"
  224. save_outdir="${save_outdir:=$outdir}"
  225. save_opt_nopkg="${save_opt_nopkg:=$opt_nopkg}"
  226. # Reset variable values in case of return
  227. arch=$save_arch
  228. jobs=$save_jobs
  229. outdir=$save_outdir
  230. opt_nopkg=$save_opt_nopkg
  231. # The following variables cannot be redefined on the recipe
  232. readonly worktree netget rsync
  233. # Import the recipe
  234. echo "{@} Building from ${CWD}/$recipe ..."
  235. . "${CWD}/$recipe"
  236. # Check for required variables
  237. if test -z "$program"
  238. then
  239. warn "${recipe}: The variable 'program' is not defined."
  240. exit 5
  241. fi
  242. if test -z "$version"
  243. then
  244. warn "${recipe}: The variable 'version' is not defined."
  245. exit 5
  246. fi
  247. if test -z "$release"
  248. then
  249. warn "${recipe}: The variable 'release' is not defined."
  250. exit 5
  251. fi
  252. # Pre-settings before to start building
  253. # Increment the release number if the option was given
  254. if test "$opt_incr_release" = opt_incr_release
  255. then
  256. release=$(( release + 1 ))
  257. fi
  258. # Allow the dot as definition for 'tardir'
  259. if test "$tardir" = .
  260. then
  261. tardir="$CWD"
  262. fi
  263. # Set default values for the following special variables
  264. pkgname="${pkgname:=$program}"
  265. pkgversion="${pkgversion:=$version}"
  266. srcdir="${srcdir:=${program}-$version}"
  267. destdir="${destdir:=${TMPDIR}/package-$pkgname}"
  268. # Complete package name adding 'pkgversion-arch+release'
  269. full_pkgname="${full_pkgname:=$pkgname-${pkgversion}-${arch}+${release}}"
  270. # If a package is going to be created, the existence of a
  271. # previous build will be detected and reported. Under normal
  272. # conditions the recipe is built as long as it is newer than
  273. # the produced package, if not, we warn to the user about it.
  274. # Rebuilding the package is possible (through the force ;-)
  275. if test "$opt_nopkg" != opt_nopkg && \
  276. { test "$opt_force" != opt_force && \
  277. test -r "${outdir}/${full_pkgname}.tlz" ; }
  278. then
  279. if is_newer "${CWD}/$recipe" "${outdir}/${full_pkgname}.tlz"
  280. then
  281. warn \
  282. "" \
  283. "The recipe is more RECENT than the detected package:" \
  284. "" \
  285. "$( stat -c "%y %n" "${CWD}/$recipe" )" \
  286. "$( stat -c "%y %n" "${outdir}/${full_pkgname}.tlz" )" \
  287. "" \
  288. " This recipe will be processed ..." \
  289. ""
  290. elif test -e "${CWD}/post-install" && \
  291. is_newer "${CWD}/post-install" "${CWD}/$recipe"
  292. then
  293. warn \
  294. "" \
  295. "The post-install script is more RECENT than the recipe:" \
  296. "" \
  297. "$( stat -c "%y %n" "${CWD}/post-install" )" \
  298. "$( stat -c "%y %n" "${CWD}/$recipe" )" \
  299. "" \
  300. " The recipe will be re-processed ..." \
  301. ""
  302. touch "${CWD}/$recipe"
  303. elif test "$opt_ignoreqsts" = opt_ignoreqsts
  304. then
  305. warn "Recipe for '${full_pkgname}.tlz': Ignored." ""
  306. return 0
  307. else
  308. warn \
  309. "" \
  310. "This recipe ALREADY produced a package:" \
  311. "$( stat -c "%y %n" "${outdir}/${full_pkgname}.tlz" )" \
  312. "" \
  313. "The recipe is still OLDER than the produced package:" \
  314. "$( stat -c "%y %n" "${CWD}/$recipe" )" \
  315. "" \
  316. " Probably nothing has changed." \
  317. ""
  318. # In non-interactive mode, the user is asked about
  319. # rebuilding the package. In interactive mode,
  320. # the user need to pass the option explicitly
  321. if test ! -t 0
  322. then
  323. printf "%b" \
  324. "Do you want to rebuild this package?\n" \
  325. "1) Yes, built it\n" \
  326. "2) No, skipt it [default]\n" \
  327. "3) Resume, skipping completed recipes\n" \
  328. "Choose an option number: " > /dev/tty
  329. IFS= read -r ANSWER < /dev/tty || exit 2;
  330. case $ANSWER in
  331. 1*)
  332. echo "$ANSWER" > /dev/tty
  333. unset ANSWER
  334. ;;
  335. 3*)
  336. unset ANSWER
  337. echo "Building UNPROCESSED/MODIFIED recipes ..." > /dev/tty
  338. echo ""
  339. opt_ignoreqsts=opt_ignoreqsts
  340. readonly opt_ignoreqsts
  341. return 0
  342. ;;
  343. *)
  344. unset ANSWER
  345. echo "Recipe for '${full_pkgname}.tlz': Cancelled." > /dev/tty
  346. echo ""
  347. return 0
  348. ;;
  349. esac
  350. else
  351. warn "Use the -f option to re-process ${CWD}/$recipe." ""
  352. return 6;
  353. fi
  354. fi
  355. fi
  356. # Fetch remote sources
  357. echo "Fetching remote sources if needed ..."
  358. if test -n "$fetch"
  359. then
  360. for origin in $fetch
  361. do
  362. _source="${origin##*/}"
  363. echo "Looking for \"$_source\" ..."
  364. echo "Verifying checksum file \"${_source}.sha256\" from '${tardir}'"
  365. if test -e "${tardir}/${_source}.sha256"
  366. then
  367. ( cd -- "$tardir" && sha256sum - ) < "${tardir}/${_source}.sha256"
  368. chkstatus_or_exit
  369. continue;
  370. else
  371. warn "${_source}.sha256: Checksum file does not exist, yet."
  372. fi
  373. # Download source or resume if allowed
  374. if test ! -e "${tardir}/$_source"
  375. then
  376. warn " Can't find $_source in ${tardir};" \
  377. "attempting to get it from ${origin%/*} ..."
  378. fi
  379. case $origin in
  380. rsync://*)
  381. (
  382. cd -- "$tardir" && $rsync $origin || exit $?
  383. sha256sum $_source > ${_source}.sha256
  384. ); chkstatus_or_exit 10
  385. ;;
  386. *://*)
  387. (
  388. cd -- "$tardir" && $netget $origin || exit $?
  389. sha256sum $_source > ${_source}.sha256
  390. ); chkstatus_or_exit 10
  391. ;;
  392. *)
  393. warn "${PROGRAM}: Unrecognized protocol for ${origin}."
  394. exit 4
  395. esac
  396. done
  397. unset origin _source
  398. else
  399. warn "The variable 'fetch' is empty."
  400. fi
  401. # Prepare special directories for build the source,
  402. # the destination and the output of the package
  403. echo "Preparing directories ..."
  404. if test -d "${TMPDIR}/$srcdir" && test -z "$keep_srcdir"
  405. then
  406. rm -rf -- "${TMPDIR}/$srcdir" || chkstatus_or_exit
  407. echo "removed directory: '${TMPDIR}/$srcdir'"
  408. fi
  409. if test -d "$destdir"
  410. then
  411. rm -rf -- "$destdir" || chkstatus_or_exit
  412. echo "removed directory: '$destdir'"
  413. fi
  414. mkdir -p -- "$destdir" || chkstatus_or_exit
  415. echo "mkdir: created directory '$destdir'"
  416. if test ! -d "$outdir"
  417. then
  418. mkdir -p -- "$outdir" || chkstatus_or_exit
  419. echo "mkdir: created directory '$outdir'"
  420. fi
  421. echo "Entering to 'TMPDIR': $TMPDIR ..."
  422. cd -- "$TMPDIR" || chkstatus_or_exit
  423. # Set trap before to run the build() function in order
  424. # to catch the return status, exit code 2 if fails
  425. trap 'chkstatus_or_exit 2' EXIT HUP INT QUIT ABRT TERM
  426. # Determine if the debugging indicators of the shell should be
  427. # retained, assuming that it has been previously passed
  428. case $- in *x*)
  429. _xtrace_flag_is_set=xtrace_flag_is_set ;;
  430. esac
  431. echo "Running build() ..."
  432. build
  433. unset build
  434. # Turn off possible shell flags coming from the recipe
  435. set +e
  436. if test "$_xtrace_flag_is_set" != xtrace_flag_is_set
  437. then
  438. set +x
  439. fi
  440. # Reset given signals
  441. trap - EXIT HUP INT QUIT ABRT TERM
  442. # If 'destdir' is empty, the package won't be created
  443. if rmdir -- "$destdir" 2> /dev/null
  444. then
  445. warn "The package \"${full_pkgname}.tlz\" won't be created. 'destdir' is empty."
  446. opt_nopkg=opt_nopkg
  447. fi
  448. # Create (make) the package
  449. if test "$opt_nopkg" != opt_nopkg
  450. then
  451. # Edit the recipe when 'release' is incremented
  452. if test "$opt_incr_release" = opt_incr_release
  453. then
  454. echo ",s/^\(release\)=.*/\1=${release}/"$'\nw' | \
  455. ed "${CWD}/$recipe" || chkstatus_or_exit
  456. fi
  457. mkdir -p -- "${destdir}/var/lib/qi" || chkstatus_or_exit
  458. # Include a recipe copy into the package
  459. cp -p "${CWD}/$recipe" \
  460. "${destdir}/var/lib/qi/${full_pkgname}.recipe" && \
  461. chmod 644 "${destdir}/var/lib/qi/${full_pkgname}.recipe" || chkstatus_or_exit
  462. # Detect post-install script for inclusion
  463. if test -f "${CWD}/post-install"
  464. then
  465. echo "${CWD}/post-install: Detected."
  466. cp -p "${CWD}/post-install" \
  467. "${destdir}/var/lib/qi/${full_pkgname}.sh" && \
  468. chmod 644 "${destdir}/var/lib/qi/${full_pkgname}.sh" || chkstatus_or_exit
  469. fi
  470. # Detect declared package names for later replacement
  471. if test -n "$replace"
  472. then
  473. warn \
  474. "The following package names has been declared for replacement:" \
  475. " $replace"
  476. rm -f "${destdir}/var/lib/qi/${full_pkgname}.replace"
  477. for item in $replace
  478. do
  479. echo "$replace" >> "${destdir}/var/lib/qi/${full_pkgname}.replace"
  480. done
  481. unset item
  482. fi
  483. # Create meta file for the package information
  484. echo "Creating meta file ${full_pkgname}.tlz.txt ..."
  485. do_meta > "${outdir}/${full_pkgname}.tlz.txt" || chkstatus_or_exit
  486. # Make a copy of it for the database
  487. cp -p "${outdir}/${full_pkgname}.tlz.txt" \
  488. "${destdir}/var/lib/qi/${full_pkgname}.txt" || chkstatus_or_exit
  489. # Produce the package
  490. cd -- "$destdir" && create_mode "${outdir}/${full_pkgname}.tlz"
  491. fi
  492. # Back to the current working directory
  493. cd -- "$CWD" || chkstatus_or_exit
  494. # Delete 'srcdir' or 'destdir' if -k is not given
  495. if test "$opt_keep" != opt_keep
  496. then
  497. echo "Deleting temporary directories ..."
  498. srcdir="${srcdir%%/*}" # Directory name without parents.
  499. if test -r "${TMPDIR}/$srcdir"
  500. then
  501. if test -z "$keep_srcdir"
  502. then
  503. rm -rf -- "${TMPDIR}/$srcdir" || chkstatus_or_exit
  504. echo "removed directory: '${TMPDIR}/${srcdir}'"
  505. else
  506. warn "The variable 'keep_srcdir' is set:" \
  507. "'${TMPDIR}/${srcdir}' will not be deleted."
  508. fi
  509. fi
  510. if test -r "$destdir"
  511. then
  512. rm -rf -- "$destdir" || chkstatus_or_exit
  513. echo "removed directory: '$destdir'"
  514. fi
  515. fi
  516. # Install or update the package if -i or -u was passed
  517. if test "$opt_nopkg" != opt_nopkg
  518. then
  519. if test "$opt_install" = opt_install
  520. then
  521. install_mode "${outdir}/${full_pkgname}.tlz"
  522. elif test "$opt_update" = opt_update
  523. then
  524. upgrade_mode "${outdir}/${full_pkgname}.tlz"
  525. fi
  526. fi
  527. echo ""
  528. echo "All done for ${CWD}/${recipe}."
  529. echo ""
  530. }
  531. create_mode()
  532. {
  533. directory=$(dirname -- "$1")
  534. name=$(basename -- "$1")
  535. # Perform sanity checks
  536. is_readable "$directory" || exit 4
  537. # Check again to find out if it is a valid directory
  538. test -d "$directory" || {
  539. warn "Package directory '$name' does not exist."
  540. exit 4
  541. }
  542. test "$directory" = . && {
  543. warn "Cannot create package on current working directory."
  544. exit 4
  545. }
  546. test "$name" = "${name%.tlz}" && {
  547. warn \
  548. "Package format '$name' not supported." \
  549. "It should be \`name-version-architecture+release.tlz'"
  550. exit 4
  551. }
  552. echo "{#} Creating package $name at $directory ..."
  553. ( umask 022 ; tar cvf - ./* | lzip -9cvv ) > "${directory}/$name"
  554. chkstatus_or_exit 3
  555. echo "Package created \"${directory}/${name}\"."
  556. echo "${directory}/$name: Creating SHA256 checksum ..."
  557. ( cd -- "$directory" && sha256sum $name > ${name}.sha256 )
  558. # Remove used variables
  559. unset directory name
  560. }
  561. delete_mode()
  562. {
  563. expunge="${packagedir}/$(basename -- $1 .tlz)"
  564. echo "<<< Deleting package $rootdir${expunge} ..."
  565. # Complain if the package directory does not exist
  566. test -d "$rootdir${expunge}" || {
  567. warn "Package directory '$rootdir${expunge}' does not exist."
  568. return 4
  569. }
  570. # Complain if the package directory cannot be well-read
  571. is_readable "$rootdir${expunge}" || exit 4
  572. # Remove package from Graft control
  573. # Scan for possible conflicts, stop if arise
  574. if test "$opt_prune" != opt_prune
  575. then
  576. echo "Checking for possible conflicts ..."
  577. if graft -d -n $graft_r -t "$targetdir" "$expunge" 2>&1 | \
  578. grep ^CONFLICT
  579. then
  580. warn "" \
  581. " A conflict occurred during uninstallation;" \
  582. "Unless the -p option is given, this package will be PRESERVED."
  583. return 6;
  584. fi
  585. fi
  586. # Ignore some signals up to completing the deinstallation
  587. trap "" HUP INT QUIT ABRT TERM
  588. # Remove objects (files, links or directories) from the target
  589. # directory that are in conflict with the package directory
  590. echo "Pruning any conflict ..."
  591. graft -p -D -u $graft_r -t "$targetdir" "$expunge"
  592. chkstatus_or_exit 2
  593. echo "Disabling links ..."
  594. graft -d -D -u $graft_v $graft_r -t "$targetdir" "$expunge"
  595. chkstatus_or_exit 2
  596. # Delete package directory
  597. if test "$opt_keep" != opt_keep
  598. then
  599. echo "Deleting package directory, if present ..."
  600. if test -d "${rootdir}$expunge"
  601. then
  602. rm -rf -- "${rootdir}$expunge" || chkstatus_or_exit
  603. echo "removed directory: '${rootdir}$expunge'"
  604. fi
  605. fi
  606. # Reset given signals
  607. trap - HUP INT QUIT ABRT TERM
  608. # Remove used variables
  609. unset expunge
  610. }
  611. install_mode()
  612. {
  613. # Complain if the package cannot be well-read
  614. is_readable "$1" || exit 4
  615. # Complain if the package does not end in .tlz
  616. if ! fnmatch '*.tlz' "$1"
  617. then
  618. warn "\`${1}' does not end in .tlz"
  619. return 4
  620. fi
  621. # Make preparations to install the package
  622. echo ">>> Installing package $1 ..."
  623. # Get the filename
  624. name=$(basename -- "$1" .tlz)
  625. echo "Checking tarball integrity ..."
  626. tar -tf "$1" > /dev/null
  627. chkstatus_or_exit 3
  628. # Create package directory using 'name'
  629. if ! test -d "$rootdir${packagedir}/$name"
  630. then
  631. mkdir -p -- "$rootdir${packagedir}/$name" || chkstatus_or_exit
  632. echo "mkdir: created directory '$rootdir${packagedir}/$name'"
  633. fi
  634. # Scan for possible conflicts, stop if arise
  635. if test "$opt_prune" != opt_prune
  636. then
  637. echo "Checking for possible conflicts ..."
  638. if graft -i -n $graft_r -t "$targetdir" "${packagedir}/$name" 2>&1 | \
  639. grep ^CONFLICT
  640. then
  641. warn "" \
  642. " A conflict occurred during installation;" \
  643. "Unless the -p option is given, this package won't be LINKED."
  644. return 6;
  645. fi
  646. fi
  647. # Ignore some signals up to completing the installation
  648. trap "" HUP INT QUIT ABRT TERM
  649. echo "Decompressing $1 ..."
  650. ( cd -- "$rootdir${packagedir}/$name" && lzip -cd - | tar xpf - ) < "$1"
  651. chkstatus_or_exit 3
  652. # Transite package to Graft control
  653. # Remove objects (files, links or directories) from the target
  654. # directory that are in conflict with the package directory
  655. echo "Pruning any conflict ..."
  656. graft -p -D -u $graft_r -t "$targetdir" "${packagedir}/$name"
  657. chkstatus_or_exit 2
  658. echo "Enabling symbolic links ..."
  659. graft -i -P $graft_v $graft_r -t "$targetdir" "${packagedir}/$name"
  660. chkstatus_or_exit 2
  661. # Avoid unnecessary runs coming from the upgrade_mode(),
  662. # this is when the incoming package is **pre-installed**
  663. if test "$_isUpgrade" != _isUpgrade.on
  664. then
  665. # Show package description
  666. if test -r "$rootdir${packagedir}/${name}/var/lib/qi/${name}.txt"
  667. then
  668. grep '^#' "$rootdir${packagedir}/${name}/var/lib/qi/${name}.txt"
  669. elif test -r "${1}.txt"
  670. then
  671. # From external meta file (current directory)
  672. grep '^#' "${1}.txt"
  673. else
  674. warn "Description file not found for '$name'."
  675. fi
  676. # Check and run the post-install script if exist
  677. if test -r "$rootdir${packagedir}/${name}/var/lib/qi/${name}.sh"
  678. then
  679. echo "Running post-install script for \`${name}' ..."
  680. (
  681. # Rely on 'targetdir' if 'rootdir' is empty
  682. cd -- "${rootdir:-$targetdir}"/ && \
  683. . "$rootdir${packagedir}/${name}/var/lib/qi/${name}.sh"
  684. )
  685. fi
  686. # Check if there are declared packages for replacement
  687. if test -r "$rootdir${packagedir}/${name}/var/lib/qi/${name}.replace"
  688. then
  689. while read -r line
  690. do
  691. for replace in "$rootdir${packagedir}/$(pkgbase $line)"-*
  692. do
  693. if ! test -e "$replace"
  694. then
  695. warn "${replace}: Declared package does not exist. (ignored)"
  696. continue;
  697. fi
  698. replace="${replace##*/}"
  699. # The search for the package to be replaced cannot
  700. # be the same to the incoming package, even to the
  701. # temporary location coming from the upgrade_mode()
  702. if test "$replace" = "$name" || test "$replace" = "${PRVLOC##*/}"
  703. then
  704. continue;
  705. fi
  706. warn "WARNING: Replacing package \`${replace}' ..."
  707. # Since the links belongs to the new package, only
  708. # those which are not in conflict can be deleted.
  709. # To complete, we will remove the package directory
  710. graft -d -D -u $graft_r \
  711. -t "$targetdir" "$replace" > /dev/null 2>&1
  712. rm -rf -- "$rootdir${packagedir}/$replace"
  713. done
  714. done < "$rootdir${packagedir}/${name}/var/lib/qi/${name}.replace"
  715. unset line
  716. fi
  717. fi
  718. # Reset given signals
  719. trap - HUP INT QUIT ABRT TERM
  720. # Remove used variables
  721. unset name
  722. }
  723. resolve_mode() {
  724. # Complain if the file cannot be well-read
  725. is_readable "$1" || exit 4
  726. # Complain if the file does not end in .order
  727. if ! fnmatch '*.order' "$1"
  728. then
  729. warn "\`${1}' does not end in .order"
  730. return 4
  731. fi
  732. # Get a clean list of the file while prints its content in reverse order,
  733. # lines containing: colons, comments, parentheses, end of line, and blank
  734. # lines, are removed. The parentheses are used to insert a reference.
  735. # The last `awk' in the pipe: removes nonconsecutive lines, duplicate.
  736. awk \
  737. '{ gsub( /:|^#(.*)$|\([^)]*)|^$/,"" ); for( i=NF; i > 0; i-- ) print $i }' \
  738. "$1" | awk '!s[$0]++'
  739. }
  740. upgrade_mode()
  741. {
  742. # Complain if the package is not a regular file
  743. test -f "$1" || {
  744. warn "\`${1}' is not a regular file."
  745. return 4
  746. }
  747. # Get the filename
  748. incoming=$(basename -- "$1" .tlz)
  749. echo "<>> Upgrading package $incoming ..."
  750. if test "$opt_force" != opt_force && test -e "${packagedir}/$incoming"
  751. then
  752. warn "" \
  753. " The package to be updated already exist;" \
  754. "Unless the -f option is given, this package won't be UPGRADED."
  755. return 6;
  756. fi
  757. # Check for packages declared in the black list only for installation
  758. for item in $blacklist
  759. do
  760. case $item in
  761. ${incoming}*)
  762. warn \
  763. "*" \
  764. "* The incoming package will be installed instead of being updated." \
  765. "* This may be crucial for the correct functioning of \`${PROGRAM}'." \
  766. "*"
  767. opt_prune=opt_prune install_mode "$1"
  768. return 0;;
  769. esac
  770. done
  771. unset item
  772. # Prepare the package to install it in a temporary location
  773. # Set random directory using packagedir as prefix and 'incoming' as suffix
  774. PRVLOC=$(mktemp -dp "$rootdir${packagedir}" ${incoming}.XXXXXXXXXXXX) || exit 2
  775. # Pre-install the package in the custom 'packagedir'
  776. save_packagedir="$packagedir"
  777. packagedir="$PRVLOC"
  778. echo "Pre-installing package using temporary location ..."
  779. opt_prune=opt_prune # Turn on prune operation.
  780. _isUpgrade=_isUpgrade.on install_mode "$1" > /dev/null
  781. _isUpgrade=_isUpgrade.off
  782. # Restore variable before looking for old packages
  783. packagedir=$save_packagedir
  784. unset save_packagedir
  785. echo "Looking for installations under the same name ..."
  786. for long_name in "$rootdir${packagedir}/$(pkgbase $incoming)"*
  787. do
  788. found="${long_name##*/}"
  789. # The search for the package to be deleted
  790. # cannot be the same to the temporary location
  791. test "$long_name" = "$PRVLOC" && continue;
  792. fnmatch "$(pkgbase $found)*" "$incoming" || continue;
  793. echo "${long_name}: Detected."
  794. # A package directory is preserved if -k is given
  795. delete_mode "$found" > /dev/null
  796. done
  797. unset long_name found
  798. # Re-install the package removing the temporary location
  799. install_mode "$1"
  800. opt_prune=opt_prune.off # Turn off prune operation.
  801. echo "Deleting temporary location ..."
  802. rm -rf -- "$PRVLOC" || chkstatus_or_exit
  803. echo "removed directory: '$PRVLOC'"
  804. echo ""
  805. echo "Successful upgrade to '${incoming}'."
  806. # Remove remaining variables
  807. unset incoming PRVLOC
  808. }
  809. extract_mode()
  810. {
  811. # Perform sanity checks before package extraction
  812. is_readable "$1" || exit 4
  813. test -f "$1" || {
  814. warn "\`${1}' is not a regular file."
  815. return 4
  816. }
  817. # Preparations to extract the package
  818. name=$(basename -- "$1" .tlz)
  819. # Set random directory using 'name' as prefix
  820. PRVDIR="${TMPDIR}/${name}.${RANDOM-0}$$"
  821. # Trap to remove 'PRVDIR' on disruptions
  822. trap "rm -rf -- $PRVDIR" HUP INT ABRT TERM
  823. # Create 'PRVDIR' removing access for all but user
  824. ( umask 077 ; mkdir -- $PRVDIR )
  825. mkdir -p -m 700 -- $PRVDIR
  826. echo "Extracting package $name ..."
  827. ( umask 000 ; cd -- $PRVDIR && lzip -cd - | tar xpvf - ) < "$1"
  828. if test $? -ne 0
  829. then
  830. # Try to remove (empty) 'PRVDIR' on failure
  831. rmdir -- $PRVDIR
  832. exit 3;
  833. fi
  834. echo "$name has been extracted on $PRVDIR"
  835. # Reset given signals
  836. trap - HUP INT ABRT TERM
  837. # Remove used variables
  838. unset name PRVDIR
  839. }
  840. #### Extra functions used in the modes
  841. pkgbase()
  842. {
  843. string=$(basename -- "$1" .tlz)
  844. # Match cases to print the package name.
  845. #
  846. # We will take into account the four segments removing
  847. # the last two to print the package (long) name
  848. case $string in
  849. *-*-*+*)
  850. echo "${string%-*-*}"
  851. ;;
  852. *)
  853. echo "$string"
  854. ;;
  855. esac
  856. unset string
  857. }
  858. unpack()
  859. {
  860. for file in "$@"
  861. do
  862. case $file in
  863. *.tar)
  864. tar tf "$file" > /dev/null && \
  865. tar xpf "$file"
  866. chkstatus_or_exit 3
  867. ;;
  868. *.tar.gz | *.tgz | *.tar.Z )
  869. gzip -cd "$file" | tar tf - > /dev/null && \
  870. gzip -cd "$file" | tar xpf -
  871. chkstatus_or_exit 3
  872. ;;
  873. *.tar.bz2 | *.tbz2 | *.tbz )
  874. bzip2 -cd "$file" | tar tf - > /dev/null && \
  875. bzip2 -cd "$file" | tar xpf -
  876. chkstatus_or_exit 3
  877. ;;
  878. *.tar.lz | *.tlz )
  879. lzip -cd "$file" | tar tf - > /dev/null && \
  880. lzip -cd "$file" | tar xpf -
  881. chkstatus_or_exit 3
  882. ;;
  883. *.tar.xz | *.txz )
  884. xz -cd "$file" | tar tf - > /dev/null && \
  885. xz -cd "$file" | tar xpf -
  886. chkstatus_or_exit 3
  887. ;;
  888. *.zip | *.ZIP )
  889. unzip -t "$file" > /dev/null && \
  890. unzip "$file" > /dev/null
  891. chkstatus_or_exit 3
  892. ;;
  893. *.gz)
  894. gzip -t "$file" && \
  895. gzip -cd "$file" > "$(basename -- $file .gz)"
  896. chkstatus_or_exit 3
  897. ;;
  898. *.Z)
  899. gzip -t "$file" && \
  900. gzip -cd "$file" > "$(basename -- $file .Z)"
  901. chkstatus_or_exit 3
  902. ;;
  903. *.bz2)
  904. bzip2 -t "$file" && \
  905. bzip2 -cd "$file" > "$(basename -- $file .bz2)"
  906. chkstatus_or_exit 3
  907. ;;
  908. *.lz)
  909. lzip -t "$file" && \
  910. lzip -cd "$file" > "$(basename -- $file .lz)"
  911. chkstatus_or_exit 3
  912. ;;
  913. *.xz)
  914. xz -t "$file" && \
  915. xz -cd "$file" > "$(basename -- $file .xz)"
  916. chkstatus_or_exit 3
  917. ;;
  918. *)
  919. warn "${PROGRAM}: cannot unpack ${file}: Unsupported extension"
  920. exit 1
  921. esac
  922. done
  923. unset file
  924. }
  925. do_meta()
  926. {
  927. # Extract information from the recipe to create the meta file.
  928. #
  929. # The package description is pre-formatted in 78 columns,
  930. # the '#' character and a space is added as prefix to conform
  931. # 80 columns in total
  932. cat << EOF
  933. $(echo "$description" | fold -w 78 | awk '$0="# " $0')
  934. QICFLAGS="$QICFLAGS"
  935. QICXXFLAGS="$QICXXFLAGS"
  936. QILDFLAGS="$QILDFLAGS"
  937. program=$program
  938. version=$version
  939. release=$release
  940. blurb="$(echo "$description" | sed -e '/^$/d;2q')"
  941. homepage="$homepage"
  942. license="$license"
  943. fetch="$fetch"
  944. replace="$replace"
  945. EOF
  946. }
  947. #### Default values
  948. PROGRAM="${0##*/}"
  949. packagedir=@PACKAGEDIR@
  950. targetdir=@TARGETDIR@
  951. blacklist="perl graft musl glibc"
  952. RC=RC
  953. RCFILE=@SYSCONFDIR@/qirc
  954. opt_install=opt_install.off
  955. opt_update=opt_update.off
  956. opt_force=opt_force.off
  957. opt_keep=opt_keep.off
  958. opt_incr_release=opt_incr_release.off
  959. opt_ignoreqsts=opt_ignoreqsts.off
  960. opt_nopkg=opt_nopkg.off
  961. opt_prune=opt_prune.off
  962. rootdir=""
  963. jobs=1
  964. mode=""
  965. verbose=0
  966. graft_v=-v
  967. graft_r=""
  968. _isUpgrade=_isUpgrade.off
  969. TMPDIR="${TMPDIR:=/usr/src/qi/build}"
  970. QICFLAGS="${QICFLAGS:=-g0 -Os}"
  971. QICXXFLAGS="${QICXXFLAGS:=$QICFLAGS}"
  972. QILDFLAGS="${QILDFLAGS:=-s}"
  973. worktree=/usr/src/qi
  974. tardir=${worktree}/sources
  975. outdir=/var/cache/qi/packages
  976. netget="wget -c -w1 -t3 --no-check-certificate"
  977. rsync="rsync -v -a -L -z -i --progress"
  978. configure_args="--prefix=@PREFIX@ --libexecdir=@LIBEXECDIR@ --bindir=@BINDIR@ --sbindir=@SBINDIR@ --sysconfdir=@SYSCONFDIR@ --localstatedir=@LOCALSTATEDIR@"
  979. infodir=@INFODIR@
  980. mandir=@MANDIR@
  981. docdir=@DOCDIR@
  982. # Store default locations
  983. QI_TARGETDIR=$targetdir
  984. QI_PACKAGEDIR=$packagedir
  985. QI_WORKTREE=$worktree
  986. QI_TARDIR=$tardir
  987. QI_OUTDIR=$outdir
  988. #### Parse options
  989. while getopts :bcdiouxLNP:t:fkvO:W:Z:a:j:13npr:hV name
  990. do
  991. case $name in
  992. b)
  993. if test -z "$mode"
  994. then
  995. readconfig
  996. mode=build_mode
  997. fi
  998. ;;
  999. c)
  1000. mode=create_mode
  1001. ;;
  1002. d)
  1003. readconfig
  1004. mode=delete_mode
  1005. ;;
  1006. i)
  1007. if test -z "$mode"
  1008. then
  1009. readconfig
  1010. mode=install_mode
  1011. fi
  1012. if test "$mode" = build_mode
  1013. then
  1014. opt_install=opt_install
  1015. fi
  1016. ;;
  1017. o)
  1018. mode=resolve_mode
  1019. ;;
  1020. u)
  1021. if test -z "$mode"
  1022. then
  1023. readconfig
  1024. mode=upgrade_mode
  1025. fi
  1026. if test "$mode" = build_mode
  1027. then
  1028. opt_update=opt_update
  1029. fi
  1030. ;;
  1031. x)
  1032. mode=extract_mode
  1033. ;;
  1034. L)
  1035. printf "%s\n" \
  1036. "QI_TARGETDIR=$QI_TARGETDIR" \
  1037. "QI_PACKAGEDIR=$QI_PACKAGEDIR" \
  1038. "QI_WORKTREE=$QI_WORKTREE" \
  1039. "QI_TARDIR=$QI_TARDIR" \
  1040. "QI_OUTDIR=$QI_OUTDIR"
  1041. exit 0
  1042. ;;
  1043. N)
  1044. RC=RC.off
  1045. ;;
  1046. P)
  1047. packagedir="$OPTARG"
  1048. ;;
  1049. t)
  1050. targetdir="$OPTARG"
  1051. ;;
  1052. f)
  1053. opt_force=opt_force
  1054. ;;
  1055. k)
  1056. opt_keep=opt_keep
  1057. ;;
  1058. v)
  1059. verbose=$(( verbose + 1 ))
  1060. ;;
  1061. O)
  1062. outdir="$OPTARG"
  1063. ;;
  1064. W)
  1065. worktree="$OPTARG"
  1066. ;;
  1067. Z)
  1068. tardir="$OPTARG"
  1069. ;;
  1070. a)
  1071. arch="$OPTARG"
  1072. ;;
  1073. j)
  1074. jobs="$OPTARG"
  1075. ;;
  1076. 1)
  1077. opt_incr_release=opt_incr_release
  1078. ;;
  1079. 3)
  1080. opt_ignoreqsts=opt_ignoreqsts
  1081. ;;
  1082. n)
  1083. opt_nopkg=opt_nopkg
  1084. ;;
  1085. p)
  1086. opt_prune=opt_prune
  1087. ;;
  1088. r)
  1089. rootdir="$OPTARG"
  1090. ;;
  1091. h)
  1092. usage
  1093. exit 0
  1094. ;;
  1095. V)
  1096. version
  1097. exit 0
  1098. ;;
  1099. :)
  1100. warn "Option '-${OPTARG}' requires an argument"
  1101. usage
  1102. exit 1
  1103. ;;
  1104. \?)
  1105. warn "Illegal option -- '-${OPTARG}'"
  1106. usage
  1107. exit 1
  1108. ;;
  1109. esac
  1110. done
  1111. shift $(( OPTIND - 1 ))
  1112. if test $# -eq 0
  1113. then
  1114. usage
  1115. exit 1
  1116. fi
  1117. # Program sanity check
  1118. for need in awk basename bzip2 chmod cp dirname find fold graft grep \
  1119. lzip mkdir mktemp rm rmdir sed sha256sum stat tar
  1120. do
  1121. if ! type $need 1> /dev/null 2> /dev/null
  1122. then
  1123. warn "${PROGRAM}: cannot operate without ${need}(1): Check your PATH"
  1124. exit 2
  1125. fi
  1126. done
  1127. unset need
  1128. # Determine verbosity level/flag
  1129. if test "$verbose" -gt 1
  1130. then
  1131. graft_v=-V
  1132. fi
  1133. # Read standard input if FILE is -, or when FILE
  1134. # is not connected to a terminal.
  1135. if test "$1" = - || test ! -t 0
  1136. then
  1137. # Unset positional parameters setting $# to zero
  1138. set --
  1139. # Assign remaining arguments to the positional parameters
  1140. while read -r input
  1141. do
  1142. set -- "$@" "$input"
  1143. done
  1144. fi
  1145. # We need at least one operating mode
  1146. if test -z "$mode"
  1147. then
  1148. usage
  1149. exit 4
  1150. fi
  1151. umask 022; # Remove write permission for group and other.
  1152. # Validate 'rootdir' directory
  1153. if test -n "$rootdir"
  1154. then
  1155. if test -d "$rootdir" && test "$rootdir" != /
  1156. then
  1157. # Remove slash from the end
  1158. rootdir="${rootdir%/}"
  1159. # A workaround for graft-2.13+. The specified directory is
  1160. # relative to the log file, we prepend it inside the rootdir
  1161. eval "$(graft -L)" ; GRAFT_LOGFILE="${GRAFT_LOGFILE:=/var/log/graft}"
  1162. mkdir -p -- "$rootdir$(dirname -- $GRAFT_LOGFILE)" || chkstatus_or_exit
  1163. # Compose 'rootdir' and log file option to be used with graft(1)
  1164. graft_r="-r $rootdir -l $GRAFT_LOGFILE"
  1165. # Unset variables coming from eval
  1166. unset GRAFT_PERL GRAFT_LOGFILE GRAFT_TARGETDIR GRAFT_PACKAGEDIR
  1167. else
  1168. warn "${PROGRAM}: \`${rootdir}' is not a qualified root directory"
  1169. exit 4
  1170. fi
  1171. export rootdir
  1172. fi
  1173. # Ensure 'TMPDIR' creation to prefix temporary files
  1174. if test ! -d "$TMPDIR"
  1175. then
  1176. mkdir -p -- "$TMPDIR" || chkstatus_or_exit
  1177. fi
  1178. readonly TMPDIR
  1179. # Process each package or recipe provided on the command-line
  1180. for package in "$@"
  1181. do
  1182. $mode $package
  1183. done