summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore34
-rw-r--r--AUTHORS10
-rw-r--r--COPYING9
-rw-r--r--COPYING-GPLv2339
-rw-r--r--COPYING-GPLv3674
-rw-r--r--HACKING/build-system.md24
-rw-r--r--HACKING/code-quality.md135
-rw-r--r--HACKING/code-style.md62
-rw-r--r--HACKING/contributing.md26
-rw-r--r--HACKING/licensing.md19
-rw-r--r--HACKING/testing.md18
-rw-r--r--INSTALL120
-rw-r--r--INSTALL-VCS17
-rw-r--r--Makefile39
-rw-r--r--build-aux/Makefile.README.txt164
-rw-r--r--build-aux/Makefile.each.head/.gitignore0
-rw-r--r--build-aux/Makefile.each.head/00-dist.mk20
-rw-r--r--build-aux/Makefile.each.tail/.gitignore0
-rw-r--r--build-aux/Makefile.head.mk63
-rw-r--r--build-aux/Makefile.once.head/.gitignore0
-rw-r--r--build-aux/Makefile.once.head/00-dist.mk44
-rw-r--r--build-aux/Makefile.once.tail/.gitignore0
-rw-r--r--build-aux/Makefile.once.tail/00-dist.mk27
-rw-r--r--build-aux/Makefile.tail.mk51
-rw-r--r--common.each.head.mk35
-rw-r--r--common.each.tail.mk89
-rw-r--r--common.once.head.mk83
-rw-r--r--common.once.tail.mk0
-rw-r--r--config.mk30
-rw-r--r--po/.gitignore1
-rw-r--r--src/Makefile7
-rw-r--r--src/abslibre-tools/Makefile4
-rwxr-xr-xsrc/abslibre-tools/createworkdir54
-rw-r--r--src/abslibre-tools/createworkdir.md36
-rwxr-xr-xsrc/abslibre-tools/diff-unfree89
-rwxr-xr-xsrc/abslibre-tools/libreaddiff98
-rwxr-xr-xsrc/abslibre-tools/libredbdiff360
-rwxr-xr-xsrc/abslibre-tools/librerelease257
-rwxr-xr-xsrc/abslibre-tools/librestage154
-rwxr-xr-xsrc/aur146
-rw-r--r--src/chroot-tools/.gitignore7
-rw-r--r--src/chroot-tools/HACKING.md3
-rw-r--r--src/chroot-tools/Makefile27
-rw-r--r--src/chroot-tools/arch-nspawn.patch71
-rw-r--r--src/chroot-tools/chcleanup.in102
-rw-r--r--src/chroot-tools/chroot.conf13
-rwxr-xr-xsrc/chroot-tools/distcc-tool247
-rw-r--r--src/chroot-tools/hooks-chcleanup.sh32
-rw-r--r--src/chroot-tools/hooks-check.sh37
-rw-r--r--src/chroot-tools/hooks-distcc.sh106
-rwxr-xr-xsrc/chroot-tools/indent57
-rwxr-xr-xsrc/chroot-tools/librechroot509
-rwxr-xr-xsrc/chroot-tools/libremakepkg289
-rw-r--r--src/chroot-tools/makechrootpkg.sh.patch342
-rw-r--r--src/chroot-tools/mkarchroot.patch67
-rwxr-xr-xsrc/dagpkg222
-rw-r--r--src/devtools/.gitignore4
-rw-r--r--src/devtools/Makefile12
-rw-r--r--src/devtools/checkpkg.patch45
-rw-r--r--src/devtools/find-libdeps.patch47
-rw-r--r--src/devtools/finddeps.patch35
-rw-r--r--src/devtools/lddd.patch29
-rw-r--r--src/gitget/Makefile4
-rwxr-xr-xsrc/gitget/gitget221
-rwxr-xr-xsrc/gitget/libregit43
-rwxr-xr-xsrc/is_built69
-rw-r--r--src/lib/.gitignore3
-rw-r--r--src/lib/HACKING.md15
-rw-r--r--src/lib/Makefile45
-rw-r--r--src/lib/blacklist.sh96
l---------src/lib/blacklist.sh.3.ronn1
-rw-r--r--src/lib/common.sh.3.ronn16
-rw-r--r--src/lib/common.sh.head25
-rw-r--r--src/lib/common.sh.tail1
-rw-r--r--src/lib/conf.sh.3.ronn126
-rw-r--r--src/lib/conf.sh.in243
-rwxr-xr-xsrc/lib/libreblacklist83
-rw-r--r--src/lib/libreblacklist.1.ronn23
-rwxr-xr-xsrc/lib/librelib98
-rw-r--r--src/lib/librelib.1.ronn55
-rw-r--r--src/lib/librelib.7.ronn49
-rwxr-xr-xsrc/lib/libremessages25
-rw-r--r--src/lib/libremessages.1.ronn241
-rwxr-xr-xsrc/lib/librexgettext226
-rw-r--r--src/lib/librexgettext.1.ronn18
-rw-r--r--src/lib/messages.sh212
l---------src/lib/messages.sh.3.ronn1
-rw-r--r--src/librefetch/.gitignore2
-rw-r--r--src/librefetch/Makefile13
-rwxr-xr-xsrc/librefetch/librefetch400
-rw-r--r--src/librefetch/librefetch-install.in114
-rw-r--r--src/librefetch/librefetch-makepkg.conf.in16
-rw-r--r--src/librefetch/librefetch.8.ronn214
-rw-r--r--src/librefetch/librefetch.conf7
-rw-r--r--src/librefetch/librefetch.conf.5.ronn42
-rw-r--r--src/librefetch/librefetchdir/Makefile56
-rwxr-xr-xsrc/librefetch/librefetchdir/libmakepkg/source.sh.gen25
-rwxr-xr-xsrc/librefetch/librefetchdir/libmakepkg/tidy/purge.sh58
-rwxr-xr-xsrc/librefetch/librefetchdir/libmakepkg/tidy/~source_date_epoch.sh38
-rwxr-xr-xsrc/librefetch/librefetchdir/makepkg.gen42
-rw-r--r--src/libretools.conf72
-rwxr-xr-xsrc/pkgbuild-check-nonfree309
-rwxr-xr-xsrc/pkgbuild-summarize-nonfree106
-rwxr-xr-xsrc/repo-diff75
-rw-r--r--src/toru/Makefile4
-rwxr-xr-xsrc/toru/toru-info43
-rwxr-xr-xsrc/toru/toru-path92
-rwxr-xr-xsrc/toru/toru-where25
-rw-r--r--src/workflows.md64
-rw-r--r--src/xbs-abs/.gitignore3
-rw-r--r--src/xbs-abs/Makefile27
-rw-r--r--src/xbs-abs/archrelease.patch8
-rwxr-xr-xsrc/xbs-abs/helper-abs201
-rw-r--r--src/xbs-abs/xbs-abs.conf13
-rw-r--r--src/xbs-abslibre/Makefile8
-rwxr-xr-xsrc/xbs-abslibre/helper-abslibre202
-rw-r--r--src/xbs/Makefile5
-rwxr-xr-xsrc/xbs/xbs186
-rw-r--r--src/xbs/xbs.conf1
-rw-r--r--test/.gitignore1
-rw-r--r--test/aur-test.sh30
-rw-r--r--test/is_built-test.sh60
-rw-r--r--test/lib-blacklist-test.sh134
-rw-r--r--test/lib-conf-test.sh43
-rw-r--r--test/lib-messages-test.sh64
-rw-r--r--test/librechroot-test.sh129
-rw-r--r--test/librefetch-test.sh99
-rw-r--r--test/librefetch.d/PKGBUILD18
-rw-r--r--test/librefetch.d/PKGBUILD-recurse18
-rw-r--r--test/librefetch.d/list.txt5
-rw-r--r--test/librelib-test.sh75
-rw-r--r--test/libremakepkg-test.sh134
-rw-r--r--test/libremakepkg.d/PKGBUILD-hello19
-rw-r--r--test/libremakepkg.d/PKGBUILD-netbuild17
-rw-r--r--test/libremakepkg.d/PKGBUILD-netpackage12
-rw-r--r--test/libremakepkg.d/PKGBUILD-netprepare17
-rw-r--r--test/libremakepkg.d/PKGBUILD-testpkg119
-rw-r--r--test/libremakepkg.d/PKGBUILD-testpkg219
-rw-r--r--test/librerelease-test.sh56
-rw-r--r--test/librestage-test.sh72
-rw-r--r--test/librestage.d/PKGBUILD-hello19
-rw-r--r--test/pkgbuild-check-nonfree-test.sh78
-rw-r--r--test/pkgbuild-check-nonfree.d/PKGBUILD.free18
-rw-r--r--test/pkgbuild-check-nonfree.d/PKGBUILD.nonfree18
-rw-r--r--test/pkgbuild-check-nonfree.d/PKGBUILD.nonfree-replacement18
-rw-r--r--test/test-common.sh78
-rwxr-xr-xtest/testenv69
-rwxr-xr-xwrite-ifchanged11
148 files changed, 11401 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..49ed84d
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,34 @@
+*~
+#*
+
+/.var.*
+.tmp*
+
+# Files generated for dist from git
+.srcfiles.mk
+/.srcversion-libretools.mk
+/.srcversion-devtools.mk
+
+# The top-level distdir
+/*-*/
+
+# File extensions
+*.ugly
+*.tar.gz
+*.pot
+*.1
+*.2
+*.3
+*.4
+*.5
+*.6
+*.7
+*.8
+*.1.html
+*.2.html
+*.3.html
+*.4.html
+*.5.html
+*.6.html
+*.7.html
+*.8.html
diff --git a/AUTHORS b/AUTHORS
new file mode 100644
index 0000000..b577056
--- /dev/null
+++ b/AUTHORS
@@ -0,0 +1,10 @@
+Daniel Molina (lluvia)
+Esteban Carnevale <alfplayer@mailoo.org>
+Joseph Graham (Xylon) <joe@t67.eu>
+Joshua Ismael Haase Hernández (xihh) <hahj87@gmail.com>
+Luke Shumaker <lukeshu@sbcglobal.net>
+Michał Masłowski <mtjm@mtjm.eu>
+Nicolás Reynolds <fauno@parabola.nu>
+
+As well as authors of code taken from Arch Linux's "devtools" and
+"dbscripts", and Pacman's "makepkg".
diff --git a/COPYING b/COPYING
new file mode 100644
index 0000000..0cca3f1
--- /dev/null
+++ b/COPYING
@@ -0,0 +1,9 @@
+Different parts of libretools are either the GNU General Public
+License version 2 or 3; sometimes with, sometimes without the option
+to use a later version of the license. Which one is specified on a
+per-file basis; look at the source. In addition to a full copyright
+and license statement, each file contains a "License:" line to make
+searching easy.
+
+Full copies of the GNU General Public License versions 2 and 3 are
+included as `COPYING-GPLv2` and `COPYING-GPLv3`, respectively.
diff --git a/COPYING-GPLv2 b/COPYING-GPLv2
new file mode 100644
index 0000000..d159169
--- /dev/null
+++ b/COPYING-GPLv2
@@ -0,0 +1,339 @@
+ GNU GENERAL PUBLIC LICENSE
+ Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The licenses for most software are designed to take away your
+freedom to share and change it. By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users. This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it. (Some other Free Software Foundation software is covered by
+the GNU Lesser General Public License instead.) You can apply it to
+your programs, too.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+ To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have. You must make sure that they, too, receive or can get the
+source code. And you must show them these terms so they know their
+rights.
+
+ We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+ Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software. If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+ Finally, any free program is threatened constantly by software
+patents. We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary. To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ GNU GENERAL PUBLIC LICENSE
+ TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+ 0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License. The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language. (Hereinafter, translation is included without limitation in
+the term "modification".) Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope. The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+ 1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+ 2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+ a) You must cause the modified files to carry prominent notices
+ stating that you changed the files and the date of any change.
+
+ b) You must cause any work that you distribute or publish, that in
+ whole or in part contains or is derived from the Program or any
+ part thereof, to be licensed as a whole at no charge to all third
+ parties under the terms of this License.
+
+ c) If the modified program normally reads commands interactively
+ when run, you must cause it, when started running for such
+ interactive use in the most ordinary way, to print or display an
+ announcement including an appropriate copyright notice and a
+ notice that there is no warranty (or else, saying that you provide
+ a warranty) and that users may redistribute the program under
+ these conditions, and telling the user how to view a copy of this
+ License. (Exception: if the Program itself is interactive but
+ does not normally print such an announcement, your work based on
+ the Program is not required to print an announcement.)
+
+These requirements apply to the modified work as a whole. If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works. But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+ 3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+ a) Accompany it with the complete corresponding machine-readable
+ source code, which must be distributed under the terms of Sections
+ 1 and 2 above on a medium customarily used for software interchange; or,
+
+ b) Accompany it with a written offer, valid for at least three
+ years, to give any third party, for a charge no more than your
+ cost of physically performing source distribution, a complete
+ machine-readable copy of the corresponding source code, to be
+ distributed under the terms of Sections 1 and 2 above on a medium
+ customarily used for software interchange; or,
+
+ c) Accompany it with the information you received as to the offer
+ to distribute corresponding source code. (This alternative is
+ allowed only for noncommercial distribution and only if you
+ received the program in object code or executable form with such
+ an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it. For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable. However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+ 4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License. Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+ 5. You are not required to accept this License, since you have not
+signed it. However, nothing else grants you permission to modify or
+distribute the Program or its derivative works. These actions are
+prohibited by law if you do not accept this License. Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+ 6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions. You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+ 7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all. For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices. Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+ 8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded. In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+ 9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number. If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation. If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+ 10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission. For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this. Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+ NO WARRANTY
+
+ 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+ 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+ <one line to give the program's name and a brief idea of what it does.>
+ Copyright (C) <year> <name of author>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation; either version 2 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along
+ with this program; if not, write to the Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+ Gnomovision version 69, Copyright (C) year name of author
+ Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+ This is free software, and you are welcome to redistribute it
+ under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License. Of course, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary. Here is a sample; alter the names:
+
+ Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+ `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+ <signature of Ty Coon>, 1 April 1989
+ Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program into
+proprietary programs. If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library. If this is what you want to do, use the GNU Lesser General
+Public License instead of this License.
diff --git a/COPYING-GPLv3 b/COPYING-GPLv3
new file mode 100644
index 0000000..94a9ed0
--- /dev/null
+++ b/COPYING-GPLv3
@@ -0,0 +1,674 @@
+ GNU GENERAL PUBLIC LICENSE
+ Version 3, 29 June 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The GNU General Public License is a free, copyleft license for
+software and other kinds of works.
+
+ The licenses for most software and other practical works are designed
+to take away your freedom to share and change the works. By contrast,
+the GNU General Public License is intended to guarantee your freedom to
+share and change all versions of a program--to make sure it remains free
+software for all its users. We, the Free Software Foundation, use the
+GNU General Public License for most of our software; it applies also to
+any other work released this way by its authors. You can apply it to
+your programs, too.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+them if you wish), that you receive source code or can get it if you
+want it, that you can change the software or use pieces of it in new
+free programs, and that you know you can do these things.
+
+ To protect your rights, we need to prevent others from denying you
+these rights or asking you to surrender the rights. Therefore, you have
+certain responsibilities if you distribute copies of the software, or if
+you modify it: responsibilities to respect the freedom of others.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must pass on to the recipients the same
+freedoms that you received. You must make sure that they, too, receive
+or can get the source code. And you must show them these terms so they
+know their rights.
+
+ Developers that use the GNU GPL protect your rights with two steps:
+(1) assert copyright on the software, and (2) offer you this License
+giving you legal permission to copy, distribute and/or modify it.
+
+ For the developers' and authors' protection, the GPL clearly explains
+that there is no warranty for this free software. For both users' and
+authors' sake, the GPL requires that modified versions be marked as
+changed, so that their problems will not be attributed erroneously to
+authors of previous versions.
+
+ Some devices are designed to deny users access to install or run
+modified versions of the software inside them, although the manufacturer
+can do so. This is fundamentally incompatible with the aim of
+protecting users' freedom to change the software. The systematic
+pattern of such abuse occurs in the area of products for individuals to
+use, which is precisely where it is most unacceptable. Therefore, we
+have designed this version of the GPL to prohibit the practice for those
+products. If such problems arise substantially in other domains, we
+stand ready to extend this provision to those domains in future versions
+of the GPL, as needed to protect the freedom of users.
+
+ Finally, every program is threatened constantly by software patents.
+States should not allow patents to restrict development and use of
+software on general-purpose computers, but in those that do, we wish to
+avoid the special danger that patents applied to a free program could
+make it effectively proprietary. To prevent this, the GPL assures that
+patents cannot be used to render the program non-free.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ TERMS AND CONDITIONS
+
+ 0. Definitions.
+
+ "This License" refers to version 3 of the GNU General Public License.
+
+ "Copyright" also means copyright-like laws that apply to other kinds of
+works, such as semiconductor masks.
+
+ "The Program" refers to any copyrightable work licensed under this
+License. Each licensee is addressed as "you". "Licensees" and
+"recipients" may be individuals or organizations.
+
+ To "modify" a work means to copy from or adapt all or part of the work
+in a fashion requiring copyright permission, other than the making of an
+exact copy. The resulting work is called a "modified version" of the
+earlier work or a work "based on" the earlier work.
+
+ A "covered work" means either the unmodified Program or a work based
+on the Program.
+
+ To "propagate" a work means to do anything with it that, without
+permission, would make you directly or secondarily liable for
+infringement under applicable copyright law, except executing it on a
+computer or modifying a private copy. Propagation includes copying,
+distribution (with or without modification), making available to the
+public, and in some countries other activities as well.
+
+ To "convey" a work means any kind of propagation that enables other
+parties to make or receive copies. Mere interaction with a user through
+a computer network, with no transfer of a copy, is not conveying.
+
+ An interactive user interface displays "Appropriate Legal Notices"
+to the extent that it includes a convenient and prominently visible
+feature that (1) displays an appropriate copyright notice, and (2)
+tells the user that there is no warranty for the work (except to the
+extent that warranties are provided), that licensees may convey the
+work under this License, and how to view a copy of this License. If
+the interface presents a list of user commands or options, such as a
+menu, a prominent item in the list meets this criterion.
+
+ 1. Source Code.
+
+ The "source code" for a work means the preferred form of the work
+for making modifications to it. "Object code" means any non-source
+form of a work.
+
+ A "Standard Interface" means an interface that either is an official
+standard defined by a recognized standards body, or, in the case of
+interfaces specified for a particular programming language, one that
+is widely used among developers working in that language.
+
+ The "System Libraries" of an executable work include anything, other
+than the work as a whole, that (a) is included in the normal form of
+packaging a Major Component, but which is not part of that Major
+Component, and (b) serves only to enable use of the work with that
+Major Component, or to implement a Standard Interface for which an
+implementation is available to the public in source code form. A
+"Major Component", in this context, means a major essential component
+(kernel, window system, and so on) of the specific operating system
+(if any) on which the executable work runs, or a compiler used to
+produce the work, or an object code interpreter used to run it.
+
+ The "Corresponding Source" for a work in object code form means all
+the source code needed to generate, install, and (for an executable
+work) run the object code and to modify the work, including scripts to
+control those activities. However, it does not include the work's
+System Libraries, or general-purpose tools or generally available free
+programs which are used unmodified in performing those activities but
+which are not part of the work. For example, Corresponding Source
+includes interface definition files associated with source files for
+the work, and the source code for shared libraries and dynamically
+linked subprograms that the work is specifically designed to require,
+such as by intimate data communication or control flow between those
+subprograms and other parts of the work.
+
+ The Corresponding Source need not include anything that users
+can regenerate automatically from other parts of the Corresponding
+Source.
+
+ The Corresponding Source for a work in source code form is that
+same work.
+
+ 2. Basic Permissions.
+
+ All rights granted under this License are granted for the term of
+copyright on the Program, and are irrevocable provided the stated
+conditions are met. This License explicitly affirms your unlimited
+permission to run the unmodified Program. The output from running a
+covered work is covered by this License only if the output, given its
+content, constitutes a covered work. This License acknowledges your
+rights of fair use or other equivalent, as provided by copyright law.
+
+ You may make, run and propagate covered works that you do not
+convey, without conditions so long as your license otherwise remains
+in force. You may convey covered works to others for the sole purpose
+of having them make modifications exclusively for you, or provide you
+with facilities for running those works, provided that you comply with
+the terms of this License in conveying all material for which you do
+not control copyright. Those thus making or running the covered works
+for you must do so exclusively on your behalf, under your direction
+and control, on terms that prohibit them from making any copies of
+your copyrighted material outside their relationship with you.
+
+ Conveying under any other circumstances is permitted solely under
+the conditions stated below. Sublicensing is not allowed; section 10
+makes it unnecessary.
+
+ 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
+
+ No covered work shall be deemed part of an effective technological
+measure under any applicable law fulfilling obligations under article
+11 of the WIPO copyright treaty adopted on 20 December 1996, or
+similar laws prohibiting or restricting circumvention of such
+measures.
+
+ When you convey a covered work, you waive any legal power to forbid
+circumvention of technological measures to the extent such circumvention
+is effected by exercising rights under this License with respect to
+the covered work, and you disclaim any intention to limit operation or
+modification of the work as a means of enforcing, against the work's
+users, your or third parties' legal rights to forbid circumvention of
+technological measures.
+
+ 4. Conveying Verbatim Copies.
+
+ You may convey verbatim copies of the Program's source code as you
+receive it, in any medium, provided that you conspicuously and
+appropriately publish on each copy an appropriate copyright notice;
+keep intact all notices stating that this License and any
+non-permissive terms added in accord with section 7 apply to the code;
+keep intact all notices of the absence of any warranty; and give all
+recipients a copy of this License along with the Program.
+
+ You may charge any price or no price for each copy that you convey,
+and you may offer support or warranty protection for a fee.
+
+ 5. Conveying Modified Source Versions.
+
+ You may convey a work based on the Program, or the modifications to
+produce it from the Program, in the form of source code under the
+terms of section 4, provided that you also meet all of these conditions:
+
+ a) The work must carry prominent notices stating that you modified
+ it, and giving a relevant date.
+
+ b) The work must carry prominent notices stating that it is
+ released under this License and any conditions added under section
+ 7. This requirement modifies the requirement in section 4 to
+ "keep intact all notices".
+
+ c) You must license the entire work, as a whole, under this
+ License to anyone who comes into possession of a copy. This
+ License will therefore apply, along with any applicable section 7
+ additional terms, to the whole of the work, and all its parts,
+ regardless of how they are packaged. This License gives no
+ permission to license the work in any other way, but it does not
+ invalidate such permission if you have separately received it.
+
+ d) If the work has interactive user interfaces, each must display
+ Appropriate Legal Notices; however, if the Program has interactive
+ interfaces that do not display Appropriate Legal Notices, your
+ work need not make them do so.
+
+ A compilation of a covered work with other separate and independent
+works, which are not by their nature extensions of the covered work,
+and which are not combined with it such as to form a larger program,
+in or on a volume of a storage or distribution medium, is called an
+"aggregate" if the compilation and its resulting copyright are not
+used to limit the access or legal rights of the compilation's users
+beyond what the individual works permit. Inclusion of a covered work
+in an aggregate does not cause this License to apply to the other
+parts of the aggregate.
+
+ 6. Conveying Non-Source Forms.
+
+ You may convey a covered work in object code form under the terms
+of sections 4 and 5, provided that you also convey the
+machine-readable Corresponding Source under the terms of this License,
+in one of these ways:
+
+ a) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by the
+ Corresponding Source fixed on a durable physical medium
+ customarily used for software interchange.
+
+ b) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by a
+ written offer, valid for at least three years and valid for as
+ long as you offer spare parts or customer support for that product
+ model, to give anyone who possesses the object code either (1) a
+ copy of the Corresponding Source for all the software in the
+ product that is covered by this License, on a durable physical
+ medium customarily used for software interchange, for a price no
+ more than your reasonable cost of physically performing this
+ conveying of source, or (2) access to copy the
+ Corresponding Source from a network server at no charge.
+
+ c) Convey individual copies of the object code with a copy of the
+ written offer to provide the Corresponding Source. This
+ alternative is allowed only occasionally and noncommercially, and
+ only if you received the object code with such an offer, in accord
+ with subsection 6b.
+
+ d) Convey the object code by offering access from a designated
+ place (gratis or for a charge), and offer equivalent access to the
+ Corresponding Source in the same way through the same place at no
+ further charge. You need not require recipients to copy the
+ Corresponding Source along with the object code. If the place to
+ copy the object code is a network server, the Corresponding Source
+ may be on a different server (operated by you or a third party)
+ that supports equivalent copying facilities, provided you maintain
+ clear directions next to the object code saying where to find the
+ Corresponding Source. Regardless of what server hosts the
+ Corresponding Source, you remain obligated to ensure that it is
+ available for as long as needed to satisfy these requirements.
+
+ e) Convey the object code using peer-to-peer transmission, provided
+ you inform other peers where the object code and Corresponding
+ Source of the work are being offered to the general public at no
+ charge under subsection 6d.
+
+ A separable portion of the object code, whose source code is excluded
+from the Corresponding Source as a System Library, need not be
+included in conveying the object code work.
+
+ A "User Product" is either (1) a "consumer product", which means any
+tangible personal property which is normally used for personal, family,
+or household purposes, or (2) anything designed or sold for incorporation
+into a dwelling. In determining whether a product is a consumer product,
+doubtful cases shall be resolved in favor of coverage. For a particular
+product received by a particular user, "normally used" refers to a
+typical or common use of that class of product, regardless of the status
+of the particular user or of the way in which the particular user
+actually uses, or expects or is expected to use, the product. A product
+is a consumer product regardless of whether the product has substantial
+commercial, industrial or non-consumer uses, unless such uses represent
+the only significant mode of use of the product.
+
+ "Installation Information" for a User Product means any methods,
+procedures, authorization keys, or other information required to install
+and execute modified versions of a covered work in that User Product from
+a modified version of its Corresponding Source. The information must
+suffice to ensure that the continued functioning of the modified object
+code is in no case prevented or interfered with solely because
+modification has been made.
+
+ If you convey an object code work under this section in, or with, or
+specifically for use in, a User Product, and the conveying occurs as
+part of a transaction in which the right of possession and use of the
+User Product is transferred to the recipient in perpetuity or for a
+fixed term (regardless of how the transaction is characterized), the
+Corresponding Source conveyed under this section must be accompanied
+by the Installation Information. But this requirement does not apply
+if neither you nor any third party retains the ability to install
+modified object code on the User Product (for example, the work has
+been installed in ROM).
+
+ The requirement to provide Installation Information does not include a
+requirement to continue to provide support service, warranty, or updates
+for a work that has been modified or installed by the recipient, or for
+the User Product in which it has been modified or installed. Access to a
+network may be denied when the modification itself materially and
+adversely affects the operation of the network or violates the rules and
+protocols for communication across the network.
+
+ Corresponding Source conveyed, and Installation Information provided,
+in accord with this section must be in a format that is publicly
+documented (and with an implementation available to the public in
+source code form), and must require no special password or key for
+unpacking, reading or copying.
+
+ 7. Additional Terms.
+
+ "Additional permissions" are terms that supplement the terms of this
+License by making exceptions from one or more of its conditions.
+Additional permissions that are applicable to the entire Program shall
+be treated as though they were included in this License, to the extent
+that they are valid under applicable law. If additional permissions
+apply only to part of the Program, that part may be used separately
+under those permissions, but the entire Program remains governed by
+this License without regard to the additional permissions.
+
+ When you convey a copy of a covered work, you may at your option
+remove any additional permissions from that copy, or from any part of
+it. (Additional permissions may be written to require their own
+removal in certain cases when you modify the work.) You may place
+additional permissions on material, added by you to a covered work,
+for which you have or can give appropriate copyright permission.
+
+ Notwithstanding any other provision of this License, for material you
+add to a covered work, you may (if authorized by the copyright holders of
+that material) supplement the terms of this License with terms:
+
+ a) Disclaiming warranty or limiting liability differently from the
+ terms of sections 15 and 16 of this License; or
+
+ b) Requiring preservation of specified reasonable legal notices or
+ author attributions in that material or in the Appropriate Legal
+ Notices displayed by works containing it; or
+
+ c) Prohibiting misrepresentation of the origin of that material, or
+ requiring that modified versions of such material be marked in
+ reasonable ways as different from the original version; or
+
+ d) Limiting the use for publicity purposes of names of licensors or
+ authors of the material; or
+
+ e) Declining to grant rights under trademark law for use of some
+ trade names, trademarks, or service marks; or
+
+ f) Requiring indemnification of licensors and authors of that
+ material by anyone who conveys the material (or modified versions of
+ it) with contractual assumptions of liability to the recipient, for
+ any liability that these contractual assumptions directly impose on
+ those licensors and authors.
+
+ All other non-permissive additional terms are considered "further
+restrictions" within the meaning of section 10. If the Program as you
+received it, or any part of it, contains a notice stating that it is
+governed by this License along with a term that is a further
+restriction, you may remove that term. If a license document contains
+a further restriction but permits relicensing or conveying under this
+License, you may add to a covered work material governed by the terms
+of that license document, provided that the further restriction does
+not survive such relicensing or conveying.
+
+ If you add terms to a covered work in accord with this section, you
+must place, in the relevant source files, a statement of the
+additional terms that apply to those files, or a notice indicating
+where to find the applicable terms.
+
+ Additional terms, permissive or non-permissive, may be stated in the
+form of a separately written license, or stated as exceptions;
+the above requirements apply either way.
+
+ 8. Termination.
+
+ You may not propagate or modify a covered work except as expressly
+provided under this License. Any attempt otherwise to propagate or
+modify it is void, and will automatically terminate your rights under
+this License (including any patent licenses granted under the third
+paragraph of section 11).
+
+ However, if you cease all violation of this License, then your
+license from a particular copyright holder is reinstated (a)
+provisionally, unless and until the copyright holder explicitly and
+finally terminates your license, and (b) permanently, if the copyright
+holder fails to notify you of the violation by some reasonable means
+prior to 60 days after the cessation.
+
+ Moreover, your license from a particular copyright holder is
+reinstated permanently if the copyright holder notifies you of the
+violation by some reasonable means, this is the first time you have
+received notice of violation of this License (for any work) from that
+copyright holder, and you cure the violation prior to 30 days after
+your receipt of the notice.
+
+ Termination of your rights under this section does not terminate the
+licenses of parties who have received copies or rights from you under
+this License. If your rights have been terminated and not permanently
+reinstated, you do not qualify to receive new licenses for the same
+material under section 10.
+
+ 9. Acceptance Not Required for Having Copies.
+
+ You are not required to accept this License in order to receive or
+run a copy of the Program. Ancillary propagation of a covered work
+occurring solely as a consequence of using peer-to-peer transmission
+to receive a copy likewise does not require acceptance. However,
+nothing other than this License grants you permission to propagate or
+modify any covered work. These actions infringe copyright if you do
+not accept this License. Therefore, by modifying or propagating a
+covered work, you indicate your acceptance of this License to do so.
+
+ 10. Automatic Licensing of Downstream Recipients.
+
+ Each time you convey a covered work, the recipient automatically
+receives a license from the original licensors, to run, modify and
+propagate that work, subject to this License. You are not responsible
+for enforcing compliance by third parties with this License.
+
+ An "entity transaction" is a transaction transferring control of an
+organization, or substantially all assets of one, or subdividing an
+organization, or merging organizations. If propagation of a covered
+work results from an entity transaction, each party to that
+transaction who receives a copy of the work also receives whatever
+licenses to the work the party's predecessor in interest had or could
+give under the previous paragraph, plus a right to possession of the
+Corresponding Source of the work from the predecessor in interest, if
+the predecessor has it or can get it with reasonable efforts.
+
+ You may not impose any further restrictions on the exercise of the
+rights granted or affirmed under this License. For example, you may
+not impose a license fee, royalty, or other charge for exercise of
+rights granted under this License, and you may not initiate litigation
+(including a cross-claim or counterclaim in a lawsuit) alleging that
+any patent claim is infringed by making, using, selling, offering for
+sale, or importing the Program or any portion of it.
+
+ 11. Patents.
+
+ A "contributor" is a copyright holder who authorizes use under this
+License of the Program or a work on which the Program is based. The
+work thus licensed is called the contributor's "contributor version".
+
+ A contributor's "essential patent claims" are all patent claims
+owned or controlled by the contributor, whether already acquired or
+hereafter acquired, that would be infringed by some manner, permitted
+by this License, of making, using, or selling its contributor version,
+but do not include claims that would be infringed only as a
+consequence of further modification of the contributor version. For
+purposes of this definition, "control" includes the right to grant
+patent sublicenses in a manner consistent with the requirements of
+this License.
+
+ Each contributor grants you a non-exclusive, worldwide, royalty-free
+patent license under the contributor's essential patent claims, to
+make, use, sell, offer for sale, import and otherwise run, modify and
+propagate the contents of its contributor version.
+
+ In the following three paragraphs, a "patent license" is any express
+agreement or commitment, however denominated, not to enforce a patent
+(such as an express permission to practice a patent or covenant not to
+sue for patent infringement). To "grant" such a patent license to a
+party means to make such an agreement or commitment not to enforce a
+patent against the party.
+
+ If you convey a covered work, knowingly relying on a patent license,
+and the Corresponding Source of the work is not available for anyone
+to copy, free of charge and under the terms of this License, through a
+publicly available network server or other readily accessible means,
+then you must either (1) cause the Corresponding Source to be so
+available, or (2) arrange to deprive yourself of the benefit of the
+patent license for this particular work, or (3) arrange, in a manner
+consistent with the requirements of this License, to extend the patent
+license to downstream recipients. "Knowingly relying" means you have
+actual knowledge that, but for the patent license, your conveying the
+covered work in a country, or your recipient's use of the covered work
+in a country, would infringe one or more identifiable patents in that
+country that you have reason to believe are valid.
+
+ If, pursuant to or in connection with a single transaction or
+arrangement, you convey, or propagate by procuring conveyance of, a
+covered work, and grant a patent license to some of the parties
+receiving the covered work authorizing them to use, propagate, modify
+or convey a specific copy of the covered work, then the patent license
+you grant is automatically extended to all recipients of the covered
+work and works based on it.
+
+ A patent license is "discriminatory" if it does not include within
+the scope of its coverage, prohibits the exercise of, or is
+conditioned on the non-exercise of one or more of the rights that are
+specifically granted under this License. You may not convey a covered
+work if you are a party to an arrangement with a third party that is
+in the business of distributing software, under which you make payment
+to the third party based on the extent of your activity of conveying
+the work, and under which the third party grants, to any of the
+parties who would receive the covered work from you, a discriminatory
+patent license (a) in connection with copies of the covered work
+conveyed by you (or copies made from those copies), or (b) primarily
+for and in connection with specific products or compilations that
+contain the covered work, unless you entered into that arrangement,
+or that patent license was granted, prior to 28 March 2007.
+
+ Nothing in this License shall be construed as excluding or limiting
+any implied license or other defenses to infringement that may
+otherwise be available to you under applicable patent law.
+
+ 12. No Surrender of Others' Freedom.
+
+ If conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot convey a
+covered work so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you may
+not convey it at all. For example, if you agree to terms that obligate you
+to collect a royalty for further conveying from those to whom you convey
+the Program, the only way you could satisfy both those terms and this
+License would be to refrain entirely from conveying the Program.
+
+ 13. Use with the GNU Affero General Public License.
+
+ Notwithstanding any other provision of this License, you have
+permission to link or combine any covered work with a work licensed
+under version 3 of the GNU Affero General Public License into a single
+combined work, and to convey the resulting work. The terms of this
+License will continue to apply to the part which is the covered work,
+but the special requirements of the GNU Affero General Public License,
+section 13, concerning interaction through a network will apply to the
+combination as such.
+
+ 14. Revised Versions of this License.
+
+ The Free Software Foundation may publish revised and/or new versions of
+the GNU General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+ Each version is given a distinguishing version number. If the
+Program specifies that a certain numbered version of the GNU General
+Public License "or any later version" applies to it, you have the
+option of following the terms and conditions either of that numbered
+version or of any later version published by the Free Software
+Foundation. If the Program does not specify a version number of the
+GNU General Public License, you may choose any version ever published
+by the Free Software Foundation.
+
+ If the Program specifies that a proxy can decide which future
+versions of the GNU General Public License can be used, that proxy's
+public statement of acceptance of a version permanently authorizes you
+to choose that version for the Program.
+
+ Later license versions may give you additional or different
+permissions. However, no additional obligations are imposed on any
+author or copyright holder as a result of your choosing to follow a
+later version.
+
+ 15. Disclaimer of Warranty.
+
+ THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
+APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
+HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
+OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
+THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
+IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
+ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+ 16. Limitation of Liability.
+
+ IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
+THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
+GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
+USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
+DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
+PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
+EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGES.
+
+ 17. Interpretation of Sections 15 and 16.
+
+ If the disclaimer of warranty and limitation of liability provided
+above cannot be given local legal effect according to their terms,
+reviewing courts shall apply local law that most closely approximates
+an absolute waiver of all civil liability in connection with the
+Program, unless a warranty or assumption of liability accompanies a
+copy of the Program in return for a fee.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+state the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+ <one line to give the program's name and a brief idea of what it does.>
+ Copyright (C) <year> <name of author>
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+Also add information on how to contact you by electronic and paper mail.
+
+ If the program does terminal interaction, make it output a short
+notice like this when it starts in an interactive mode:
+
+ <program> Copyright (C) <year> <name of author>
+ This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+ This is free software, and you are welcome to redistribute it
+ under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License. Of course, your program's commands
+might be different; for a GUI interface, you would use an "about box".
+
+ You should also get your employer (if you work as a programmer) or school,
+if any, to sign a "copyright disclaimer" for the program, if necessary.
+For more information on this, and how to apply and follow the GNU GPL, see
+<http://www.gnu.org/licenses/>.
+
+ The GNU General Public License does not permit incorporating your program
+into proprietary programs. If your program is a subroutine library, you
+may consider it more useful to permit linking proprietary applications with
+the library. If this is what you want to do, use the GNU Lesser General
+Public License instead of this License. But first, please read
+<http://www.gnu.org/philosophy/why-not-lgpl.html>.
diff --git a/HACKING/build-system.md b/HACKING/build-system.md
new file mode 100644
index 0000000..93770cc
--- /dev/null
+++ b/HACKING/build-system.md
@@ -0,0 +1,24 @@
+The build system is built around an automake-like system for GNU Make
+that I wrote. It is documented in `automake.txt`. It provides all of
+the standard targets and such; you tell it what to do by setting a
+series of `am_whatever` variables. I'm just going to call it
+"automake" here.
+
+I also hook into non-exposed parts of automake with a couple of
+`_am_whatever` variables. Hopefully I will come up with good ways to
+expose the needed functionality in the future.
+
+There are a couple of variables that get automatically set from git.
+This happens by `include`ing a hidden makefile that sets them; if
+`$(topsrcdir)/.git` exists, it knows to use git to generate it;
+otherwise it expects the file to exist:
+
+| variable | file |
+|--------------------+----------------------------------------|
+| srcfiles | $(srcdir)/.srcfiles.mk |
+| LIBRETOOLS_VERSION | $(topsrcdir)/.srcversion-libretools.mk |
+| LIBRETOOLS_COMMIT | $(topsrcdir)/.srcversion-libretools.mk |
+| DEVTOOLS_VERSION | $(topsrcdir)/.srcversion-devtools.mk |
+| DEVTOOLS_COMMIT | $(topsrcdir)/.srcversion-devtools.mk |
+
+`srcfiles` basically becomes `am_src_files`.
diff --git a/HACKING/code-quality.md b/HACKING/code-quality.md
new file mode 100644
index 0000000..8efd561
--- /dev/null
+++ b/HACKING/code-quality.md
@@ -0,0 +1,135 @@
+Code content
+============
+
+Be aware of the `librelib(7)` library suite, which lives `src/lib`.
+It is a suite of Bash libraries that will help you out. Most of the
+people looking at the libretools code are familiar with the `messages`
+part of it, which actually contains a bunch of utility routines, not
+just message printing. There is also a library for dealing with
+`blacklist.txt`, and one for loading configuration files and
+PKGBUILDs. These are common tasks, but are tricky to handle
+consistently--the libraries are there to make things easier. Take a
+look at the man pages.
+
+Message printing: All of the message printing routines, except for
+`term_title` and `flag`, take printf-type arguments. Take advantage
+of that; don't use string interpolation (don't do `"foo ${var}
+bar"`). The reason for this is that if you don't do string
+interpolation, messages can be automatically internationalized.
+(Internationalization is incomplete at the momement)
+
+Message printing: The in `--help`/`-h` text, use `print` to print
+lines that should not wrap, `echo` to print blank lines, `prose` to
+print paragraphs, `bullet` to print bullet points, and `flag` to print
+option flags. The text should follow this general format:
+
+ print "Usage: %s [OPTIONS] VARS_ARE_UNDERSCORE_AND_CAPITAL" "${program_name}"
+ print "One line description of program, no period"
+ echo
+ prose "More details. This is a paragraph."
+ echo
+ print "Options:"
+ flag "-h" "Show this message"
+
+In the "Usage:" line, use printf `%s` and the value `"${0##*/}"` to
+determine the program name at runtime.
+
+There used to be guidelines for how to align the option flags and
+descriptions, but now the `flag` command exists takes care of it for
+you. Yay for things being easier!
+
+When using `set -u`, `set -e`, or `trap`, you should also use `set -E`
+to have the error handling be passed down to subshells.
+
+Feel free to use `set -e` (fail on error), but be careful of the
+caveats (there are a bunch of them); don't assume all errors are
+checked because of it.
+
+Use `set -u` if you can; it makes using an unset variable an error.
+ - If a variable not being set is valid (perhaps a configuration
+ option), use `${var:-}` when accessing it to suppress the error.
+ - An empty array counts as unset, so if you have an array that may be
+ empty, use `${var+"${var[@]}"}` (don't put quotes around the outer
+ pair of braces) to only access it if it's non-empty.
+ - The reason for this is that a normal string variable is basically
+ an array with length=1; an unset variable looks like an array
+ with length=0. Weird stuff.
+
+In the shebang, use `#!/usr/bin/env bash`. This allows us to not
+hardcode the location of bash (I'm not sure why this is useful for
+something distro-dependent like libretools, but fauno seems to have a
+use-case for it).
+
+In the shebang, don't pass flags to bash, besides breaking `env`
+(above), it means people will make mistakes when debugging, and
+running things with `bash FILENAME`. Instead, use `set` to adjust the
+flags inside of the program.
+
+Obey `$TMPDIR`. It's usually as easy as passing `--tmpdir` to
+`mktemp`.
+
+Use `trap` to clean up your temporary files. This way, even if your
+program terminates early, things will be cleaned up.
+
+Bash best practices
+===================
+
+Basically, know what you are doing, and be safe with it. The problem
+is that most people don't know about safe bash scripting.
+
+A lot of people look at the "Advanced Bash Scripting" ebook--DO NOT do
+that, it is trash... though it contains a "reference card" page that
+may be useful and isn't trash.
+
+Take a look at Gentoo's Bash guidelines
+<http://devmanual.gentoo.org/tools-reference/bash/index.html>.
+They're pretty good, and cover most of the "gotcha's" about Bash
+syntax. It mentions but discourages the use of Bash 3
+features... why? Who still uses Bash 2? Feel free to use Bash 4
+features!
+
+I wrote an article on Bash arrays
+<https://lukeshu.com/blog/bash-arrays.html>. A lot of people think
+they're tricky, but they're simple once you know how they work. It's
+short enough that you should read the whole thing. Know the
+difference between `"${array[@]}"` and `"${array[*]}"`. And I'll say
+it again here, ALWAYS wrap those in double quotes; there is no reason
+I can think of that the unquoted behavior would ever be the correct
+thing.
+
+My brief rules of thumb:
+
+ - Quote every variable.
+ - That includes arrays: `"${array[@]}"` and `"${array[*]}"`.
+ - In most (but not all!) cases inside of `[[ ... ]]` conditions,
+ variables don't need to be quoted. When in doubt, quote them.
+ - When assigning one variable to another, you don't need quotes;
+ you don't need quotes for `foo=$bar`
+ - Try to avoid global variables; declare all variables in functions
+ with `local`.
+ - Or `declare`; inside of a function, unless you pass the `-g`
+ flag, `declare` makes the variable local.
+ - Use `local VAR` before a `for VAR in LIST` loop--the variable is created in the
+ current scope, not the scope of the loop.
+ - Feeding input to `while` loops is weird because of how subshells
+ work:
+
+ # Input from a file
+ # BAD
+ cat file | while read line; do
+ ...
+ done
+ # GOOD
+ while read line; do
+ ...
+ done <file
+
+ # Input from a program
+ # BAD
+ prog | while read line; do
+ ...
+ done
+ # GOOD
+ while read line; do
+ ...
+ done < <(prog)
diff --git a/HACKING/code-style.md b/HACKING/code-style.md
new file mode 100644
index 0000000..077bc49
--- /dev/null
+++ b/HACKING/code-style.md
@@ -0,0 +1,62 @@
+The style guidelines aren't terribly strict. As long as things are
+consistent per-file, I'm pretty happy.
+
+Style guidelines
+================
+
+Unless you have a good reason, use `[[ ... ]]` instead of `[ ... ]`;
+they work similarly, but `[[ ... ]]` is sometimes more readable (fine,
+rarely, but never less), and is harder to make mistakes with quoting,
+because it is syntactic magic, as opposed to `[ ... ]` which is an
+executable which just happens to be implemented as a builtin.
+
+Use a litteral tab for indent. When indenting line-wrapped text, such
+as that for `prose`, do it like this: (» indicates tab, · indicates
+space)
+
+ func() {
+ » prose "This is the first line. This paragraph is going to be
+ » ·······wrapped."
+ }
+
+The `; then` and `; do` should go on the same line as
+`if`/`elif`/`for`/`while`. Also, there is no space before the `;`.
+
+Prefer the `for VAR in LIST` syntax over the `for ((init; cond; inc))`
+syntax, when possible. For example (heh, `for` example):
+
+ local i
+ for (( i = 1 ; i <= 10 ; i++ )); do
+
+should be
+
+ local i
+ for i in {1..10}; do
+
+Of course, if the upper bound is a variable, the C-like syntax is
+the better option, as otherwise you would have to use `seq` (calling
+an external), or `eval` (gross, easy to mess up royally).
+
+Indent comments like you would code; don't leave them at the beginning
+of the line. Example:
+
+ for item in "${list[@]}"; do
+ if [[ $item == foo ]]; then
+ # BAD
+ foobar
+ fi
+ if [[ $item == bar ]]; then
+ # GOOD
+ barbaz
+ fi
+ done
+
+Fauno, I'm sorry. But I don't know how you can read your own code :P.
+
+Some people argue in favor of the useless use of cat, because data
+should flow from left to right. However, the input redirection
+doesn't have to go on the right side of a command:
+
+ cat file | program # useless use of cat
+ program < file # data flows right to left
+ < file program # just right
diff --git a/HACKING/contributing.md b/HACKING/contributing.md
new file mode 100644
index 0000000..65066b5
--- /dev/null
+++ b/HACKING/contributing.md
@@ -0,0 +1,26 @@
+Contributing
+============
+
+I'd love to have your patches! Code should be hackable; if you want
+to modify something, but can't figure out how: 1) ping me for help, 2)
+it probably means the code was too complicated in the first place.
+
+Patches should be sent to <dev@lists.parabolagnulinux.org>; please put
+"[PATCH]" and "libretools" in the subject line. If you have commit
+access, but want me to look over it first, feel free to create a new
+branch in git, and I will notice it. Try to avoid pushing to the
+"master" branch unless it's a trivial change; it makes it easier to
+review things; though I *will* look over every commit before I do a
+release, so don't think you can sneak something in :)
+
+Be sure to make sure to follow the licensing requirements in
+`HACKING/licensing.md`
+
+I'd love to discuss possible changes on IRC (I'm lukeshu), either on
+irc.freenode.net#parabola or in personal messages. My account may be
+online even if I'm not; I will eventually see your message, I do a
+search for mentions of "luke" on #parabola every time I get on.
+
+Please write unit tests for new functionality. Or old functionality.
+Please write unit tests! See `HACKING/testing.md` for details on
+testing.
diff --git a/HACKING/licensing.md b/HACKING/licensing.md
new file mode 100644
index 0000000..6d17e31
--- /dev/null
+++ b/HACKING/licensing.md
@@ -0,0 +1,19 @@
+We don't require copyright assignment or any fancy paperwork! Just
+make sure you specify the license and include a copyright statement
+with your name and the current year.
+
+New code should (please) be licensed GPLv2+. I say v2 instead of v3
+because some code from Arch is GPLv2 (no "or any later version"), and
+having to worry about which programs can be combined is a huge pain.
+
+Copyright statements should look like
+
+ # Copyright (C) YEARS NAME <EMAIL>
+
+for most code, for 3rd-party code that has been imported, indent it a
+bit:
+
+ # Copyright (C) YEARS NAME <EMAIL>
+
+Always put a line with `# License:` specifying the license of that
+file.
diff --git a/HACKING/testing.md b/HACKING/testing.md
new file mode 100644
index 0000000..8dee485
--- /dev/null
+++ b/HACKING/testing.md
@@ -0,0 +1,18 @@
+Testing
+=======
+
+Please write unit tests for new things. Tests can be run with `make
+check`, which just runs `./testenv roundup` in the `test/` directory.
+Relatedly, you need the `roundup` (the `sh-roundup` package on
+Parabola) tool to run the tests. `./testenv` can be given
+`--no-network` and/or `--no-sudo` to dissable tests that require those
+things. Make can be made to pass those things in by setting
+`TESTENVFLAGS`. If you don't dissable either, I *strongly* recommend
+setting TMPDIR to somewhere on a btrfs partition before running the
+tests; otherwise the chroot tests will take forever. I mean, they
+already take long on btrfs, but without it... _dang_.
+
+I also recommend having the `haveged` daemon running. That's good
+general advice, but also: some of the tests make GPG keys, this
+"should" take on the order of 1 second, but can take several minutes
+if you don't have `haveged` running.
diff --git a/INSTALL b/INSTALL
new file mode 100644
index 0000000..56e7103
--- /dev/null
+++ b/INSTALL
@@ -0,0 +1,120 @@
+Installation of libretools is pretty straight-forward:
+
+ $ make
+ # make install
+
+As a caveat, by default, if the directory `devtools-par` exists in the
+same directory as the `libretools-${version}` directory, and contains
+newer files than the libretools directory, they will be copied into
+the libretools directory. See INSTALL-VCS for more details.
+
+Dependencies
+------------
+
+Unlike a lot of software, run-time dependencies are not required at
+build-time.
+
+Libretools is mostly shell scripts. It very specifically targets
+Parabola GNU/Linux-libre; there are a lot of dependencies, most of
+which are undocumented. Switching away from a GNU user-land is bound
+to cause issues.
+
+## Build dependencies
+
+The "unusual" build-time dependencies are:
+
+ - GNU Make -- other `make`s will not work.
+ - GNU sed -- must support `-r` for ERE; BSD sed uses `-E` for this
+ purpose.
+ - Emacs 24.4+ -- `emacs --batch` is used to use Emacs Lisp to process
+ some text. GNU Emacs 24.4 introduced a new `advice-add` system
+ which we depend on.
+ - ronn -- A markdown-to-manpage converter
+
+At this time, the build system does not support not building the
+documentation; ronn is required.
+
+Additionally, other usual "core utilities" are required:
+
+ cat, chmod, cp, echo, false, find, install, ln, msguniq, rm, tr,
+ xgettext
+
+Also, `/bin/sh` must support `{brace,expansion}`. If it doesn't, find
+a shell that does, and configure `make` to use it. Though, because
+libretools is mostly shell scripts, if you deviate too much from a GNU
+command-line environment, I suspect that you will run into more
+issues. If I were targeting anything other than Parabola, this would
+be a bigger issue.
+
+## Test suite dependencies
+
+If you wish to run the test suite, you will need the "roundup" shell
+unit testing program. On Parabola GNU/Linux-libre it is called
+"sh-roundup". If your operating system doesn't have it, it is
+available at <http://bmizerany.github.io/roundup/>.
+
+## Run-time dependencies
+
+Being mostly shell scripts, many external program are used. Anything
+that is included when installing the `base` package group on Parabola
+GNU/Linux, I consider an implicit dependency. If something isn't used
+now, that doesn't mean it won't be in the future.
+
+On top of that, the following dependencies are also needed:
+
+librelib subpackage:
+ - wget
+gitget subpackage:
+ - librelib (provided)
+ - git
+xbs subpackage:
+ - gitget (provided)
+ - librelib (provided)
+ - subversion (only for the 'abs' module)
+main libretools subpackage:
+ - librelib (provided)
+ - xbs (provided)
+ - pacman 5.0
+ - arch-install-scripts
+ - GNU Make (only needed for `librefetch`)
+ - ssh client (OpenSSH, only needed for `librerelease`)
+ - rsync
+ - systemd-nspawn (for the chroot tools)
+ - tokyocabinet
+
+Configuration
+-------------
+
+This is not a GNU package, there is no `./configure` script.
+
+There are two ways to set configuration variables:
+ 1. Edit config.mk
+ 2. Pass `VARIABLE=VALUE` to `make`
+
+The configuration variables mostly match GNU packages, but default
+values differ; libretools installs to `prefix=/usr` by default instead
+of GNU's `prefix=/usr/local`.
+
+Building and installing subpackages
+-----------------------------------
+
+There are several subpackages you can build and install. This is done
+by running:
+
+ $ make build-${package}
+ # make install-${package}
+
+respectively. In addition to `build` and `install`, the activities
+you can do are:
+
+ - `copy` -- copy necessary files from the devtools-par source code
+ - `build` -- build all programs and files
+ - `install` -- install everything
+ - `clean` -- remove generated files
+
+The subpackages you can run these on are:
+
+ - libretools -- The main libretools package
+ - librelib -- Generic libraries included
+ - gitget -- A git downloader
+ - xbs -- Tools for dealing with differences between ABS/ABSLibre/PBS
diff --git a/INSTALL-VCS b/INSTALL-VCS
new file mode 100644
index 0000000..be80d41
--- /dev/null
+++ b/INSTALL-VCS
@@ -0,0 +1,17 @@
+The only tricky thing when building from Git is the weird dependence
+on the devtools-par source code.
+
+The build system will by default look at `$(topdir)/../devtools-par`
+for the devtools-par source code, and copy the relevant files into the
+libretools directory. This can be changed by adjusting the
+`devtoolsdir` configuration variable, see "Configuration" in the main
+INSTALL file. If the `$(devtoolsdir)` directory does not exist, but
+all of the copied files exist in the libretools directory, they will
+simply be used.
+
+In the distribution tarball includes the copies of the devtools files,
+so it is not necessary to download the devtools source separately
+when using the tarball.
+
+Once you have the devtools source taken care of, you can build and
+install like normal.
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..5be5195
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,39 @@
+include $(dir $(lastword $(MAKEFILE_LIST)))/config.mk
+include $(topsrcdir)/automake.head.mk
+
+# these are the resulting packages
+packages=libretools librelib gitget xbs
+# and which directories they contain
+libretools=\
+ src \
+ src/abslibre-tools \
+ src/chroot-tools \
+ src/devtools \
+ src/librefetch \
+ src/toru
+librelib=src/lib
+gitget=src/gitget
+xbs=\
+ src/xbs \
+ src/xbs-abs \
+ src/xbs-abslibre
+
+verbs=build install uninstall mostlyclean clean distclean maintainer-clean check
+$(foreach verb,$(verbs),$(foreach package,$(packages),$(eval $(verb)-$(package): $(addsuffix /$(verb),$($(package))))))
+$(foreach verb,$(verbs),$(foreach package,$(packages),$(eval .PHONY: $(verb)-$(package))))
+
+$(outdir)/check::
+ cd $(@D)/test && ./testenv $(TESTENVFLAGS) roundup
+
+_po_rule = \
+po/%(package).pot: $(addsuffix /everything.pot,$(%(package))); \
+ cat $^ | msguniq -Fi --to-code=UTF-8 > '$@' || rm -f '$@'
+$(foreach package,$(packages),$(eval $(subst %(package),$(package),$(value _po_rule))))
+
+pots =
+am_out_files += $(foreach package,$(packages),po/$(package).pot)
+am_clean_files += .var.*
+am_gen_files += .srcversion-libretools.mk .srcversion-devtools.mk
+am_subdirs = src $(foreach package,$(packages),$($(package)))
+
+include $(topsrcdir)/automake.tail.mk
diff --git a/build-aux/Makefile.README.txt b/build-aux/Makefile.README.txt
new file mode 100644
index 0000000..e06ba52
--- /dev/null
+++ b/build-aux/Makefile.README.txt
@@ -0,0 +1,164 @@
+Luke's AutoMake
+===============
+
+Yo, this document is incomplete. It describes the magical
+automake.{head,tail}.mk Makefiles and how to use them, kinda.
+
+I wrote a "clone" of automake. I say clone, because it works
+differently. Yeah, I need a new name for it.
+
+High-level overview
+-------------------
+
+Now, what this does for you is:
+
+It makes it _easy_ to write non-recursive Makefiles--and ones that are
+similar to plain recursive Makefiles, at that! (search for the paper
+"Recursive Make Considered Harmful") As harmful as recursive make is,
+it's historically been difficult to to write non-recursive Makefiles.
+This makes it easy.
+
+It also makes it easy to follow the GNU standards for your makefiles:
+it takes care of this entire table of .PHONY targets for you:
+
+| this | and this | are aliases for this |
+|------+------------------+--------------------------------------------------------|
+| all | build | $(outdir)/build |
+| | install | $(outdir)/install |
+| | uninstall | $(outdir)/uninstall |
+| | mostlyclean | $(outdir)/mostlyclean |
+| | clean | $(outdir)/clean |
+| | distclean | $(outdir)/distclean |
+| | maintainer-clean | $(outdir)/maintainer-clean |
+| | check | $(outdir)/check (not implemented for you) |
+| | dist | $(topoutdir)/$(PACKAGE)-$(VERSION).tar.gz (not .PHONY) |
+
+(You are still responsible for implementing the `$(outdir)/check`
+target in each of your Makefiles.)
+
+What you have to do is:
+
+In each source directory, you write a `Makefile`, very similarly to if
+you were writing for plain GNU Make, with
+
+ topoutdir ?= ...
+ topsrcdir ?= ...
+ include $(topsrcdir)/build-aux/Makefile.head.mk
+
+ # your makefile
+
+ include $(topsrcdir)/build-aux/Makefile.tail.mk
+
+And in the top-level source directory, Write your own helper makefiles
+that get included:
+ - `common.once.head.mk`: before parsing any of your Makefiles
+ - `common.each.head.mk`: before parsing each of your Makefiles
+ - `common.each.tail.mk`: after parsing each of your Makefiles
+ - `common.each.tail.mk`: after parsing all of your Makefiles
+
+The `common.*.mk` makefiles are nice for including generic pattern
+rules and variables that aren't specific to a directory.
+
+You're probably thinking that this sounds too good to be true!
+Unfortunately, there are two major deviations from writing a plain
+recursive Makefile:
+
+ 1. all targets and prerequisites (including .PHONY targets!) need to
+ be prefixed with
+ `$(srcdir)`/`$(outdir)`/`$(topsrcdir)`/`$(topoutdir)`.
+ * sub-gotcha: this means that if a pattern rule has a
+ prerequisite that may be in srcdir or outdir, then it must be
+ specified twice, once for each case.
+ 2. if a prerequisite is in a directory "owned" by another Makefile,
+ you must filter the pathname through `am_path`:
+ `$(call am_path,YOUR_PATH)`. Further, that path must NOT contain
+ a `..` segment; if you need to refer to a sibling directory, do it
+ relative to `$(topoutdir)` or `$(topsrcdir)`.
+
+Telling automake about your program
+-----------------------------------
+
+You tell automake what to do for you by setting some variables. They
+are all prefixed with `am_`; this prefix may be changed by editing the
+`_am` variable at the top of `automake.head.mk`.
+
+The exception to this is the `am_path` variable, which is a macro that
+is used to make a list of filenames relative to the appropriate
+directory, because unlike normal GNU (Auto)Make, `$(outdir)` isn't
+nescessarily equal to `.`. See above.
+
+There are several commands that generate files; simply record the list
+of files that each command generates as the following variable
+variables:
+
+| Variable | Create Command | Delete Command | Description | Relative to |
+|--------------+----------------+-----------------------------+-----------------------------------+-------------|
+| am_src_files | emacs | rm -rf . | Files that the developer writes | srcdir |
+| am_gen_files | ??? | make maintainer-clean | Files the developer compiles | srcdir |
+| am_cfg_files | ./configure | make distclean | Users' compile-time configuration | outdir |
+| am_out_files | make all | make mostlyclean/make clean | Files the user compiles | outdir |
+| am_sys_files | make install | make uninstall | Files the user installs | DESTDIR |
+
+In addition, there are two more variables that control not how files
+are created, but how they are deleted:
+
+| Variable | Affected command | Description | Relative to |
+|----------------+------------------+------------------------------------------------+-------------|
+| am_clean_files | make clean | A list of things to `rm` in addition to the | outdir |
+| | | files in `$(am_out_files)`. (Example: `*.o`) | |
+|----------------+------------------+------------------------------------------------+-------------|
+| am_slow_files | make mostlyclean | A list of things that (as an exception) should | outdir |
+| | | _not_ be deleted. (otherwise, `mostlyclean` | |
+| | | is the same as `clean`) | |
+
+Finally, there are two variables that express the relationships
+between directories:
+
+| Variable | Description |
+|------------+---------------------------------------------------------|
+| am_subdirs | A list of other directories (containing Makefiles) that |
+| | may be considered "children" of this |
+| | directory/Makefile; building a phony target in this |
+| | directory should also build it in the subdirectory. |
+| | They are not necesarily actually subdirectories of this |
+| | directory in the filesystem. |
+|------------+---------------------------------------------------------|
+| am_depdirs | A list of other directories (containing Makefiles) that |
+| | contain or generate files that are dependencies of |
+| | targets in this directory. They are not necesarily |
+| | actually subdirectories of this directory in the |
+| | filesystem. Except for files that are dependencies of |
+| | files in this directory, things in the dependency |
+| | directory will not be built. |
+
+Tips, notes
+-----------
+
+I like to have the first (non-comment) line in a Makefile be:
+
+ include $(dir $(lastword $(MAKEFILE_LIST)))/../../config.mk
+
+(adjusting the number of `../` sequences as nescessary). Then, my
+(user-editable) `config.mk` is of the form:
+
+ ifeq ($(topsrcdir),)
+ topoutdir := $(patsubst %/,%,$(dir $(lastword $(MAKEFILE_LIST))))
+ topsrcdir := $(topoutdir)
+
+ # your configuration
+
+ endif
+
+If the package has a `./configure` script, then I have it modifiy
+topsrcdir as necessary, as well as modifying whatever other parts of
+the configuration. All of the configuration lives in `config.mk`;
+`./configure` doesn't modify any `Makefile`s, it just generates
+`config.mk`, and copies (or (sym?)link?) every `$(srcdir)/Makefile` to
+`$(outdir)/Makefile`.
+
+----
+Copyright (C) 2016 Luke Shumaker
+
+This documentation file is placed into the public domain. If that is
+not possible in your legal system, I grant you permission to use it in
+absolutely every way that I can legally grant to you.
diff --git a/build-aux/Makefile.each.head/.gitignore b/build-aux/Makefile.each.head/.gitignore
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/build-aux/Makefile.each.head/.gitignore
diff --git a/build-aux/Makefile.each.head/00-dist.mk b/build-aux/Makefile.each.head/00-dist.mk
new file mode 100644
index 0000000..a094305
--- /dev/null
+++ b/build-aux/Makefile.each.head/00-dist.mk
@@ -0,0 +1,20 @@
+# Copyright (C) 2015-2016 Luke Shumaker
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+ifeq ($(outdir),$(topoutdir))
+std.clean_files += $(addprefix $(dist.pkgname)-*,$(dist.exts) .tar /)
+endif
+
+$(outdir)/dist: $(addprefix $(topoutdir)/$(dist.pkgname)-$(dist.version),$(dist.exts))
diff --git a/build-aux/Makefile.each.tail/.gitignore b/build-aux/Makefile.each.tail/.gitignore
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/build-aux/Makefile.each.tail/.gitignore
diff --git a/build-aux/Makefile.head.mk b/build-aux/Makefile.head.mk
new file mode 100644
index 0000000..63a3462
--- /dev/null
+++ b/build-aux/Makefile.head.mk
@@ -0,0 +1,63 @@
+# Copyright (C) 2015-2016 Luke Shumaker
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+# This bit only gets evaluated once, at the very beginning
+ifeq ($(_at.NO_ONCE),)
+
+ifeq ($(topsrcdir),)
+$(error topsrcdir must be set before including Makefile.head.mk)
+endif
+ifeq ($(topoutdir),)
+$(error topoutdir must be set before including Makefile.head.mk)
+endif
+
+_at.noslash = $(patsubst %/.,%,$(patsubst %/,%,$1))
+# These are all $(call _at.func,parent,child)
+#at.relto = $(if $2,$(shell realpath -sm --relative-to='$1' $2))
+_at.is_subdir = $(filter $(abspath $1)/%,$(abspath $2)/.)
+_at.relto_helper = $(if $(call _at.is_subdir,$1,$2),$(patsubst $1/%,%,$(addsuffix /.,$2)),$(addprefix ../,$(call _at.relto_helper,$(patsubst %/,%,$(dir $1)),$2)))
+_at.relto = $(call _at.noslash,$(call _at.relto_helper,$(call _at.noslash,$(abspath $1)),$(call _at.noslash,$(abspath $2))))
+at.relto = $(foreach p,$2,$(call _at.relto,$1,$p))
+# Note that _at.is_subdir says that a directory is a subdirectory of
+# itself.
+at.path = $(call at.relto,.,$1)
+
+define at.nl
+
+
+endef
+
+_at.rest = $(wordlist 2,$(words $1),$1)
+_at.reverse = $(if $1,$(call _at.reverse,$(_at.rest))) $(firstword $1)
+
+at.dirlocal += at.subdirs
+at.dirlocal += at.depdirs
+
+include $(sort $(wildcard $(topsrcdir)/build-aux/Makefile.once.head/*.mk))
+
+endif # _at.NO_ONCE
+
+# This bit gets evaluated for each Makefile
+
+## Set outdir and srcdir (assumes that topoutdir and topsrcdir are
+## already set)
+outdir := $(call at.path,$(dir $(lastword $(filter-out %.mk,$(MAKEFILE_LIST)))))
+srcdir := $(call at.path,$(topsrcdir)/$(call _at.relto,$(topoutdir),$(outdir)))
+
+_at.included_makefiles := $(_at.included_makefiles) $(call at.path,$(outdir)/Makefile)
+
+$(foreach v,$(at.dirlocal),$(eval $v=))
+
+include $(sort $(wildcard $(topsrcdir)/build-aux/Makefile.each.head/*.mk))
diff --git a/build-aux/Makefile.once.head/.gitignore b/build-aux/Makefile.once.head/.gitignore
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/build-aux/Makefile.once.head/.gitignore
diff --git a/build-aux/Makefile.once.head/00-dist.mk b/build-aux/Makefile.once.head/00-dist.mk
new file mode 100644
index 0000000..d5bfcd3
--- /dev/null
+++ b/build-aux/Makefile.once.head/00-dist.mk
@@ -0,0 +1,44 @@
+# Copyright (C) 2015-2016 Luke Shumaker
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+# Developer configuration
+
+dist.exts ?= .tar.gz
+dist.pkgname ?= $(PACKAGE)
+dist.version ?= $(VERSION)
+
+ifeq ($(dist.pkgname),)
+$(error dist.pkgname must be set)
+endif
+ifeq ($(dist.version),)
+$(error dist.version must be set)
+endif
+
+# User configuration
+
+CP ?= cp
+GZIP ?= gzip
+MKDIR ?= mkdir
+MKDIR_P ?= mkdir -p
+MV ?= mv
+RM ?= rm -f
+TAR ?= tar
+
+GZIPFLAGS ?= $(GZIP_ENV)
+GZIP_ENV ?= --best
+
+# Implementation
+
+at.phony += dist
diff --git a/build-aux/Makefile.once.tail/.gitignore b/build-aux/Makefile.once.tail/.gitignore
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/build-aux/Makefile.once.tail/.gitignore
diff --git a/build-aux/Makefile.once.tail/00-dist.mk b/build-aux/Makefile.once.tail/00-dist.mk
new file mode 100644
index 0000000..b8b7733
--- /dev/null
+++ b/build-aux/Makefile.once.tail/00-dist.mk
@@ -0,0 +1,27 @@
+# Copyright (C) 2015-2016 Luke Shumaker
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+_dist.copyfile = $(MKDIR_P) $(dir $2) && $(CP) -T $1 $2
+_dist.addfile = $(call _dist.copyfile,$3,$2/$(call at.relto,$1,$3))
+$(topoutdir)/$(dist.pkgname)-$(dist.version): $(foreach v,$(filter std.src_files/% std.gen_files/%,$(.VARIABLES)),$($v))
+ $(RM) -r $@ $(@D)/tmp.$(@F)
+ $(MKDIR) $(@D)/tmp.$(@F)
+ $(foreach f,$^,$(call _dist.addfile,$(topsrcdir),$(@D)/tmp.$(@F),$f)$(at.nl))
+ $(MV) $(@D)/tmp.$(@F) $@ || $(RM) -r $(@D)/tmp.$(@F)
+
+$(topoutdir)/$(dist.pkgname)-$(dist.version).tar: $(topoutdir)/$(dist.pkgname)-$(dist.version)
+ $(TAR) cf $@ -C $(<D) $(<F)
+$(topoutdir)/$(dist.pkgname)-$(dist.version).tar.gz: $(topoutdir)/$(dist.pkgname)-$(dist.version).tar
+ $(GZIP) $(GZIPFLAGS) < $< > $@
diff --git a/build-aux/Makefile.tail.mk b/build-aux/Makefile.tail.mk
new file mode 100644
index 0000000..dfbad5a
--- /dev/null
+++ b/build-aux/Makefile.tail.mk
@@ -0,0 +1,51 @@
+# Copyright (C) 2015-2016 Luke Shumaker
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+# This bit gets evaluated for each Makefile processed
+
+include $(call _at.reverse,$(sort $(wildcard $(topsrcdir)/build-aux/Makefile.each.tail/*.mk)))
+
+at.subdirs := $(patsubst ./%,%,$(addprefix $(outdir)/,$(at.subdirs)))
+at.depdirs := $(patsubst ./%,%,$(addprefix $(outdir)/,$(at.depdirs)))
+
+# Move all of the dirlocal variables to their namespaced version
+$(foreach v,$(at.dirlocal),$(eval $v/$(outdir) := $$($v)))
+$(foreach v,$(at.dirlocal),$(eval undefine $v))
+
+# Remember that this is a directory that we've visited
+_at.outdirs := $(_at.outdirs) $(outdir)
+
+# Generic phony target declarations:
+# mark them phony
+.PHONY: $(addprefix $(outdir)/,$(at.phony))
+# have them depend on subdirs
+$(foreach t,$(at.phony),$(eval $(outdir)/$t: $(addsuffix /$t,$(at.subdirs/$(outdir)))))
+
+# Include Makefiles from other directories
+$(foreach _at.NO_ONCE,y,\
+ $(foreach makefile,$(call at.path,$(addsuffix /Makefile,$(at.subdirs/$(outdir)) $(at.depdirs/$(outdir)))),\
+ $(eval include $(filter-out $(_at.included_makefiles),$(makefile)))))
+
+# This bit only gets evaluated once, after all of the other Makefiles are read
+ifeq ($(_at.NO_ONCE),)
+
+outdir = /bogus
+srcdir = /bogus
+
+$(foreach v,$(at.dirlocal),$(eval $v=))
+
+include $(call _at.reverse,$(sort $(wildcard $(topsrcdir)/build-aux/Makefile.once.tail/*.mk)))
+
+endif # _at.NO_ONCE
diff --git a/common.each.head.mk b/common.each.head.mk
new file mode 100644
index 0000000..e3a269e
--- /dev/null
+++ b/common.each.head.mk
@@ -0,0 +1,35 @@
+pkgconfdir = $(sysconfdir)/libretools.d
+pkgdocdir = $(docdir)/libretools
+pkglibexecdir = $(libexecdir)/libretools
+
+# Detect things about the directory we're in ####################################
+
+srcfiles-ignore/$(srcdir) = $(addsuffix /%,$(_am_subdirs/$(@D)))
+
+ifeq ($(wildcard $(topsrcdir)/.git),)
+include $(srcdir)/.srcfiles.mk
+else
+$(srcdir)/.srcfiles.mk: FORCE
+ @echo srcfiles = $(filter-out $(srcfiles-ignore/$(@D)),$(shell cd $(@D) && git ls-files)) | $(write-ifchanged) $@
+-include $(srcdir)/.srcfiles.mk
+endif
+
+detect-ignore =
+detect-ignore-exec =
+detect-ignore-conf =
+detect-ignore-ronn =
+detect-ignore-md = HACKING.md
+detect-ignore-sh =
+
+devtools-files =
+
+libretools-srcs = $(detect-all)
+libretools-mans = $(patsubst %.ronn,%,$(detect-ronn))
+libretools-bins = $(detect-exec)
+libretools-libexecs =
+libretools-libs = $(detect-sh)
+libretools-docs = $(detect-md) $(detect-ronn)
+libretools-confs = $(detect-conf)
+
+libretools-files = $(libretools-mans) $(libretools-bins) $(libretools-libexecs) $(libretools-libs) $(libretools-docs) $(libretools-confs)
+pots = $(libretools-bins) $(libretools-libexecs) $(libretools-libs)
diff --git a/common.each.tail.mk b/common.each.tail.mk
new file mode 100644
index 0000000..e58b5c5
--- /dev/null
+++ b/common.each.tail.mk
@@ -0,0 +1,89 @@
+# Copyright (C) 2015, 2016 Luke Shumaker
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+ifneq ($(sort $(wildcard $(addprefix $(srcdir)/,$(devtools-files)))),$(sort $(addprefix $(srcdir)/,$(devtools-files))))
+ifeq ($(wildcard $(devtoolsdir)/),)
+$(error config.mk:devtoolsdir points to a non-existant directory: $(devtoolsdir))
+endif
+endif
+
+am_src_files += $(libretools-srcs)
+am_gen_files += .srcfiles.mk $(devtools-files)
+am_out_files += $(filter-out $(am_src_files) $(am_gen_files),$(libretools-files)) \
+ $(if $(pots),everything.pot)
+am_sys_files += $(addprefix $(bindir)/,$(libretools-bins)) \
+ $(addprefix $(pkgconfdir)/,$(libretools-confs)) \
+ $(addprefix $(pkglibexecdir)/,$(libretools-libexecs) $(libretools-libs)) \
+ $(addprefix $(pkgdocdir)/,$(libretools-docs)) \
+ $(addprefix $(mandir)/man1/,$(filter %.1,$(libretools-mans))) \
+ $(addprefix $(mandir)/man2/,$(filter %.2,$(libretools-mans))) \
+ $(addprefix $(mandir)/man3/,$(filter %.3,$(libretools-mans))) \
+ $(addprefix $(mandir)/man4/,$(filter %.4,$(libretools-mans))) \
+ $(addprefix $(mandir)/man5/,$(filter %.5,$(libretools-mans))) \
+ $(addprefix $(mandir)/man6/,$(filter %.6,$(libretools-mans))) \
+ $(addprefix $(mandir)/man7/,$(filter %.7,$(libretools-mans))) \
+ $(addprefix $(mandir)/man8/,$(filter %.8,$(libretools-mans)))
+am_clean_files += *.pot *.ugly *.rej *.orig .tmp*
+
+exec_$(outdir) := $(exec_$(outdir)) $(libretools-bins) $(libretools-libexecs)
+
+_is_executable = $(filter $(exec_$(@D)),$(@F))
+
+$(srcdir)/%.in: $(devtoolsdir)/%.in
+ cp -T '$<' '$@'
+$(srcdir)/%.in: $(devtoolsdir)/lib/%
+ cp -T '$<' '$@'
+$(outdir)/%: $(srcdir)/%.in
+ @echo 'EDIT < $< > $@'; $(edit) < '$<' | install -T -m$(if $(_is_executable),755,644) /dev/stdin '$@'
+$(outdir)/%: $(srcdir)/%.ronn
+ ronn --roff $(RONNFLAGS) < '$<' > '$@'
+$(outdir)/%.html: $(srcdir)/%.ronn
+ ronn --html $(RONNFLAGS) < '$<' > '$@'
+$(outdir)/%.pot: $(outdir)/% $(topsrcdir)/src/lib/librexgettext
+ $(topsrcdir)/src/lib/librexgettext $(LIBREXGETTEXT_FLAGS) '$<' > '$@'
+$(outdir)/%.pot: $(srcdir)/% $(topsrcdir)/src/lib/librexgettext
+ $(topsrcdir)/src/lib/librexgettext $(LIBREXGETTEXT_FLAGS) '$<' > '$@'
+$(outdir)/everything.pot: $(addprefix $(outdir)/,$(addsuffix .pot,$(pots)))
+ cat $^ | $(pofmt) > '$@'
+
+# If we have a .patch file, the flow is:
+# $(devtoolsdir)/%.in -> %.in + %.patch -> %.ugly -> %
+_do_patch = $(filter $(patsubst %.patch,%,$(filter %.patch,$(detect-all))),$(patsubst %.in,%,$(devtools-files)))
+$(outdir)/%.ugly: $(srcdir)/%.in $(srcdir)/%.patch
+ cp -T $< $@
+ patch $@ $(<D)/$*.patch
+$(sort $(addprefix $(outdir)/,$(_do_patch))): $(outdir)/%: $(outdir)/%.ugly
+ @echo 'EDIT < $< > $@'; $(edit) < '$<' | install -T -m$(if $(_is_executable),755,644) /dev/stdin '$@'
+ @echo 'INDENT $@'; $(call indent,$@)
+
+$(DESTDIR)$(pkgconfdir)/% : $(outdir)/% ; install -T -Dm644 '$<' '$@'
+$(DESTDIR)$(pkgdocdir)/% : $(outdir)/% ; install -T -Dm644 '$<' '$@'
+$(DESTDIR)$(mandir)/man1/%.1: $(outdir)/%.1; install -T -Dm644 '$<' '$@'
+$(DESTDIR)$(mandir)/man2/%.2: $(outdir)/%.2; install -T -Dm644 '$<' '$@'
+$(DESTDIR)$(mandir)/man3/%.3: $(outdir)/%.3; install -T -Dm644 '$<' '$@'
+$(DESTDIR)$(mandir)/man4/%.4: $(outdir)/%.4; install -T -Dm644 '$<' '$@'
+$(DESTDIR)$(mandir)/man5/%.5: $(outdir)/%.5; install -T -Dm644 '$<' '$@'
+$(DESTDIR)$(mandir)/man6/%.6: $(outdir)/%.6; install -T -Dm644 '$<' '$@'
+$(DESTDIR)$(mandir)/man7/%.7: $(outdir)/%.7; install -T -Dm644 '$<' '$@'
+$(DESTDIR)$(mandir)/man8/%.8: $(outdir)/%.8; install -T -Dm644 '$<' '$@'
+$(DESTDIR)$(pkglibexecdir)/%: $(outdir)/% ; mkdir -p '$(@D)' && cp -T '$<' '$@'
+$(DESTDIR)$(bindir)/% : $(outdir)/% ; install -T -Dm755 '$<' '$@'
+# Repeat the last two rules again with explicit targets because
+# otherwise it would try to do src/xbs->/bin/xbs instead of
+# src/xbs/xbs->/bin/xbs
+ifneq ($(filter $(notdir $(outdir)),$(libretools-files)),)
+$(DESTDIR)$(pkglibexecdir)/$(notdir $(outdir)): $(DESTDIR)$(pkglibexecdir)/%: $(outdir)/% ; mkdir -p '$(@D)' && cp -T '$<' '$@'
+$(DESTDIR)$(bindir)/$(notdir $(outdir)): $(DESTDIR)$(bindir)/% : $(outdir)/% ; install -T -Dm755 '$<' '$@'
+endif
diff --git a/common.once.head.mk b/common.once.head.mk
new file mode 100644
index 0000000..33084fa
--- /dev/null
+++ b/common.once.head.mk
@@ -0,0 +1,83 @@
+# Copyright (C) 2015 Luke Shumaker
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+# Configure how Make works
+MAKEFLAGS += --no-builtin-rules --no-builtin-variables
+.SECONDARY:
+.DELETE_ON_ERROR:
+
+.PHONY: FORCE
+
+write-ifchanged = $(topsrcdir)/write-ifchanged
+
+# Magic for tracking variables that affect files. If a file changes
+# based on a variable, just stick $(var)VARNAME as a dependency.
+var = $(call _am_path,$(topoutdir)/.var.)
+$(var)%: FORCE
+ @printf '%s' '$($*)' | $(write-ifchanged) $@
+
+# Usage: <INPUT $(edit) >OUTPUT
+# Expand m4_include macros to use librelib
+# This is used when using sources grabbed from devtools
+# Magic for doing @variable@ replacement in files
+edit = sed \
+ -e 's|^\#!\s*/bin/bash|\#!/usr/bin/env bash|' \
+ -e 's|m4_include(lib/\(.*\))|. "$$(librelib \1)"|' \
+ $(foreach v,$(patsubst $(var)%,%,$(filter $(var)%,$^)), -e 's|@$(v)@|$($(v))|g' )
+
+# Usage: $(call indent,FILENAME)
+# Command to auto-indent a file.
+indent = emacs --batch $1 \
+ --eval '(setq make-backup-files nil)' \
+ --eval '(setq sh-basic-offset 8)' \
+ --eval '(defun sh-smie-sh-rules--fix (args) "fix bug in Emacs 24 sh-script.el" (if (equal args (list :after "then")) (list :after "if") args))' \
+ --eval "(advice-add 'sh-smie-sh-rules :filter-args \#'sh-smie-sh-rules--fix)" \
+ --eval '(indent-region (point-min) (point-max) nil)' \
+ -f save-buffer &>/dev/null
+
+# Usage <INPUT $(pofmt) >OUTPUT
+# Normalize a .po(t) file
+pofmt = msguniq -Fi --to-code=UTF-8
+
+# It's easy to think of these as "each" variables, but because they
+# will be evaluated on demand, only am_src_files needs to be "each".
+detect-all = $(filter-out $(detect-ignore) ,$(srcfiles))
+detect-exec = $(filter-out $(detect-ignore-exec),$(patsubst $(srcdir)/%,%,$(shell find $(addprefix $(srcdir)/,$(detect-all)) -executable)))
+detect-conf = $(filter-out $(detect-ignore-conf),$(filter %.conf ,$(detect-all)))
+detect-ronn = $(filter-out $(detect-ignore-ronn),$(filter %.ronn ,$(detect-all)))
+detect-md = $(filter-out $(detect-ignore-md) ,$(filter %.ronn %.md,$(detect-all)))
+detect-sh = $(filter-out $(detect-ignore-sh) ,$(filter %.sh ,$(detect-all)))
+
+ifeq ($(wildcard $(topsrcdir)/.git),)
+include $(topsrcdir)/.srcversion-libretools.mk
+else
+$(topsrcdir)/.srcversion-libretools.mk: FORCE
+ @{ \
+ echo LIBRETOOLS_VERSION = $(patsubst v%,%,$(shell cd $(topsrcdir) && git describe --tags)); \
+ echo LIBRETOOLS_COMMIT = $(shell cd $(topsrcdir) && git rev-parse HEAD); \
+ :; } | $(write-ifchanged) $@
+-include $(topsrcdir)/.srcversion-libretools.mk
+endif
+
+ifeq ($(wildcard $(devtoolsdir)/.git),)
+include $(topsrcdir)/.srcversion-devtools.mk
+else
+$(topsrcdir)/.srcversion-devtools.mk: FORCE
+ @{ \
+ echo DEVTOOLS_VERSION = $(patsubst libretools-%,%,$(shell cd $(devtoolsdir) && git describe --tags)); \
+ echo DEVTOOLS_COMMIT = $(shell cd $(devtoolsdir) && git rev-parse HEAD); \
+ :; } | $(write-ifchanged) $@
+-include $(topsrcdir)/.srcversion-devtools.mk
+endif
diff --git a/common.once.tail.mk b/common.once.tail.mk
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/common.once.tail.mk
diff --git a/config.mk b/config.mk
new file mode 100644
index 0000000..414f761
--- /dev/null
+++ b/config.mk
@@ -0,0 +1,30 @@
+# Note: In the default version of this file, commented out values
+# indicate what the GNU standards dictate, when our values
+# differ. We're not a GNU package.
+
+ifeq ($(topsrcdir),)
+topsrcdir := $(patsubst %/,%,$(dir $(lastword $(MAKEFILE_LIST))))
+endif
+
+PACKAGE = libretools
+VERSION = $(LIBRETOOLS_VERSION)
+
+DESTDIR =
+
+#prefix = /usr/local
+prefix = /usr
+exec_prefix = $(prefix)
+bindir = $(exec_prefix)/bin
+#libexecdir = $(exec_prefix)/libexec
+libexecdir = $(exec_prefix)/lib
+
+datarootdir = $(prefix)/share
+datadir = $(datarootdir)
+#sysconfdir = $(prefix)/etc
+sysconfdir = /etc
+
+docdir = $(datarootdir)/doc
+mandir = $(datarootdir)/man
+
+devtoolsdir = $(call abspath,$(topsrcdir)/../devtools-par)
+RONNFLAGS = --manual='libretools Manual' --organization='Parabola'
diff --git a/po/.gitignore b/po/.gitignore
new file mode 100644
index 0000000..e3a8c5d
--- /dev/null
+++ b/po/.gitignore
@@ -0,0 +1 @@
+*.pot
diff --git a/src/Makefile b/src/Makefile
new file mode 100644
index 0000000..e1e5cf4
--- /dev/null
+++ b/src/Makefile
@@ -0,0 +1,7 @@
+include $(dir $(lastword $(MAKEFILE_LIST)))/../config.mk
+include $(topsrcdir)/automake.head.mk
+pkgconfdir = $(sysconfdir)
+
+srcfiles-ignore/$(srcdir) = $(addsuffix /%,abslibre-tools chroot-tools devtools gitget lib librefetch toru xbs xbs-abs xbs-abslibre)
+
+include $(topsrcdir)/automake.tail.mk
diff --git a/src/abslibre-tools/Makefile b/src/abslibre-tools/Makefile
new file mode 100644
index 0000000..2903f4a
--- /dev/null
+++ b/src/abslibre-tools/Makefile
@@ -0,0 +1,4 @@
+include $(dir $(lastword $(MAKEFILE_LIST)))/../../config.mk
+include $(topsrcdir)/automake.head.mk
+
+include $(topsrcdir)/automake.tail.mk
diff --git a/src/abslibre-tools/createworkdir b/src/abslibre-tools/createworkdir
new file mode 100755
index 0000000..5599de9
--- /dev/null
+++ b/src/abslibre-tools/createworkdir
@@ -0,0 +1,54 @@
+#!/usr/bin/env bash
+# CreateWorkDir
+# Creates a dir structure for working with Parabola packages
+
+# Copyright (C) 2010-2011 Nicolás Reynolds <fauno@parabola.nu>
+# Copyright (C) 2011 Joshua Ismael Haase Hernández (xihh) <hahj87@gmail.com>
+# Copyright (C) 2013 Luke Shumaker <lukeshu@sbcglobal.net>
+#
+# License: GNU GPLv3+
+#
+# This file is part of Parabola.
+#
+# Parabola is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Parabola is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Parabola. If not, see <http://www.gnu.org/licenses/>.
+
+set -euE
+
+. libremessages
+. "$(librelib conf.sh)"
+load_files libretools
+check_vars libretools WORKDIR ABSLIBRERECV ABSLIBRESEND # these are asked for by `xbs download`
+
+trap 'error "Aborting..."' EXIT
+
+msg "Creating WORKDIR at %s..." "$WORKDIR"
+mkdir -p "$WORKDIR"
+
+msg "Creating staging directory in WORKDIR..."
+mkdir -p "$WORKDIR/staging"
+
+cmd=(xbs -b abslibre download)
+if ! "${cmd[@]}"; then
+ error "Could not clone ABSLibre"
+ plain "Try running this command:"
+ echo
+ printf '%q ' "${cmd[@]}"
+ echo
+ exit 1
+fi
+
+msg "Finished, your packaging directory tree looks like this now:"
+ls --color=auto "${WORKDIR}"/*
+
+trap -- EXIT
diff --git a/src/abslibre-tools/createworkdir.md b/src/abslibre-tools/createworkdir.md
new file mode 100644
index 0000000..e50b00f
--- /dev/null
+++ b/src/abslibre-tools/createworkdir.md
@@ -0,0 +1,36 @@
+# CreateWorkDir
+
+This script recreates a proper directory tree for packaging. Its aim is to help
+you be organized with the work you do as a packager, and establish a certain
+standard for packages' publication, so you don't have to lose much time with
+them. Just package and upload!
+
+It will create a directory tree like this:
+
+ $WORKDIR/
+ ├── abslibre/
+ │ ├── .git/
+ │ ├── libre/<PKGBUILDS>
+ │ └── libre-testing/<PKGBUILDS>
+ └── staging/
+ ├── libre/
+ └── libre-testing/
+
+*Related Variables*
+ - WORKDIR
+
+## staging/
+
+This directory contains one directory for each repository, where the resulting
+packages are in moved for syncing against the main repository on Parabola's
+server. This directory is architecture independent.
+
+## abslibre/
+
+This is the git repo for Parabola's PKGBUILDs. Here you can find the ABS tree
+for our packages, and also where you'll have to put new ones for commit.
+
+(You'll need push access to Parabola's main server, but pulling is public.)
+
+*Related Variables*
+ - ABSLIBREGIT
diff --git a/src/abslibre-tools/diff-unfree b/src/abslibre-tools/diff-unfree
new file mode 100755
index 0000000..12c919f
--- /dev/null
+++ b/src/abslibre-tools/diff-unfree
@@ -0,0 +1,89 @@
+#!/usr/bin/env bash
+# This script will help you diff a *-libre PKGBUILD against the unfree one
+# to check for updates.
+
+# Copyright (C) 2010-2011 Nicolás Reynolds <fauno@parabola.nu>
+# Copyright (C) 2011 Joshua Ismael Haase Hernández (xihh) <hahj87@gmail.com>
+#
+# License: GNU GPLv3+
+#
+# This file is part of Parabola.
+#
+# Parabola is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Parabola is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Parabola. If not, see <http://www.gnu.org/licenses/>.
+
+. libremessages
+. "$(librelib conf.sh)"
+load_files libretools
+check_vars libretools DIFFPROG || exit 1
+
+usage() {
+ print "Usage: %s [community|packages] [unfree-package] [repo]" "${0##*/}"
+ print "Usage: %s --help" "${0##*/}"
+ prose "Helps you diff build scripts from ABSLibre against (Unfree) ABS."
+ echo
+ prose "Package name and repo will we guessed if you don't specify them."
+}
+
+main() {
+ if [[ "$1" == "--help" ]]; then
+ usage
+ exit 0
+ fi
+
+ local package_guess=${PWD##*/}
+ local repo=${1:-$(basename ${PWD%/*})}
+ local package=${2:-${package_guess%-libre}}
+ local trunk=${3:-trunk}
+
+ svnrepo="packages"
+ case $repo in
+ community*) svnrepo="community";;
+ multilib*) svnrepo="community";;
+ *) :;;
+ esac
+
+ if [[ ! -r PKGBUILD ]]; then
+ error "This is not a build dir."
+ exit 1
+ fi
+
+
+ tmp_dir="$(mktemp --tmpdir -d ${package}.XXXXXX)"
+ if [[ ! -d "${tmp_dir}" ]]; then
+ error "Can't create temp dir"
+ exit 1
+ fi
+ unfree_dir="${tmp_dir}/${svnrepo}/${package}/${trunk}"
+
+ pushd "${tmp_dir}" &>/dev/null
+
+ msg "Getting diff from %s..." "$repo/$package"
+
+ svn checkout --depth=empty svn://svn.archlinux.org/$svnrepo &>/dev/null
+
+ cd ${svnrepo}
+ svn update ${package}
+
+ # Back to start dir
+ popd &>/dev/null
+
+ msg "Diffing files"
+
+ for _file in ${unfree_dir}/*; do
+ msg2 "%s" "$(basename "${_file}")"
+ ${DIFFPROG} "$PWD/$(basename "${_file}")" "${_file}"
+ done
+}
+
+main "$@"
diff --git a/src/abslibre-tools/libreaddiff b/src/abslibre-tools/libreaddiff
new file mode 100755
index 0000000..8698920
--- /dev/null
+++ b/src/abslibre-tools/libreaddiff
@@ -0,0 +1,98 @@
+#!/usr/bin/env bash
+# -*- coding: utf-8 -*-
+
+# Copyright (C) 2011-2012 Michał Masłowski <mtjm@mtjm.eu>
+# Copyright (C) 2012 Daniel Molina (lluvia)
+#
+# License: GNU GPLv3+
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+set -e
+
+. libremessages
+. "$(librelib conf.sh)"
+load_files libretools
+check_vars libretools WORKDIR
+
+for arg in "$@" ; do
+ case "$arg" in
+ -h|--h|--he|--hel|--help|-\?)
+ {
+ print 'Usage: %s repo [arch]' "${0##*/}"
+ echo
+ prose "This script outputs a diff of package names and versions
+ in repo between pacman's sync db and abslibre checkout."
+ } >&2
+ exit 0
+ ;;
+ esac
+done
+
+# The repo to find missing packages in.
+repo=$1
+# The arch to check in Arch repos, other will have all arches checked.
+arch=${2:-mips64el}
+# A Python tuple of repos which don't have arch=any packages.
+archrepos='("core", "extra", "community")'
+
+diff -U0 \
+ <( (
+ cd /var/lib/pacman/sync
+ for f in $repo.db ; do
+ tar xOf $f | python -c 'import sys
+arch = None
+name = None
+version = None
+it = iter(sys.stdin)
+try:
+ while True:
+ line = next(it)
+ if line == "%ARCH%\n":
+ arch = next(it)
+ if arch == "'"$arch"'\n" or "'$repo'" not in '"$archrepos"':
+ print("%s-%s" % (name.strip(), version.strip()))
+ if line == "%NAME%\n":
+ name = next(it)
+ if line == "%VERSION%\n":
+ version = next(it)
+except StopIteration:
+ pass
+'
+ done
+ ) | sort ) \
+ <( (
+ cd "${WORKDIR}/abslibre"
+ # Needed to not include pkgnames specific to other arches.
+ CARCH=$arch
+ for f in $repo/* ; do
+ load_PKGBUILD "$f/PKGBUILD" || continue
+ is_here=false
+ for arc in ${arch[@]} ; do
+ if [[ "$arc" == "any" ]] || [[ "$arc" == "$CARCH" ]] ; then
+ is_here=true
+ break
+ fi
+ done
+ if [[ "$is_here" == "true" ]] ; then
+ for name in "${pkgname[@]}" ; do
+ if [[ -z "$epoch" ]] ; then
+ echo $name-$pkgver-$pkgrel
+ else
+ echo $name-$epoch:$pkgver-$pkgrel
+ fi
+ done
+ fi
+ done
+ ) | sort ) | sed -rn 's/^[+-][^+-].+$/&/p'
diff --git a/src/abslibre-tools/libredbdiff b/src/abslibre-tools/libredbdiff
new file mode 100755
index 0000000..09c7ad1
--- /dev/null
+++ b/src/abslibre-tools/libredbdiff
@@ -0,0 +1,360 @@
+#!/usr/bin/env bash
+name="Libredbdiff"
+
+# Copyright (C) 2014 Esteban Carnevale <alfplayer@mailoo.org>
+# Copyright (C) 2014 Luke Shumaker <lukeshu@sbcglobal.net>
+#
+# License: GNU GPLv3+
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+baseconfpath="/etc/libredbdiff"
+basedbpath="/var/lib/libredbdiff"
+
+conffile="$baseconfpath/pacman.conf.parabola"
+conffilearch="$baseconfpath/pacman.conf.archlinux"
+
+dbpath="$basedbpath/pacman.parabola"
+dbpatharch="$basedbpath/pacman.archlinux"
+
+mirrorlist="$baseconfpath/mirrorlist.parabola"
+mirrorlistarch="$baseconfpath/mirrorlist.archlinux"
+
+mirror='http://repo.parabola.nu/$repo/os/$arch'
+mirrorarch='http://mirrors.kernel.org/archlinux/$repo/os/$arch'
+
+repos="libre pcr libre-multilib nonprism"
+
+field_pkgname_parabola=30
+field_pkgname_arch=30
+
+
+. libremessages
+
+cmd="${0##*/}"
+
+arch_packages_tmp="/tmp/${cmd}.arch-packages"
+parabola_packages_tmp="/tmp/${cmd}.parabola-packages"
+
+field_pkgname_total="$((${field_pkgname_parabola} + ${field_pkgname_arch}))"
+printf_format="%s %-${field_pkgname_parabola}s%-${field_pkgname_arch}s %s | %s\n"
+printf_format_noarch="%s %-${field_pkgname_total}s %s\n"
+
+downloadfile() {
+ local outfile=$1
+ local url=$2
+ local mesg=("${@:3}")
+ if [[ ! -e $outfile ]] ; then
+ msg "${mesg[@]}"
+ if wget -q "$url" -O "$outfile"; then
+ return 255
+ else
+ die "Failed to download %q. Exiting." "$outfile"
+ fi
+ elif [[ $init ]]; then
+ warning "%q already exists. Skipping." "$outfile"
+ fi
+}
+
+enablerepo() {
+ repo="$1"
+ conffile_arg="$2"
+ msg2 "Enabling repo %q in %q" "$repo" "${conffile_arg}"
+ sed -i "s/\#\[$repo\]/[$repo]/" "${conffile_arg}"
+ sed -i "\/\[$repo\]/,+1 s/#Include/Include/" "${conffile_arg}"
+}
+
+createdir() {
+ local dir=$1
+ if [[ ! -e $dir ]] ; then
+ msg "Creating directory %q" "$dir"
+ mkdir -- "$1" || die "Failed to create directory %q. Exiting." "$dir"
+ elif [[ $init ]]; then
+ warning "%q already exists. Skipping." "$dir"
+ fi
+}
+
+setmirror() {
+ local distro="$1"
+ local mirror="$2"
+ local mirrorlist="$3"
+ if [[ $init ]] && [[ $mirror ]]; then
+ mirrorescaped="${mirror//./\\.}"
+ mirrorescaped="${mirrorescaped//\$/\\$}"
+ msg2 "Setting %s as the only enabled %s mirror." "${mirror}" "${distro}"
+ sed -i 's|^#\(Server = '"${mirrorescaped}"'\)$|\1|' "${mirrorlist}"
+ fi
+}
+
+filenotfound() {
+ local file=$1
+ if [[ ! -r $1 ]]; then
+ error "Could not read %q." "$file"
+ die "Nothing done. It may be necessary to run %q without \
+arguments as root to initialize %s." "$cmd" "$name"
+ fi
+}
+
+initialize() {
+ createdir "$baseconfpath"
+ createdir "$basedbpath"
+
+ downloadfile \
+ "${conffile}" \
+ "https://projects.parabola.nu/abslibre.git/plain/libre/pacman/pacman.conf.x86_64" \
+ "Downloading Parabola %q" \
+ pacman.conf
+ if [[ $? == 255 ]] ; then
+ msg2 "Setting DBPath in %q" "${conffile}"
+ sed -i "s|^#DBPath .*|DBPath = ${dbpath}|" "${conffile}"
+ enablerepo nonprism "${conffile}"
+ enablerepo pcr "${conffile}"
+ enablerepo libre-multilib "${conffile}"
+ enablerepo multilib "${conffile}"
+ fi
+
+ downloadfile \
+ "${conffilearch}" \
+ "https://projects.archlinux.org/svntogit/packages.git/plain/pacman/trunk/pacman.conf.x86_64" \
+ "Downloading Arch %q" \
+ pacman.conf
+ if [[ $? == 255 ]] ; then
+ msg2 "Setting DBPath in %q" "${conffilearch}"
+ sed -i "s|^#DBPath .*|DBPath = ${dbpatharch}|" "${conffilearch}"
+ msg2 "Setting Arch mirrorlist file in %q" "${conffilearch}"
+ sed -i "s|/etc/pacman\.d/mirrorlist$|$baseconfpath/mirrorlist.archlinux|" \
+ "${conffilearch}"
+ enablerepo multilib "${conffilearch}"
+ fi
+
+ downloadfile \
+ "${mirrorlist}" \
+ "https://repo.parabola.nu/mirrorlist.txt" \
+ "Downloading Parabola %q" \
+ mirrorlist
+ if [[ $? == 255 ]] ; then
+ sed -i 's|^Server|#Server|' "${mirrorlist}"
+ setmirror "Parabola" "$mirror" "$mirrorlist"
+ fi
+
+ downloadfile \
+ "${mirrorlistarch}" \
+ "https://projects.archlinux.org/svntogit/packages.git/plain/pacman-mirrorlist/trunk/mirrorlist" \
+ "Downloading Arch %q" \
+ mirrorlist
+ if [[ $? == 255 ]] ; then
+ setmirror "Arch" "$mirrorarch" "$mirrorlistarch"
+ fi
+}
+
+repo_test() {
+ for repo in ${repos} ; do
+ if [[ $repo == $1 ]] ; then
+ found=1
+ return 0
+ fi
+ done
+ if [[ $found != 1 ]] ; then
+ die "The specified Parabola repo \"%s\" cannot be compared. It's not in the list of repos in the configuration variable \"repos\"." "$1"
+ fi
+}
+
+compare_pkgs() {
+ local cmp
+ if [[ ${verarch[$pkgname]} ]] ; then
+ cmp=$(vercmp "${ver[$pkgname]}" "${verarch[$pkgname]}")
+ if [[ $cmp -lt 0 ]]; then
+ printf "${printf_format}" \
+ '=' \
+ "${pkgname}" \
+ "" \
+ "${ver[$pkgname]}" \
+ "${verarch[$pkgname]}"
+ fi
+ elif [[ ${provides[$pkgname]} ]]; then
+ for provide in "${provides[$pkgname]}"; do
+ if [[ ${verarch["$provide"]} ]]; then
+ cmp=$(vercmp "${ver[$pkgname]}" "${verarch[$provide]}")
+ if [[ $cmp -lt 0 ]]; then
+ printf "${printf_format}" \
+ 'p' \
+ "${pkgname}" \
+ "${provide}" \
+ "${ver[$pkgname]}" \
+ "${verarch[$provide]}"
+ fi
+ fi
+ done
+ else
+ printf "${printf_format_noarch}" \
+ 'o' \
+ "${pkgname}" \
+ "${ver[$pkgname]}"
+ fi
+}
+
+print_cmp() {
+ local repo="$1"
+ awk -F/ -v repo="$repo" \
+ '$1 == repo {print $2}' \
+ ${parabola_packages_tmp} | \
+ while read -a line ; do
+ ver["${line[0]}"]="${line[1]}"
+ provides[${line[0]}]="${line[@]:2}"
+ pkgname=${line[0]}
+ compare_pkgs
+ done
+}
+
+usage() {
+ print "Usage: %q [-n|-h]" "$cmd"
+ print 'Show packages that need to be updated from Arch repositories.'
+ echo
+ prose "Compares packages in Parabola repositories. Packages from
+ all configured Parabola repositories are compared. A Parabola
+ repository name can be specified as argument to compare only
+ packages in that repository."
+ echo
+ prose "The default mode of operation is to download/update all necessary
+ files for comparison, but not compare them. Specify the \`-n\`
+ flag to not download anything, but to compare already downloaded
+ files."
+ echo
+ print 'Options:'
+ flag '-n' "Don't update anything, just compare already downloaded files."
+ flag '-h' 'Show this message'
+ echo
+ print "Output format:"
+ print "type_character parabola_pkgname (arch_pkgname) parabola_pkgver - (arch_pkgver)"
+ echo
+ print "Type characters:"
+ flag '=' "Arch package with the same pkgname and greater pkgver was found"
+ flag 'p' "Arch package with pkgname equal to a provide and greater
+ pkgver was found In this case arch_pkgname is a provide of
+ parabola_pkgname"
+ flag 'o' "No Arch package with the same pkgname or with pkgname equal to
+ a provide was found"
+}
+
+main() {
+ local UPDATE=1
+ local arg
+ local repo_arg
+
+ for arg in "$@"; do
+ case "$arg" in
+ -n|--noupdate)
+ UPDATE=0
+ ;;
+ -h|--help)
+ usage
+ return 0
+ ;;
+ *)
+ repo_test "$arg"
+ repo_arg="$arg"
+ break
+ ;;
+ esac
+ done
+
+ if (( $UPDATE )) ; then
+ if [[ $EUID != 0 ]]; then
+ die "To initialize %s or update %s pacman databases, %s must be run as root (without arguments). Nothing done." \
+ "$name" \
+ "$name" \
+ "$cmd"
+ fi
+
+ if ! [[ -e "${conffile}" && \
+ -e "${conffilearch}" && \
+ -e "${mirrorlist}" && \
+ -e "${mirrorlist}" ]]; then
+ warning "At least one %s configuration file is missing." \
+ "${name}"
+ msg "Downloading and preparing missing configuration files."
+ init=1
+ fi
+
+ createdir "$baseconfpath"
+ createdir "$basedbpath"
+
+ initialize
+
+ if ! [[ -d "$dbpath" && \
+ -d "$dbpatharch" ]]; then
+ warning "At least one %s pacman DB directory is missing. Synchronizing %s DB files." \
+ "${name}" "${name}"
+ fi
+
+ createdir "$dbpath"
+ msg "Synchronizing %s pacman databases for Parabola" "$name"
+ pacman --config "${conffile}" -Sy ||
+ die "Failed to synchronize pacman database for Parabola. Exiting."
+
+ createdir "$dbpatharch"
+ msg "Synchronizing %s pacman databases for Arch" "$name"
+ pacman --config "${conffilearch}" -b "${dbpatharch}" -Sy ||
+ die "Failed to synchronize pacman database for Arch. Exiting."
+
+ msg "%s pacman databases are updated. %s is ready. Run %q -n to print results." \
+ "$name" "$name" "$cmd"
+ return 0
+ else
+ filenotfound "${dbpath}"
+ filenotfound "${dbpatharch}"
+ filenotfound "${conffile}"
+ filenotfound "${conffilearch}"
+ filenotfound "${mirrorlist}"
+ filenotfound "${mirrorlistarch}"
+
+ unset provides ver verarch
+ declare -gA provides ver verarch
+
+ if ! [[ -d "$dbpath" && \
+ -d "$dbpatharch" ]]; then
+ die "At least one %s pacman DB directory is missing. To update %s pacman databases, %s must be run as root. Nothing done." \
+ "$name" \
+ "$name" \
+ "$cmd"
+ fi
+
+ pacman --config "${conffilearch}" -Ss | \
+ grep -v '^ ' | \
+ awk -F/ '{print $2}' \
+ > ${arch_packages_tmp} || \
+ die "pacman command to get Arch package data has failed. Exiting."
+ chmod 777 ${arch_packages_tmp}
+
+ while read -a line; do
+ verarch["${line[0]}"]="${line[1]}"
+ done < ${arch_packages_tmp}
+
+ expac --config "${conffile}" -S '%r/%n %v %S' \
+ > ${parabola_packages_tmp} || \
+ die "expac command to get Parabola package data has failed. Exiting."
+ chmod 777 ${parabola_packages_tmp}
+
+ if [[ ${repo_arg} ]] ; then
+ print_cmp "${repo_arg}"
+ else
+ for repo in ${repos} ; do
+ echo "[$repo]"
+ print_cmp "$repo"
+ done
+ fi
+ fi
+}
+
+main "$@"
diff --git a/src/abslibre-tools/librerelease b/src/abslibre-tools/librerelease
new file mode 100755
index 0000000..b7a77d3
--- /dev/null
+++ b/src/abslibre-tools/librerelease
@@ -0,0 +1,257 @@
+#!/usr/bin/env bash
+# Librerelease
+# Uploads packages and releases them
+
+# Copyright (C) 2010-2012 Joshua Ismael Haase Hernández (xihh) <hahj87@gmail.com>
+# Copyright (C) 2010-2013 Nicolás Reynolds <fauno@parabola.nu>
+# Copyright (C) 2013 Michał Masłowski <mtjm@mtjm.eu>
+# Copyright (C) 2013-2014 Luke Shumaker <lukeshu@sbcglobal.net>
+#
+# For just the create_signature() function:
+# Copyright (C) 2006-2013 Pacman Development Team <pacman-dev@archlinux.org>
+# Copyright (C) 2002-2006 Judd Vinet <jvinet@zeroflux.org>
+# Copyright (C) 2005 Aurelien Foret <orelien@chez.com>
+# Copyright (C) 2006 Miklos Vajna <vmiklos@frugalware.org>
+# Copyright (C) 2005 Christian Hamar <krics@linuxforum.hu>
+# Copyright (C) 2006 Alex Smith <alex@alex-smith.me.uk>
+# Copyright (C) 2006 Andras Voroskoi <voroskoi@frugalware.org>
+#
+# License: GNU GPLv3+
+#
+# This file is part of Parabola.
+#
+# Parabola is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Parabola is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Parabola. If not, see <http://www.gnu.org/licenses/>.
+
+# create_signature() is taken from pacman:makepkg, which is GPLv2+,
+# so we take the '+' to combine it with our GPLv3+.
+
+. libremessages
+. "$(librelib conf.sh)"
+
+dryrun=""
+upload_only=false
+readonly rsync_flags=(
+ --no-group
+ --no-perms
+ --copy-links
+ --hard-links
+ --partial
+ --human-readable
+ --progress
+ -e ssh
+)
+
+# Functions ####################################################################
+
+list0_files() {
+ find -L "${WORKDIR}/staging" -type f -not -name '*.lock' \
+ -exec realpath -z --relative-to="${WORKDIR}/staging" {} +
+}
+
+# This function is taken almost verbatim from makepkg
+create_signature() {
+ local ret=0
+ local filename="$1"
+ msg "Signing package..."
+
+ local SIGNWITHKEY=()
+ if [[ -n $GPGKEY ]]; then
+ SIGNWITHKEY=(-u "${GPGKEY}")
+ fi
+ # The signature will be generated directly in ascii-friendly format
+ gpg --detach-sign --use-agent "${SIGNWITHKEY[@]}" "$filename" || ret=$?
+
+
+ if (( ! ret )); then
+ msg2 "Created signature file %s." "$filename.sig"
+ else
+ error "Failed to sign package file."
+ return $ret
+ fi
+}
+
+sign_packages() {
+ IFS=$'\n'
+ local files=($(find "${WORKDIR}/staging/" \
+ \( -type d -name "${ABSLIBREDEST##*/}" \) -prune \
+ -o \( -type f -not -iname '*.sig' \) -print))
+ local file
+ for file in "${files[@]}"; do
+ if [[ -f "${file}.sig" ]]; then
+ msg2 "File signature found, verifying..."
+
+ # Verify that the signature is correct, else remove for re-signing
+ if ! gpg --quiet --verify "${file}.sig" >/dev/null 2>&1; then
+ error "Failed! Re-signing..."
+ rm -f "${file}.sig"
+ fi
+ fi
+
+ if ! [[ -f "${file}.sig" ]]; then
+ create_signature "$file" || return 2
+ fi
+ done
+}
+
+# Clean everything if not in dry-run mode
+clean_files() {
+ local file_list=$1
+
+ local rmcmd=(rm -fv)
+ if [[ -n "${dryrun}" ]]; then
+ rmcmd=(printf "$(_ "removed '%s' (dry-run)")\n")
+ fi
+
+ msg "Removing files from local staging directory"
+ cd "${WORKDIR}/staging" && xargs -0r -a "$file_list" "${rmcmd[@]}"
+ cd "${WORKDIR}/staging" && find . -mindepth 1 -type d -empty \
+ -exec rmdir -p {} + 2>/dev/null
+}
+
+################################################################################
+
+usage() {
+ print "Usage: %s [OPTIONS]" "${0##*/}"
+ echo
+ prose 'This script uploads packages on $WORKDIR/staging
+ to the Parabola server.'
+ echo
+ print "Options:"
+ flag '-c' 'Clean; delete packages in $WORKDIR/staging'
+ flag '-l' "List; list packages but not upload them"
+ flag '-u' "Upload-only; do not run db-update on the server"
+
+ flag '-n' "Dry-run; don't actually do anything"
+ flag '-h' "Show this message"
+}
+
+main() {
+ if [[ -w / ]]; then
+ error "This program should be run as regular user"
+ return 1
+ fi
+
+ # Parse options
+ local mode="release_packages"
+ while getopts 'clunh' arg; do
+ case $arg in
+ c) mode=clean ;;
+ l) mode=pretty_print_packages ;;
+ u) upload_only=true ;;
+ n) dryrun="--dry-run" ;;
+ h) mode=usage ;;
+ *) usage >&2; return 1 ;;
+ esac
+ done
+ shift $(($OPTIND - 1))
+ if [[ $# != 0 ]]; then
+ usage >&2
+ return 1
+ fi
+
+ if [[ $mode == usage ]]; then
+ usage
+ return 0
+ fi
+
+ load_files makepkg
+ check_vars makepkg GPGKEY
+ load_files libretools
+ check_vars libretools WORKDIR REPODEST ABSLIBREDEST || return 1
+ REPODEST+='/staging/'
+ # The following settings are actually optional
+ #check_vars libretools HOOKPRERELEASE HOOKPOSTRELEASE || return 1
+
+ "$mode"
+}
+
+# The different modes (sans 'usage') ###########################################
+
+pretty_print_packages() {
+ find "$WORKDIR/staging/" -mindepth 1 -maxdepth 1 -type d -not -empty | sort |
+ while read -r path; do
+ msg2 "${path##*/}"
+ cd "$path"
+ find -L . -type f -not -name '*.lock' | sed 's|^\./| |' | sort
+ done
+}
+
+clean() {
+ lock 8 "${WORKDIR}/staging.lock" \
+ 'Waiting for an exclusive lock on the staging directory'
+
+ local file_list="$(mktemp -t ${0##*/}.XXXXXXXXXX)"
+ trap "$(printf 'rm -f -- %q' "$file_list")" EXIT
+ list0_files > "$file_list"
+
+ lock_close 8
+
+ clean_files "$file_list"
+}
+
+release_packages() {
+ if [[ -n $HOOKPRERELEASE ]]; then
+ msg "Running HOOKPRERELEASE..."
+ plain '%s' "${HOOKPRERELEASE}"
+ bash -c "${HOOKPRERELEASE}"
+ fi
+
+ lock 8 "${WORKDIR}/staging.lock" \
+ 'Waiting for an exclusive lock on the staging directory'
+
+ sign_packages || return 1
+
+ # Make the permissions of the packages 644 otherwise the user will get access
+ # denied error when they try to download (rsync --no-perms doesn't seem to
+ # work).
+ find "${WORKDIR}/staging" -type f -exec chmod 644 {} +
+ find "${WORKDIR}/staging" -type d -exec chmod 755 {} +
+
+ local file_list="$(mktemp -t ${0##*/}.XXXXXXXXXX)"
+ trap "$(printf 'rm -f -- %q' "$file_list")" EXIT
+ list0_files > "$file_list"
+
+ lock_close 8
+
+ msg "%s to upload" "$(cd "${WORKDIR}/staging" && du -hc --files0-from="$file_list" | sed -n '$s/\t.*//p')"
+ msg "Uploading packages..."
+ xargs -0r -a "$file_list" dirname -z | ssh "${REPODEST%%:*}" "$(printf 'mkdir -p -- %q && cd %q && xargs -0r mkdir -pv --' "${REPODEST#*:}"{,})"
+ if ! rsync ${dryrun} "${rsync_flags[@]}" \
+ -0 --files-from="$file_list" \
+ "${WORKDIR}/staging" \
+ "${REPODEST}/"
+ then
+ error "Sync failed, try again"
+ return 1
+ fi
+
+ clean_files "$file_list"
+
+ if $upload_only; then
+ return 0
+ fi
+
+ msg "Running db-update on repos"
+ ssh "${REPODEST%%:*}" "$(printf 'STAGING=%q dbscripts/db-update' "${REPODEST#*:}")"
+
+ if [[ -n $HOOKPOSTRELEASE ]]; then
+ msg "Running HOOKPOSTRELEASE..."
+ plain '%s' "${HOOKPOSTRELEASE}"
+ bash -c "${HOOKPOSTRELEASE}"
+ fi
+
+ return 0
+}
+
+main "$@"
diff --git a/src/abslibre-tools/librestage b/src/abslibre-tools/librestage
new file mode 100755
index 0000000..39523ac
--- /dev/null
+++ b/src/abslibre-tools/librestage
@@ -0,0 +1,154 @@
+#!/usr/bin/env bash
+# LibreStage
+# Prepares packages for upload
+
+# Copyright (C) 2010-2012 Nicolás Reynolds <fauno@parabola.nu>
+# Copyright (C) 2011 Joshua Ismael Haase Hernández (xihh) <hahj87@gmail.com>
+# Copyright (C) 2013-2014 Luke Shumaker <lukeshu@sbcglobal.net>
+#
+# License: GNU GPLv3+
+#
+# This file is part of Parabola.
+#
+# Parabola is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Parabola is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Parabola. If not, see <http://www.gnu.org/licenses/>.
+
+. libremessages
+. "$(librelib conf.sh)"
+
+usage() {
+ print "Usage: %s [REPO]" "${0##*/}"
+ print "Stages the package(s) build by ./PKGBUILD for upload."
+ echo
+ prose "The package(s) are staged for the named repository, or the name
+ of the parent directory if a repository is not named."
+}
+
+main() {
+ if [[ -w / ]]; then
+ error "This program should be run as a regular user"
+ return 1
+ fi
+
+ # Parse options, set up
+ while getopts 'h' arg; do
+ case $arg in
+ h) usage; return 0;;
+ *) usage >&2; return 1;;
+ esac
+ done
+ local repo=
+ case $# in
+ 0) repo="$(basename "$(dirname "$PWD")")";;
+ 1) repo=$1;;
+ *) usage >&2; return 1;;
+ esac
+
+ if ! [[ -e ./PKGBUILD ]]; then
+ error "PKGBUILD not found"
+ return 1
+ fi
+
+ if ! xbs -b abslibre status; then
+ error "There are uncommitted changes in the current directory"
+ return 1
+ fi
+
+ # Load configuration
+ load_files libretools
+ # ABSLIBREDEST is used by xbs release-client
+ check_vars libretools WORKDIR ARCHES ABSLIBREDEST || return 1
+ load_files makepkg # for PKGDEST and SRCDEST, which are optional
+ load_files librefetch # for MIRRORS, which is optional
+
+ # Load the PKGBUILD
+ load_PKGBUILD
+
+ # Now for the main routine.
+ local staged=false
+ slock 8 "${WORKDIR}/staging.lock" \
+ 'Waiting for a shared lock on the staging directory'
+
+ # Look for makepkg output
+ local CARCH _pkgname pkgfile
+ for CARCH in "${ARCHES[@]}" any; do
+ for _pkgname in "${pkgname[@]}" "${pkgname[@]/%/-debug}"; do
+ if ! pkgfile=$(find_cached_package "$_pkgname" "$(get_full_version "$_pkgname")" "$CARCH"); then
+ continue
+ fi
+
+ msg 'Found package: %s' "${pkgfile##*/}"
+
+ # This little check is from devtools:commitpkg
+ if grep -q "packager = Unknown Packager" <(bsdtar -xOqf "$pkgfile" .PKGINFO); then
+ die "PACKAGER wes not set when building package"
+ fi
+
+ xbs -b abslibre release-client "$repo" "$CARCH"
+ mkdir -p "${WORKDIR}/staging/${repo}"
+ if cp "$pkgfile" "${WORKDIR}/staging/${repo}/${pkgfile##*/}"; then
+ msg2 "%s staged on [%s]" "$_pkgname" "$repo"
+ staged=true
+ else
+ error "Can't put %s on [%s]" "$_pkgname" "$repo"
+ return 1
+ fi
+ done
+ done
+
+ # Look for librefetch output
+ local netfile mirror path
+ local srcurl srcname srcpath
+ for netfile in "${source[@]}"; do
+ for mirror in "${MIRRORS[@]}"; do
+ srcurl=${netfile#*::}
+ if [[ "$srcurl" == "$mirror"* ]]; then
+ if [[ $netfile = *::* ]]; then
+ srcname=${netfile%%::*}
+ else
+ srcname=${netfile##*/}
+ fi
+
+ srcpath=''
+ for path in "./$srcname" "${SRCDEST:-.}/$srcname"; do
+ if [[ -f "$path" ]]; then
+ srcpath="$path"
+ break
+ fi
+ done
+ if [[ -n "$srcpath" ]]; then
+ msg "Found generated source file: %s" "$srcname"
+ local dest="${WORKDIR}/staging/other/${srcurl##"$mirror"}"
+ mkdir -p -- "${dest%/*}"
+ if cp "$srcpath" "$dest"; then
+ msg2 "%s staged on [%s]" "$srcname" other
+ staged=true
+ else
+ error "Can't put %s on [%s]" "$srcname" other
+ return 1
+ fi
+ fi
+ break
+ fi
+ done
+ done
+
+ if $staged ; then
+ return 0
+ else
+ error "Nothing was staged"
+ return 1
+ fi
+}
+
+main "$@"
diff --git a/src/aur b/src/aur
new file mode 100755
index 0000000..49c79b9
--- /dev/null
+++ b/src/aur
@@ -0,0 +1,146 @@
+#!/usr/bin/env bash
+# Copyright (C) 2010-2011 Joshua Ismael Haase Hernández (xihh) <hahj87@gmail.com>
+# Copyright (C) 2010-2012 Nicolás Reynolds <fauno@parabola.nu>
+# Copyright (C) 2012-2014 Luke Shumaker <lukeshu@sbcglobal.net>
+#
+# License: GNU GPLv3+
+#
+# This file is part of Parabola.
+#
+# Parabola is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Parabola is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Parabola. If not, see <http://www.gnu.org/licenses/>.
+
+. libremessages
+
+usage() {
+ print "Usage: %s [-h] PKGNAME [PKGNAME2 PKGNAME3...]" "${0##*/}"
+ print "Downloads packages from the AUR, and does basic freedom checks."
+ echo
+ prose "This script will download packages from AUR to the current
+ directory and check their license for nonfree issues. This does
+ not mean that they are free; they may be incorrectly labeled, or
+ have other freedom issues. It's a tool to help Parabola
+ packagers, not to help users install things directly from AUR."
+}
+
+main() {
+ while getopts 'h' arg; do
+ case $arg in
+ h) usage; return 0;;
+ *) usage >&2; return 1;;
+ esac
+ done
+ if [[ $# -lt 1 ]]; then
+ usage >&2
+ return 1
+ fi
+
+ . "$(librelib conf.sh)"
+ load_files libretools
+ check_vars libretools DIFFPROG || exit 1
+
+ local startdir="$(pwd)"
+ local missing_deps=()
+ local ret=0
+ local pkg
+ local copy_new
+ local copy_old
+ for pkg in "$@"; do
+ pkg="${pkg%%[<>=]*}" # remove the version
+ msg "Processing package: %s" "$pkg"
+ copy_new="$startdir/$pkg"
+ copy_old=
+
+ if [[ -f "${copy_new}/PKGBUILD" ]]; then
+ warning "%s already exists, will compare with new version." "$pkg"
+
+ # Store our copy of the PKGBUILD dir
+ copy_old=$copy_new
+ copy_new="$(mktemp --tmpdir -d aur-${pkg}.new.XXXX)/$pkg"
+ cd "${copy_new%/*}"
+ fi
+
+ msg2 "Downloading"
+ local url="https://aur.archlinux.org/packages/${pkg:0:2}/$pkg/$pkg.tar.gz"
+ set -o pipefail
+ if ! wget -O- -q "$url" | bsdtar xf -; then
+ ret=$(($ret|2))
+ error "Couldn't get %s" "$pkg"
+ continue
+ fi
+ set +o pipefail
+
+ if [[ -n $copy_old ]]; then
+ msg2 "Diffing files"
+ cd "$copy_new"
+
+ # Diff all files with our difftool
+ local diffed=false
+ for file in *; do
+ if ! cmp -s "${copy_old}/${file}" "${copy_new}/${file}" ; then
+ warning "%s != %s" "${copy_old}/${file}" "${copy_new}/${file}"
+ diffed=true
+ "${DIFFPROG}" "${copy_old}/${file}" "${copy_new}/${file}"
+ fi
+ done
+ if $diffed; then
+ read -p "Press enter to continue."
+ fi
+
+ # Go back to our copy to continue working
+ cd "$copy_old"
+ rm -rf -- "${copy_new%/*}"
+ else
+ cd "$copy_new"
+ fi
+
+ load_PKGBUILD
+
+ ################################################################
+
+ pkgbuild-check-nonfree -c
+ pkgbuild-summarize-nonfree $?
+
+ ################################################################
+
+ local _deps=(
+ # depends
+ "${depends[@]}" "${makedepends[@]}" "${checkdepends[@]}"
+ # mksource depends
+ "${mkdepends[@]}"
+ )
+ local _dep
+ msg2 "Checking dependencies"
+ for _dep in "${_deps[@]}"; do
+ _dep=${_dep/[<>=]*/}
+ if ! is_built $_dep; then
+ if ! pacman -Sddp "$_dep" &>/dev/null ; then
+ plain "%s: will be downloaded from AUR" "$_dep"
+ missing_deps+=($_dep)
+ fi
+ else
+ plain "%s: is on repos" "$_dep"
+ fi
+ done
+ cd "$startdir"
+ done
+
+ if [[ ${#missing_deps[*]} -gt 0 ]]; then
+ msg "Retrieving missing deps: %s" "${missing_deps[*]}"
+ "$0" "${missing_deps[@]}"
+ ret=$(($ret|$?))
+ fi
+ return $ret;
+}
+
+main "$@"
diff --git a/src/chroot-tools/.gitignore b/src/chroot-tools/.gitignore
new file mode 100644
index 0000000..f0969c7
--- /dev/null
+++ b/src/chroot-tools/.gitignore
@@ -0,0 +1,7 @@
+makechrootpkg.sh*
+arch-nspawn*
+mkarchroot*
+!*.patch
+
+chcleanup
+chcleanup.lib
diff --git a/src/chroot-tools/HACKING.md b/src/chroot-tools/HACKING.md
new file mode 100644
index 0000000..e50aa11
--- /dev/null
+++ b/src/chroot-tools/HACKING.md
@@ -0,0 +1,3 @@
+Unfortunately, `makechrootpkg.sh` is GPLv2 ONLY. This means that
+everything that uses it must be held to GPLv2+ instead of GPLv3+. I'm
+calling this anything that gets loaded into the same process as it.
diff --git a/src/chroot-tools/Makefile b/src/chroot-tools/Makefile
new file mode 100644
index 0000000..48a1631
--- /dev/null
+++ b/src/chroot-tools/Makefile
@@ -0,0 +1,27 @@
+include $(dir $(lastword $(MAKEFILE_LIST)))/../../config.mk
+include $(topsrcdir)/automake.head.mk
+pkglibexecdir = $(libexecdir)/libretools/chroot
+
+libretools-bins = chcleanup librechroot libremakepkg
+libretools-libexecs += arch-nspawn chcleanup distcc-tool indent mkarchroot
+libretools-libs += makechrootpkg.sh
+devtools-files = makechrootpkg.sh.in mkarchroot.in arch-nspawn.in
+am_clean_files += chcleanup.lib
+
+$(srcdir)/makechrootpkg.sh.in: $(srcdir)/%.sh.in: $(devtoolsdir)/%.in
+ cp $< $@
+
+$(outdir)/chcleanup: $(srcdir)/chcleanup.in $(outdir)/chcleanup.lib
+ m4 -I$(@D) -P $< | $(edit) | install -m755 /dev/stdin $@
+$(outdir)/chcleanup.lib: $(call _am_path,$(topoutdir)/src/lib/common.sh) $(outdir)/Makefile
+ bash -c '. $<; declare -f _l plain msg msg2 error' > $@
+
+$(outdir)/distcc-tool.pot: LIBREXGETTEXT_FLAGS+=--simple=errusage
+
+$(DESTDIR)$(bindir)/chcleanup: $(var)bindir $(var)libexecdir
+ mkdir -p $(@D)
+ ln -srfT $(DESTDIR)$(libexecdir)/libretools/chroot/chcleanup $@
+
+am_depdirs = ../lib
+
+include $(topsrcdir)/automake.tail.mk
diff --git a/src/chroot-tools/arch-nspawn.patch b/src/chroot-tools/arch-nspawn.patch
new file mode 100644
index 0000000..dc4cb25
--- /dev/null
+++ b/src/chroot-tools/arch-nspawn.patch
@@ -0,0 +1,71 @@
+--- arch-nspawn.in 2016-05-10 13:48:14.303797115 -0400
++++ arch-nspawn.ugly 2016-05-10 13:48:23.296957958 -0400
+@@ -1,4 +1,6 @@
+ #!/bin/bash
++# License: GNU GPLv2
++#
+ # This program is free software; you can redistribute it and/or modify
+ # it under the terms of the GNU General Public License as published by
+ # the Free Software Foundation; version 2 of the License.
+@@ -14,6 +16,8 @@
+
+ working_dir=''
+
++files=()
++
+ usage() {
+ echo "Usage: ${0##*/} [options] working-dir [systemd-nspawn arguments]"
+ echo "A wrapper around systemd-nspawn. Provides support for pacman."
+@@ -22,17 +26,21 @@
+ echo ' -C <file> Location of a pacman config file'
+ echo ' -M <file> Location of a makepkg config file'
+ echo ' -c <dir> Set pacman cache'
++ echo ' -f <file> Copy file from the host to the chroot'
++ echo ' -s Do not run setarch'
+ echo ' -h This message'
+ exit 1
+ }
+
+ orig_argv=("$@")
+
+-while getopts 'hC:M:c:' arg; do
++while getopts 'hC:M:c:f:s' arg; do
+ case "$arg" in
+ C) pac_conf="$OPTARG" ;;
+ M) makepkg_conf="$OPTARG" ;;
+ c) cache_dir="$OPTARG" ;;
++ f) files+=("$OPTARG") ;;
++ s) nosetarch=1 ;;
+ h|?) usage ;;
+ *) error "invalid argument '%s'" "$arg"; usage ;;
+ esac
+@@ -78,6 +86,12 @@
+ [[ -n $pac_conf ]] && cp $pac_conf "$working_dir/etc/pacman.conf"
+ [[ -n $makepkg_conf ]] && cp $makepkg_conf "$working_dir/etc/makepkg.conf"
+
++ local file
++ for file in "${files[@]}"; do
++ mkdir -p "$(dirname "$working_dir$file")"
++ cp -T "$file" "$working_dir$file"
++ done
++
+ sed -r "s|^#?\\s*CacheDir.+|CacheDir = $(echo -n ${cache_dirs[@]})|g" -i "$working_dir/etc/pacman.conf"
+ }
+ # }}}
+@@ -92,6 +106,7 @@
+ fi
+
+ build_mount_args
++cache_dirs+=('/repo/')
+ copy_hostconf
+
+ eval $(grep '^CARCH=' "$working_dir/etc/makepkg.conf")
+@@ -99,6 +114,8 @@
+ armv7h) CARCH=armv7l;;
+ esac
+
++[[ -z $nosetarch ]] || unset CARCH
++
+ exec ${CARCH:+setarch "$CARCH"} systemd-nspawn -q \
+ -D "$working_dir" \
+ --register=no \
diff --git a/src/chroot-tools/chcleanup.in b/src/chroot-tools/chcleanup.in
new file mode 100644
index 0000000..04cf8ad
--- /dev/null
+++ b/src/chroot-tools/chcleanup.in
@@ -0,0 +1,102 @@
+#!/usr/bin/env bash
+# Copyright (C) 2011-2012 Nicolás Reynolds <fauno@parabola.nu>
+# Copyright (C) 2012-2013, 2015 Luke Shumaker <lukeshu@sbcglobal.net>
+#
+# If you don't see m4_include(...) below, but see function definitions
+# for msg() et al., then this is a generated file, and contains some
+# code from librelib. The the source distribution for full copyright
+# information.
+#
+# License: GNU GPLv3+
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+# Performs chroot cleanup smartly, it only removes the unneeded packages or
+# leaves you with a cleansystem
+#
+# See: HOOKPREBUILD
+
+set -eE
+
+DRYRUN=${DRYRUN:-false}
+
+# Statically include various library routines to avoid having
+# dependencies on outside files.
+[[ -n ${TEXTDOMAIN:-} ]] || export TEXTDOMAIN='libretools'
+[[ -n ${TEXTDOMAINDIR:-} ]] || export TEXTDOMAINDIR='/usr/share/locale'
+
+if type gettext &>/dev/null; then
+ _() { gettext "$@"; }
+else
+ _() { echo "$@"; }
+fi
+# Begin chcleanup.lib ##########################################################
+m4_include(chcleanup.lib)
+# End chcleanup.lib ############################################################
+
+if [[ ! -f /.arch-chroot ]] && ! ${DRYRUN}; then
+ error "(chcleanup): Must be run inside of a chroot"
+ exit 1
+fi
+
+# Note: the in-chroot pkgconfdir is non-configurable, this is
+# intentionally hard-coded.
+source /etc/libretools.d/chroot.conf
+# If we're running makepkg
+if [[ -f PKGBUILD ]]; then
+ export CARCH="$(. /etc/makepkg.conf; printf '%s' "$CARCH")"
+ source ./PKGBUILD
+ CHROOTEXTRAPKG+=("${depends[@]}"
+ "${makedepends[@]}"
+ "${checkdepends[@]}")
+fi
+
+msg "Cleaning chroot..."
+
+# Sync the local repo with pacman
+cp /repo/repo.db /var/lib/pacman/sync/repo.db
+
+# Setup the temporary directory
+TEMPDIR="$(mktemp --tmpdir -d ${0##*/}.XXXXXXXXXX)"
+trap "rm -rf -- $(printf '%q' "$TEMPDIR")" EXIT
+
+cp -a /var/lib/pacman/sync "${TEMPDIR}/"
+pkglist="${TEMPDIR}"/pkglist.txt
+
+# Get the full list of packages needed by dependencies, including the base system
+msg2 "Creating a full list of packages..."
+pacman -b "${TEMPDIR}" \
+ -Sp --print-format "%n" base-devel "${CHROOTEXTRAPKG[@]}" >"$pkglist" || {
+ ret=$?
+ error "Could not create a full list of packages, exiting."
+ plain "This is likely caused by a dependency that could not be found."
+ exit $ret
+}
+
+# Diff installed packages against a clean chroot then remove leftovers
+packages=($(comm -23 <(pacman -Qq | sort -u) \
+ <(sort -u "${pkglist}")))
+
+if [[ ${#packages[@]} = 0 ]]; then
+ msg2 "No packages to remove"
+else
+ msg2 "Removing %d packages" ${#packages[@]}
+
+ if ${DRYRUN}; then
+ echo "${packages[*]}"
+ else
+ # Only remove leftovers, -Rcs removes too much
+ pacman --noconfirm -Rn "${packages[@]}"
+ fi
+fi
diff --git a/src/chroot-tools/chroot.conf b/src/chroot-tools/chroot.conf
new file mode 100644
index 0000000..3b3c445
--- /dev/null
+++ b/src/chroot-tools/chroot.conf
@@ -0,0 +1,13 @@
+# The full path to the chroot is
+# $CHROOTDIR/$CHROOT/$COPY
+# where $COPY is set at runtime.
+# See `librechroot help` for details.
+CHROOTDIR=/var/lib/archbuild
+CHROOT=default
+
+# Extra packages to have installed on the chroot.
+# This is in addition to CHROOTPKG=(base-devel)
+CHROOTEXTRAPKG=()
+#CHROOTEXTRAPKG+=(distcc-nozeroconf socat) # for BUILDENV+=(distcc)
+#CHROOTEXTRAPKG+=(ccache) # for BUILDENV+=(ccache)
+#CHROOTEXTRAPKG+=(libretools) # for running libremakepkg inside the chroot
diff --git a/src/chroot-tools/distcc-tool b/src/chroot-tools/distcc-tool
new file mode 100755
index 0000000..d181109
--- /dev/null
+++ b/src/chroot-tools/distcc-tool
@@ -0,0 +1,247 @@
+#!/usr/bin/env bash
+# -*- tab-width: 4; sh-basic-offset: 4 -*-
+# distcc-tool
+
+# Copyright (C) 2013-2014 Luke Shumaker <lukeshu@sbcglobal.net>
+#
+# License: GNU GPLv3+
+#
+# This file is part of Parabola.
+#
+# Parabola is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Parabola is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Parabola. If not, see <http://www.gnu.org/licenses/>.
+
+# This program has very few dependencies:
+# - bash: I don't know what version, I use fairly modern features
+# - socat
+# - cat: any version
+# - rm: any version
+# - sed: any version
+# On Parabola, this means the packages:
+# bash, coreutils, sed, socat
+
+if ! type gettext &>/dev/null; then
+ gettext() { echo "$@"; }
+fi
+
+q0="$(printf '%q' "$0")" # quoted $0
+
+panic() {
+ echo "$(gettext 'panic: malformed call to internal function')" >&2
+ exit 1
+}
+
+error() {
+ mesg="$(gettext "$1")"; shift
+ printf "$(gettext 'ERROR:') $mesg\n" "$@" >&2
+ exit 1
+}
+
+print() {
+ local mesg=$1
+ shift
+ printf -- "$(gettext "$mesg")\n" "$@"
+}
+
+usage() {
+ print "Usage: %s COMMAND [COMMAND-ARGS]" "$q0"
+ print "Tool for using distcc within a networkless chroot"
+ echo
+ print "Commands:"
+ print ' help print this message'
+ print ' odaemon CHROOTPATH daemon to run outside of the chroot'
+ print ' idaemon DISTCC_HOSTS daemon to run inside of the chroot'
+ print ' rewrite DISTCC_HOSTS prints a rewritten version of DISTCC_HOSTS'
+ print ' client HOST PORT connects stdio to TCP:$HOST:$PORT'
+ print 'Commands: for internal use'
+ print ' server counterpart to client; spawned by odaemon'
+}
+
+errusage() {
+ if [[ $# -gt 0 ]]; then
+ fmt="$(gettext "$1")"; shift
+ printf "$(gettext 'ERROR:') $fmt\n" "$@" >&2
+ fi
+ usage >&2
+ exit 1
+}
+
+main() {
+ local cmd=$1
+ shift
+ case "$cmd" in
+ help)
+ [[ $# -eq 0 ]] || errusage '%s: invalid number of arguments' "$cmd"
+ usage;;
+ odaemon|idaemon|rewrite)
+ [[ $# -eq 1 ]] || errusage '%s: invalid number of arguments' "$cmd"
+ $cmd "$@";;
+ client)
+ [[ $# -eq 2 ]] || errusage '%s: invalid number of arguments' "$cmd"
+ $cmd "$@";;
+ server)
+ [[ $# -eq 0 ]] || errusage '%s: invalid number of arguments' "$cmd"
+ $cmd "$@";;
+ *) errusage 'unknown subcommand: %s' "$cmd";;
+ esac
+}
+
+################################################################################
+# DISTCC_HOSTS parser #
+################################################################################
+
+# usage: parse_DISTCC_HOSTS true|false DISTCC_HOSTS
+# parses DISTCC_HOSTS and:
+# $1==true : It sets up port forwarding for inside the choot, sleep forever
+# $1==false: Prints a modified version of DISTCC_HOSTS that uses the forwarded
+# ports that were set up when $1==true.
+parse_DISTCC_HOSTS() {
+ { [[ $# -eq 2 ]] && { [[ $1 == true ]] || [[ $1 == false ]]; }; } || panic
+ local forward_ports=$1
+ local DISTCC_HOSTS=$2
+
+ local pids=() # child pids
+ local newhosts=()
+ local newport=8000 # next port to be used for port forwarding
+
+ # This is based on the grammar specified in distcc(1)
+ local HOSTSPEC
+ for HOSTSPEC in $(sed 's/#.*//' <<<"$DISTCC_HOSTS"); do
+ case "$HOSTSPEC" in
+ # LOCAL_HOST
+ localhost|localhost/*|--localslots=*|--localslots_cpp=*)
+ # "localhost" runs commands directly, not talking to distccd at
+ # localhost, use an IP or real hostname for that.
+ # So, just pass these through.
+ newhosts+=("$HOSTSPEC")
+ ;;
+ # SSH_HOST
+ *@*)
+ # SSH_HOST doesn't allow custom port numbers, and even if it
+ # did, ssh would complain about MITM. Instead, we'll count on
+ # ssh ProxyCommand being configured to use `client`.
+ newhosts+=("$HOSTSPEC")
+ ;;
+ # GLOBAL_OPTION
+ --*)
+ # pass these through
+ newhosts+=("$HOSTSPEC")
+ ;;
+ # ZEROCONF
+ +zeroconf)
+ error "%s does not support the +zeroconf option" "$q0"
+ exit 1
+ ;;
+ # TCP_HOST or OLDSTYLE_TCP_HOST
+ *)
+ declare HOSTID= PORT= LIMIT= OPTIONS=
+ if [[ $HOSTSPEC =~ ^([^:/]+)(:([0-9]+))?(/([0-9]+))?(,.*)?$ ]]; then
+ # TCP_HOST
+ HOSTID=${BASH_REMATCH[1]}
+ PORT=${BASH_REMATCH[3]}
+ LIMIT=${BASH_REMATCH[5]}
+ OPTIONS=${BASH_REMATCH[6]}
+ elif [[ $HOSTSPEC =~ ^([^:/]+)(/([0-9]+))?(:([0-9]+))?(,.*)?$ ]]; then
+ # OLDSTYLE_TCP_HOST
+ HOSTID=${BASH_REMATCH[1]}
+ LIMIT=${BASH_REMATCH[3]}
+ PORT=${BASH_REMATCH[5]}
+ OPTIONS=${BASH_REMATCH[6]}
+ else
+ error "Could not parse HOSTSPEC: %s" "$HOSTSPEC"
+ fi
+
+ # set up port forwaring
+ if $forward_ports; then
+ socat TCP-LISTEN:${newport},fork SYSTEM:"$q0 client $HOSTID ${PORT:-3632}" &
+ pids+=($!)
+ fi
+
+ # add the forwarded port
+ local newhost="127.0.0.1:$newport"
+ [[ -z $LIMIT ]] || newhost+="/$LIMIT"
+ [[ -z $OPTIONS ]] || newhost+="$OPTIONS"
+ newhosts+=("$newhost")
+ : $((newport++))
+ ;;
+ esac
+ done
+ if $forward_ports; then
+ if [[ -z "${pids[*]}" ]]; then
+ # listen on port 8000, but immediatly close, just so that we are
+ # listening on something
+ socat TCP-LISTEN:${newport},fork SYSTEM:true &
+ pids+=($!)
+ fi
+ trap "kill -- ${pids[*]}" EXIT
+ wait "${pids[@]}"
+ else
+ printf '%s\n' "${newhosts[*]}"
+ fi
+}
+
+################################################################################
+# Port forwarding primitives #
+################################################################################
+
+# Usage: server
+# Reads "host port" from the first line of stdin, then connects stdio to the
+# specified TCP socket.
+server() {
+ [[ $# -eq 0 ]] || panic
+ local host port
+ read -r host port
+ socat STDIO TCP:"$host:$port"
+}
+
+# Usage: client HOST PORT
+# For usage inside of a chroot. It talks through the UNIX-domain socket to an
+# instance of `server` outside, in order to connect stdio to the specified TCP
+# socket.
+client() {
+ [[ $# -eq 2 ]] || panic
+ local file=/socket
+ { printf '%s\n' "$*"; cat; } | socat UNIX-CONNECT:"$file" STDIO
+}
+
+################################################################################
+# High-level routines #
+################################################################################
+
+# Usage: odaemon CHROOTPATH
+# Listens on "$CHROOTPATH/socket" and spawns a `server` for each new connection.
+odaemon() {
+ [[ $# -eq 1 ]] || panic
+ local chrootpath=$1
+
+ umask 111
+ socat UNIX-LISTEN:"$chrootpath/socket",fork SYSTEM:"$q0 server" &
+ trap "kill -- $!; rm -f -- $(printf '%q' "$chrootpath/socket")" EXIT
+ wait
+}
+
+# Usage: idaemon DISTCC_HOSTS
+# Sets things up inside of the chroot to forward distcc hosts out.
+idaemon() {
+ [[ $# -eq 1 ]] || panic
+ parse_DISTCC_HOSTS true "$1"
+}
+
+# Usage: rewrite DISTCC_HOSTS
+# Prints a modified version of DISTCC_HOSTS for inside the chroot.
+rewrite() {
+ [[ $# -eq 1 ]] || panic
+ parse_DISTCC_HOSTS false "$1"
+}
+
+main "$@"
diff --git a/src/chroot-tools/hooks-chcleanup.sh b/src/chroot-tools/hooks-chcleanup.sh
new file mode 100644
index 0000000..7afb13d
--- /dev/null
+++ b/src/chroot-tools/hooks-chcleanup.sh
@@ -0,0 +1,32 @@
+#!/usr/bin/env bash
+# Copyright (C) 2013 Luke Shumaker <lukeshu@sbcglobal.net>
+#
+# License: GNU GPLv2+
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+set -euE
+
+hook_pre_build+=("clean_chroot")
+
+clean_chroot() (
+ set +x
+ local copydir=$1
+ if $INCHROOT; then
+ cd /startdir
+ "$(librelib chroot/chcleanup)"
+ else
+ librechroot "${librechroot_flags[@]}" clean-pkgs
+ fi
+)
diff --git a/src/chroot-tools/hooks-check.sh b/src/chroot-tools/hooks-check.sh
new file mode 100644
index 0000000..850516b
--- /dev/null
+++ b/src/chroot-tools/hooks-check.sh
@@ -0,0 +1,37 @@
+#!/usr/bin/env bash
+# Copyright (C) 2013 Luke Shumaker <lukeshu@sbcglobal.net>
+#
+# License: GNU GPLv2+
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+set -euE
+
+hook_check_pkgbuild+=("check_pkgbuild_nonfree")
+check_pkgbuild_nonfree() {
+ local s=0
+ sudo -EH -u "$LIBREUSER" pkgbuild-check-nonfree -f || s=$?
+ pkgbuild-summarize-nonfree $s
+}
+
+#hook_check_pkgbuild+=("check_pkgbuild_namcap")
+check_pkgbuild_namcap() {
+ sudo -EH -u "$LIBREUSER" namcap PKGBUILD
+}
+
+#hook_check_pkg+=("check_pkg")
+check_pkg() {
+ # TODO
+ :
+}
diff --git a/src/chroot-tools/hooks-distcc.sh b/src/chroot-tools/hooks-distcc.sh
new file mode 100644
index 0000000..bb234b8
--- /dev/null
+++ b/src/chroot-tools/hooks-distcc.sh
@@ -0,0 +1,106 @@
+#!/usr/bin/env bash
+# Copyright (C) 2013 Luke Shumaker <lukeshu@sbcglobal.net>
+#
+# License: GNU GPLv2+
+#
+# This file is part of Parabola.
+#
+# Parabola is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 2 of the License, or
+# (at your option) any later version.
+#
+# Parabola is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Parabola. If not, see <http://www.gnu.org/licenses/>.
+
+set -euE
+
+hook_pre_build+=("distcc_start")
+hook_post_build+=("distcc_stop")
+
+_distcc_check() {
+ local copydir=$1
+ local home=$2
+
+ local files=(
+ "$copydir/bin/distcc-tool"
+ "$copydir/run/distcc-tool.pid"
+ "$home/.makepkg.conf"
+ "$home/.ssh/config"
+ )
+
+ local file_err=false
+ for files in "${files[@]}"; do
+ if [[ -f $file ]]; then
+ file_err=true
+ error "Auto-generated file already exists, remove it: %s" "$file"
+ fi
+ done
+ if $file_err; then
+ exit 1
+ fi
+}
+
+distcc_start() {
+ local copydir=$1
+
+ # Because /{,usr/}{,s}bin are all symlinked together for
+ # fileystem 2013.05-2 and up, I can take shortcuts when checking for
+ # existance of programs.
+ if $NONET && [[ -f "$copydir/bin/socat" && -f "$copydir/bin/distcc" ]]; then
+ local home
+ if $INCHROOT; then
+ home=$LIBREHOME
+ else
+ home="$copydir/build"
+ fi
+
+ _distcc_check
+
+ local _distcc_tool="$(librelib chroot/distcc-tool)"
+ install -m755 "$_distcc_tool" "$copydir/bin/distcc-tool"
+
+ mkdir -p "$home/.ssh"
+
+ printf '%s\n' \
+ '/bin/distcc-tool idaemon "$DISTCC_HOSTS" &' \
+ 'DISTCC_HOSTS="$(/bin/distcc-tool rewrite "$DISTCC_HOSTS")"' \
+ > "$home/.makepkg.conf"
+
+ printf '%s\n' \
+ 'Host *' \
+ ' ProxyCommand /bin/distcc-tool client %h %p' \
+ > "$home/.ssh/config"
+
+ "$_distcc_tool" odaemon "$copydir" &
+ echo $! > "$copydir/run/distcc-tool.pid"
+ fi
+}
+
+distcc_stop() {
+ local copydir=$1
+
+ local home
+ if $INCHROOT; then
+ home=$LIBREHOME
+ else
+ home="$copydir/build"
+ fi
+
+ if [[ -f "$copydir/run/distcc-tool.pid" ]]; then
+
+ odaemon=$(< "$copydir/distcc-tool.pid")
+ kill -- "$odaemon"
+
+ rm -f -- \
+ "$home/.makepkg.conf" \
+ "$home/.ssh/config" \
+ "$copydir/bin/distcc-tool" \
+ "$copydir/run/distcc-tool.pid"
+ fi
+}
diff --git a/src/chroot-tools/indent b/src/chroot-tools/indent
new file mode 100755
index 0000000..0f047c6
--- /dev/null
+++ b/src/chroot-tools/indent
@@ -0,0 +1,57 @@
+#!/usr/bin/env perl
+# Copyright (C) 2013 Luke Shumaker <lukeshu@sbcglobal.net>
+#
+# License: GNU GPLv3+
+#
+# This file is part of Parabola.
+#
+# Parabola is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Parabola is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Parabola. If not, see <http://www.gnu.org/licenses/>.
+
+use warnings;
+use strict;
+
+use constant BUFFER_SIZE => 40;
+binmode(STDIN);
+binmode(STDOUT);
+
+exit(1) if ($#ARGV != 0);
+my $indent = $ARGV[0];
+
+# 0: no indent has been printed for this line, an indent WILL need to be printed
+# 1: an indent needs to be printed for this line IFF there is any more output on it
+# 2: no indent (currently) needs to be printed for this line
+my $print_indent = 0;
+
+my $buffer;
+my $size;
+my $c;
+while (1) {
+ $size = sysread(STDIN, $buffer, BUFFER_SIZE);
+ last if ($size < 1);
+ for (0..$size-1) {
+ $c = substr($buffer, $_, 1);
+ if ($c eq "\n") {
+ syswrite(STDOUT, $indent) if ($print_indent == 0);
+ syswrite(STDOUT, $c, 1);
+ $print_indent = 0;
+ } elsif ($c eq "\r") {
+ syswrite(STDOUT, $c, 1);
+ $print_indent = 1 if ($print_indent == 2);
+ } else {
+ syswrite(STDOUT, $indent) if ($print_indent < 2);
+ syswrite(STDOUT, $c, 1);
+ $print_indent = 2;
+ }
+ }
+}
diff --git a/src/chroot-tools/librechroot b/src/chroot-tools/librechroot
new file mode 100755
index 0000000..cf564ed
--- /dev/null
+++ b/src/chroot-tools/librechroot
@@ -0,0 +1,509 @@
+#!/usr/bin/env bash
+set -euE
+# librechroot
+
+# Copyright (C) 2010-2012 Nicolás Reynolds <fauno@parabola.nu>
+# Copyright (C) 2011-2012 Joshua Ismael Haase Hernández (xihh) <hahj87@gmail.com>
+# Copyright (C) 2012 Michał Masłowski <mtjm@mtjm.eu>
+# Copyright (C) 2012-2016 Luke Shumaker <lukeshu@sbcglobal.net>
+#
+# License: GNU GPLv2+
+#
+# This file is part of Parabola.
+#
+# Parabola is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 2 of the License, or
+# (at your option) any later version.
+#
+# Parabola is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Parabola. If not, see <http://www.gnu.org/licenses/>.
+
+# HACKING: if a command is added or removed, it must be changed in 4 places:
+# - the usage() text
+# - the commands=() array
+# - the case statement in main() that checks the number of arguments
+# - the case statement in main() that runs them
+
+. "$(librelib conf)"
+load_files chroot
+
+. libremessages
+
+shopt -s nullglob
+umask 0022
+
+################################################################################
+# Wrappers for files in ${pkglibexecdir}/chroot/ #
+################################################################################
+
+readonly _arch_nspawn="$(librelib chroot/arch-nspawn)"
+readonly _mkarchroot="$(librelib chroot/mkarchroot)"
+readonly _makechrootpkg="$(librelib chroot/makechrootpkg.sh)"
+
+arch_nspawn_flags=()
+sysd_nspawn_flags=()
+
+hack_arch_nspawn_flags() {
+ local copydir="$1"
+
+ local makepkg_conf="$copydir/etc/makepkg.conf"
+
+ OPTIND=1
+ set -- ${arch_nspawn_flags+"${arch_nspawn_flags[@]}"}
+ while getopts 'hC:M:c:f:s' arg; do
+ case "$arg" in
+ M) makepkg_conf="$OPTARG" ;;
+ *) :;;
+ esac
+ done
+
+ # Detect the architecture of the chroot
+ local CARCH
+ if [[ -f "$makepkg_conf" ]]; then
+ eval $(grep '^CARCH=' "$makepkg_conf")
+ else
+ CARCH="$(uname -m)"
+ fi
+
+ if [[ "$CARCH" == armv7h ]] && ! setarch armv7l 2>/dev/null; then
+ # We're running an ARM chroot on a non-ARM processor
+
+ # Make sure that qemu-static is set up with binfmt_misc
+ if [[ $(grep -xF \
+ -e 'enabled'\
+ -e 'interpreter /usr/bin/qemu-arm-static' \
+ /proc/sys/fs/binfmt_misc/arm 2>/dev/null |wc -l) -lt 2 ]]; then
+ error 'Cannot cross-compile for ARM on x86'
+ plain 'This requires a binfmt_misc entry for qemu-arm-static,'
+ plain 'which is provided by the %s package.' binfmt-qemu-static
+ plain 'If you have this, you may need to restart %s.' systemd-binfmt.service
+ return 1
+ fi
+
+ # Let qemu/binfmt_misc do its thing
+ arch_nspawn_flags+=(-f /usr/bin/qemu-arm-static -s)
+
+ # The -any packages are built separately for ARM from
+ # x86, so if we use the same CacheDir as the x86 host,
+ # then there will be PGP errors.
+ mkdir -p /var/cache/pacman/pkg-arm
+ arch_nspawn_flags+=(-c /var/cache/pacman/pkg-arm)
+ fi
+}
+
+# Usage: arch-nspawn $copydir $cmd...
+arch-nspawn() {
+ local copydir=$1; shift
+ local cmd=("$@")
+
+ local arch_nspawn_flags=(${arch_nspawn_flags+"${arch_nspawn_flags[@]}"})
+ hack_arch_nspawn_flags "$copydir"
+
+ "$_arch_nspawn" \
+ ${arch_nspawn_flags+"${arch_nspawn_flags[@]}"} \
+ "$copydir" \
+ ${sysd_nspawn_flags+"${sysd_nspawn_flags[@]}"} \
+ -- \
+ "${cmd[@]}"
+}
+
+# Usage: mkarchroot $copydir $pkgs...
+mkarchroot() {
+ local copydir=$1; shift
+ local pkgs=("$@")
+
+ local arch_nspawn_flags=(${arch_nspawn_flags+"${arch_nspawn_flags[@]}"})
+ hack_arch_nspawn_flags "$copydir"
+
+ unshare -m "$_mkarchroot" \
+ ${arch_nspawn_flags+"${arch_nspawn_flags[@]}"} \
+ "$copydir" \
+ "${pkgs[@]}"
+}
+
+# Usage: _makechrootpkg $function $arguments...
+# Don't load $_makechrootpkg directly because it doesn't work with -euE
+_makechrootpkg() (
+ set +euE
+ . "$_makechrootpkg"
+ "$@"
+)
+
+################################################################################
+# Utility functions #
+################################################################################
+
+# Usage: make_empty_repo $copydir
+make_empty_repo() {
+ local copydir=$1
+ mkdir -p "${copydir}/repo"
+ bsdtar -czf "${copydir}/repo/repo.db.tar.gz" -T /dev/null
+ ln -s "repo.db.tar.gz" "${copydir}/repo/repo.db"
+}
+
+# Usage: chroot_add_to_local_repo $copydir $pkgfiles...
+chroot_add_to_local_repo() {
+ local copydir=$1; shift
+ mkdir -p "$copydir/repo"
+ local pkgfile
+ for pkgfile in "$@"; do
+ cp "$pkgfile" "$copydir/repo"
+ pushd "$copydir/repo" >/dev/null
+ repo-add repo.db.tar.gz "${pkgfile##*/}"
+ popd >/dev/null
+ done
+}
+
+# Print code to set $rootdir and $copydir; blank them on error
+calculate_directories() {
+ # Don't assume that CHROOTDIR or CHROOT are set,
+ # but assume that COPY is set.
+ local rootdir copydir
+
+ if [[ -n ${CHROOTDIR:-} ]] && [[ -n ${CHROOT:-} ]]; then
+ rootdir="${CHROOTDIR}/${CHROOT}/root"
+ else
+ rootdir=''
+ fi
+
+ if [[ ${COPY:0:1} = / ]]; then
+ copydir=$COPY
+ elif [[ -n ${CHROOTDIR:-} ]] && [[ -n ${CHROOT:-} ]]; then
+ copydir="${CHROOTDIR}/${CHROOT}/${COPY}"
+ else
+ copydir=''
+ fi
+
+ declare -p rootdir
+ declare -p copydir
+}
+
+check_mountpoint() {
+ local file=$1
+ local mountpoint="$(df -P "$file"|sed '1d;s/.*\s//')"
+ local mountopts=($(LC_ALL=C mount|awk "{ if (\$3==\"$mountpoint\") { gsub(/[(,)]/, \" \", \$6); print \$6 } }"))
+ ! in_array nosuid "${mountopts[@]}" && ! in_array noexec "${mountopts[@]}"
+}
+
+################################################################################
+# Main program #
+################################################################################
+
+usage() {
+ eval "$(calculate_directories)"
+ print "Usage: %s [OPTIONS] COMMAND [ARGS...]" "${0##*/}"
+ print 'Interacts with an archroot (arch chroot).'
+ echo
+ prose 'This is configured with `chroot.conf`, either in
+ `/etc/libretools.d/`, or `$XDG_CONFIG_HOME/libretools/`.
+ The variables you may set are $CHROOTDIR, $CHROOT, and
+ $CHROOTEXTRAPKG.'
+ echo
+ prose 'There may be multiple chroots; they are stored in $CHROOTDIR.'
+ echo
+ prose 'Each chroot is named; the default is configured with $CHROOT.'
+ echo
+ prose 'Each named chroot has a master clean copy (named `root`), and any
+ number of other named copies; the copy used by default is the
+ current username (or $SUDO_USER, or `copy` if root).'
+ echo
+ prose 'The full path to the chroot copy is "$CHROOTDIR/$CHROOT/$COPY",
+ unless the copy name is manually specified as an absolute path,
+ in which case, that path is used.'
+ echo
+ prose 'The current settings for the above variables are:'
+ printf ' CHROOTDIR : %s\n' "${CHROOTDIR:-$(_ 'ERROR: NO SETTING')}"
+ printf ' CHROOT : %s\n' "${CHROOT:-$(_ 'ERROR: NO SETTING')}"
+ printf ' COPY : %s\n' "$COPY"
+ printf ' rootdir : %s\n' "${rootdir:-$(_ 'ERROR')}"
+ printf ' copydir : %s\n' "${copydir:-$(_ 'ERROR')}"
+ echo
+ prose 'If the chroot or copy does not exist, it will be created
+ automatically. A chroot by default contains the packages in the
+ group "base-devel" and any packages named in $CHROOTEXTRAPKG.
+ Unless the `-C` or `-M` flags are used, the configuration files
+ that this program installs are the stock versions supplied in the
+ packages, not the versions from your host system. Other tools
+ (such as libremakepkg) may alter the configuration.'
+ echo
+ prose 'This command will make the following configuration changes in the
+ chroot:'
+ bullet 'overwrite `/etc/libretools.d/chroot.conf`'
+ bullet 'overwrite `/etc/pacman.d/mirrorlist`'
+ bullet 'set `CacheDir` in `/etc/pacman.conf`'
+ prose 'If a new `pacman.conf` is inserted with the `-C` flag, the change
+ is made after the file is copied in; the `-C` flag doesn'"'"'t
+ stop the change from being effective.'
+ echo
+ prose 'The processor architecture of the chroot is determined
+ by the by `CARCH` variable in the `/etc/makepkg.conf`
+ file inside of the chroot.'
+ echo
+ prose 'The `-A CARCH` flag is *almost* simply an alias for'
+ printf ' %s\n' \
+ '-C "/usr/share/pacman/defaults/pacman.conf.$CARCH" \' \
+ '-M "/usr/share/pacman/defaults/makepkg.conf.$CARCH"'
+ prose 'However, before doing that, it actually makes a temporary copy of
+ `pacman.conf`, and sets the `Architecture` line to match the
+ `CARCH` line in `makepkg.conf`.'
+ echo
+ prose 'Creating a copy, deleting a copy, or syncing a copy can be fairly
+ slow; but are very fast if $CHROOTDIR is on a btrfs partition.'
+ echo
+ print 'Options:'
+ flag "-n <$(_ CHROOT)>" 'Name of the chroot to use'
+ flag "-l <$(_ COPY)>" 'Name of, or absolute path to, the copy to use'
+ flag '-N' 'Disable networking in the chroot'
+ flag "-C <$(_ FILE)>" 'Copy this file to `$copydir/etc/pacman.conf`'
+ flag "-M <$(_ FILE)>" 'Copy this file to `$copydir/etc/makepkg.conf`'
+ flag "-A <$(_ CARCH)>" 'Set the architecture of the copy; simply an
+ alias for the `-C` and `-M` flags, see above.'
+ flag "-w <$(_ 'PATH[:PATH]')>" 'Bind mount a file or directory, read/write'
+ flag "-r <$(_ 'PATH[:PATH]')>" 'Bind mount a file or directory, read-only'
+ echo
+ print 'Commands:'
+ print ' Create/copy/delete:'
+ flag 'noop|make' 'Do not do anything, but still creates the chroot
+ copy if it does not exist'
+ flag 'sync' 'Sync the copy with the clean (`root`) copy'
+ flag 'delete' 'Delete the chroot copy'
+ print ' Dealing with packages:'
+ flag "install-file $(_ FILES...)" 'Like `pacman -U FILES...`'
+ flag "install-name $(_ NAMES...)" 'Like `pacman -S NAMES...`'
+ flag 'update' 'Like `pacman -Syu`'
+ flag 'clean-pkgs' 'Remove all packages from the chroot copy that
+ are not in base-devel, $CHROOTEXTRAPKG, or named
+ as a dependency in the file `/startdir/PKGBUILD`
+ in the chroot copy'
+ print ' Other:'
+ flag "run $(_ CMD...)" 'Run CMD in the chroot copy'
+ flag 'enter' 'Enter an interactive shell in the chroot copy'
+ flag 'clean-repo' 'Clean /repo in the chroot copy'
+ flag 'help' 'Show this message'
+}
+readonly commands=(
+ noop make sync delete
+ install-file install-name update clean-pkgs
+ run enter clean-repo help
+)
+
+# Globals: $CHROOTDIR, $CHROOT, $COPY, $rootdir and $copydir
+main() {
+ COPY=$LIBREUSER
+ [[ $COPY != root ]] || COPY=copy
+
+ local mode=enter
+ while getopts 'n:l:NC:M:A:w:r:' opt; do
+ case $opt in
+ n) CHROOT=$OPTARG;;
+ l) COPY=$OPTARG;;
+ N) sysd_nspawn_flags+=(--private-network);;
+ C|M) arch_nspawn_flags+=(-$opt "$OPTARG");;
+ A)
+ if ! [[ -f "/usr/share/pacman/defaults/pacman.conf.$OPTARG" && -f "/usr/share/pacman/defaults/makepkg.conf.$OPTARG" ]]; then
+ error 'Unsupported architecture: %s' "$OPTARG"
+ plain 'See the files in %q for valid architectures.' /usr/share/pacman/defaults/
+ return 1;
+ fi
+ trap 'rm -f -- "$tmppacmanconf"' EXIT
+ tmppacmanconf="$(mktemp --tmpdir librechroot-pacman.conf.XXXXXXXXXX)"
+ < "/usr/share/pacman/defaults/pacman.conf.$OPTARG" sed -r "s|^#?\\s*Architecture.+|Architecture = ${OPTARG}|g" > "$tmppacmanconf"
+ arch_nspawn_flags+=(
+ -C "$tmppacmanconf"
+ -M "/usr/share/pacman/defaults/makepkg.conf.$OPTARG"
+ );;
+ w) sysd_nspawn_flags+=("--bind=$OPTARG");;
+ r) sysd_nspawn_flags+=("--bind-ro=$OPTARG");;
+ *) usage >&2; return 1;;
+ esac
+ done
+ shift $(($OPTIND - 1))
+ if [[ $# -lt 1 ]]; then
+ error "Must specify a command"
+ usage >&2
+ return 1
+ fi
+ mode=$1
+ if ! in_array "$mode" "${commands[@]}"; then
+ error "Unrecognized command: %s" "$mode"
+ usage >&2
+ return 1
+ fi
+ shift
+ case "$mode" in
+ noop|make|sync|delete|update|enter|clean-pkgs|clean-repo)
+ if [[ $# -gt 0 ]]; then
+ error 'Command `%s` does not take any arguments: %s' "$mode" "$*"
+ usage >&2
+ return 1
+ fi
+ :;;
+ install-file)
+ if [[ $# -lt 1 ]]; then
+ error 'Command `%s` requires at least one file' "$mode"
+ usage >&2
+ return 1
+ else
+ local missing=()
+ local file
+ for file in "$@"; do
+ if ! [[ -f $file ]]; then
+ missing+=("$file")
+ fi
+ done
+ if [[ ${#missing[@]} -gt 0 ]]; then
+ error "%s: file(s) not found: %s" "$mode" "${missing[*]}"
+ return 1
+ fi
+ fi
+ :;;
+ install-name)
+ if [[ $# -lt 1 ]]; then
+ error 'Command `%s` requires at least one package name' "$mode"
+ usage >&2
+ return 1
+ fi
+ :;;
+ run)
+ if [[ $# -lt 1 ]]; then
+ error 'Command `%s` requires at least one argument' "$mode"
+ usage >&2
+ return 1
+ fi
+ :;;
+ esac
+
+
+ if [[ $mode == help ]]; then
+ usage
+ return 0
+ fi
+
+ check_vars chroot CHROOTDIR CHROOT
+ eval "$(calculate_directories)"
+
+ readonly LIBREUSER LIBREHOME
+ readonly CHROOTDIR CHROOT COPY
+ readonly rootdir copydir
+ readonly mode
+
+ ########################################################################
+
+ if (( EUID )); then
+ error "This program must be run as root."
+ return 1
+ fi
+
+ umask 0022
+
+ # XXX: SYSTEMD-STDIN HACK
+ if ! [[ -t 0 ]]; then
+ error "Input is not a TTY"
+ plain "https://labs.parabola.nu/issues/431"
+ plain "https://bugs.freedesktop.org/show_bug.cgi?id=70290"
+ prose "Due to a bug in systemd-nspawn, redirecting stdin is not
+ supported." >&2
+ return 1
+ fi
+
+ # Keep this lock for as long as we are running
+ # Note that '9' is the same FD number as in mkarchroot et al.
+ lock 9 "$copydir.lock" \
+ "Waiting for existing lock on chroot copy to be released: [%s]" "$COPY"
+
+ if [[ $mode != delete ]]; then
+ if ! check_mountpoint "$copydir.lock"; then
+ error "Chroot copy is mounted with nosuid or noexec options: [%s]" "$COPY"
+ return 1
+ fi
+
+ if [[ ! -d $rootdir ]]; then
+ msg "Creating 'root' copy for chroot [%s]" "$CHROOT"
+ mkarchroot "$rootdir" base-devel
+ make_empty_repo "$rootdir"
+ fi
+
+ if [[ ! -d $copydir ]] || [[ $mode == sync ]]; then
+ msg "Syncing copy [%s] with root copy" "$COPY"
+ _makechrootpkg sync_chroot "$CHROOTDIR/$CHROOT" "$COPY"
+ fi
+
+ # Note: the in-chroot pkgconfdir is non-configurable, this is
+ # intentionally hard-coded.
+ mkdir -p "$copydir/etc/libretools.d"
+ {
+ if [[ ${#CHROOTEXTRAPKG[*]} -eq 0 ]]; then
+ echo 'CHROOTEXTRAPKG=()'
+ else
+ printf 'CHROOTEXTRAPKG=('
+ printf '%q ' "${CHROOTEXTRAPKG[@]}"
+ printf ')\n'
+ fi
+ } > "$copydir"/etc/libretools.d/chroot.conf
+
+ # "touch" the chroot first
+ # this will
+ # - overwrite '/etc/pacman.d/mirrorlist'"
+ # - set 'CacheDir' in \`/etc/pacman.conf'"
+ # - apply -C or -M flags
+ arch-nspawn "$copydir" true
+ trap EXIT # clear the trap to remove the tmp pacman.conf from -A
+ arch_nspawn_flags=() # XXX dirty hack, don't apply -C or -M again
+ fi
+
+ ########################################################################
+
+ case "$mode" in
+ # Creat/copy/delete
+ noop|make|sync) :;;
+ delete)
+ if [[ -d $copydir ]]; then
+ _makechrootpkg delete_chroot "$copydir"
+ fi
+ ;;
+
+ # Dealing with packages
+ install-file)
+ _makechrootpkg install_packages "$copydir" "$@"
+ chroot_add_to_local_repo "$copydir" "$@"
+ ;;
+ install-name)
+ arch-nspawn "$copydir" pacman -Sy -- "$@"
+ ;;
+ update)
+ arch-nspawn "$copydir" pacman -Syu --noconfirm
+ ;;
+ clean-pkgs)
+ trap "rm -f -- $(printf '%q ' "$copydir"/{bin/chcleanup,chrootexec})" EXIT
+ install -m755 "$(librelib chroot/chcleanup)" "$copydir/bin/chcleanup"
+ printf '%s\n' \
+ '#!/bin/bash' \
+ 'mkdir -p /startdir' \
+ 'cd /startdir' \
+ '/bin/chcleanup' \
+ > "$copydir/chrootexec"
+ chmod 755 "$copydir/chrootexec"
+ arch-nspawn "$copydir" /chrootexec
+ ;;
+
+ # Other
+ run)
+ arch-nspawn "$copydir" "$@"
+ ;;
+ enter)
+ arch-nspawn "$copydir" bash
+ ;;
+ clean-repo)
+ rm -rf "${copydir}"/repo/*
+ make_empty_repo "$copydir"
+ ;;
+ esac
+}
+
+main "$@"
diff --git a/src/chroot-tools/libremakepkg b/src/chroot-tools/libremakepkg
new file mode 100755
index 0000000..a226e38
--- /dev/null
+++ b/src/chroot-tools/libremakepkg
@@ -0,0 +1,289 @@
+#!/usr/bin/env bash
+set -euE
+# libremakepkg
+
+# Copyright (C) 2010-2012 Nicolás Reynolds <fauno@parabola.nu>
+# Copyright (C) 2010-2012 Joshua Ismael Haase Hernández (xihh) <hahj87@gmail.com>
+# Copyright (C) 2012 Michał Masłowski <mtjm@mtjm.eu>
+# Copyright (C) 2012-2015 Luke Shumaker <lukeshu@sbcglobal.net>
+#
+# License: GNU GPLv2+
+#
+# This file is part of Parabola.
+#
+# Parabola is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 2 of the License, or
+# (at your option) any later version.
+#
+# Parabola is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Parabola. If not, see <http://www.gnu.org/licenses/>.
+
+. "$(librelib conf)"
+. "$(librelib messages)"
+. "$(librelib chroot/makechrootpkg.sh)"
+
+set -o pipefail
+shopt -s nullglob
+umask 0022
+
+# Global variables:
+readonly _indent="$(librelib chroot/indent)"
+readonly INCHROOT=$([[ -f /.arch-chroot ]] && echo true || echo false)
+NONET=true # can be changed with the -N flag
+# {PKG,SRC,SRCPKG,LOG}DEST set at runtime by makepkg.conf
+# MAKEFLAGS, PACKAGER set at runtime by makepkg.conf
+# LIBREUSER, LIBREHOME are set by conf.sh
+librechroot_flags=()
+
+# Hooks ########################################################################
+
+hook_pre_build=(:)
+hook_post_build=(:)
+hook_check_pkgbuild=(:)
+hook_check_pkg=(:)
+. "$(librelib chroot/hooks-chcleanup.sh)"
+. "$(librelib chroot/hooks-check.sh)"
+. "$(librelib chroot/hooks-distcc.sh)"
+
+# Boring/mundane functions #####################################################
+
+indent() {
+ "$_indent" ' | '
+}
+
+# Usage: exit_copy $copydir $src_owner
+# End immediately, but copy log files out
+exit_copy() {
+ local copydir=$1
+ local src_owner=$2
+ if ! $INCHROOT; then
+ msg "Copying log and package files out of the chroot..."
+ move_products "$copydir" "$src_owner"
+ fi
+}
+
+# Usage; run_hook $hookname $args...
+run_hook() {
+ local hookname=$1; shift
+ local hookvar="hook_${hookname}[@]"
+
+ local fails=()
+ for hook in "${!hookvar}"; do
+ "$hook" "$@" || fails+=("$hook")
+ done |& indent
+
+ if [[ ${#fails[@]} -gt 0 ]]; then
+ error "Failure(s) in %s: %s" "$hookname" "${fails[*]}"
+ return 1
+ else
+ return 0
+ fi
+}
+
+# Usage: add_to_local_repo $copydir $pkgfiles...
+add_to_local_repo() {
+ local copydir=$1; shift
+ mkdir -p "$copydir/repo"
+ local pkgfile
+ for pkgfile in "$@"; do
+ cp "$pkgfile" "$copydir/repo"
+ pushd "$copydir/repo" >/dev/null
+ repo-add repo.db.tar.gz "${pkgfile##*/}"
+ popd >/dev/null
+ done
+}
+
+hook_post_build+=('cleanup')
+cleanup() {
+ local copydir=$1
+ rm -f -- "$copydir"/chroot{prepare,build}
+}
+
+build() (
+ local copydir=$1
+ local repack=$2
+ local makepkg_args=("${@:3}")
+
+ local run_ynet=()
+ local run_nnet=()
+ if $INCHROOT; then
+ run_ynet=(unshare)
+ run_nnet=(unshare -n)
+ else
+ run_ynet=(librechroot "${librechroot_flags[@]}" run)
+ run_nnet=(librechroot "${librechroot_flags[@]}" -N run)
+ fi
+ $NONET || run_nnet=("${run_ynet[@]}")
+
+ prepare_chroot "$copydir" "$LIBREHOME" "$repack" false
+ "${run_ynet[@]}" /chrootprepare "${makepkg_args[@]}" |& indent
+ run_hook pre_build "$copydir"
+ trap "run_hook post_build $(printf '%q' "$copydir")" EXIT
+ "${run_nnet[@]}" /chrootbuild "${makepkg_args[@]}" |& indent
+)
+
+# The main program #############################################################
+
+usage() {
+ print "Usage: %s [options]" "${0##*/}"
+ print 'This program will build your package.'
+ echo
+ prose 'If run from outside of a chroot, command will make the following
+ configuration changes in the chroot:'
+ bullet 'whatever changes `librechroot` makes.'
+ bullet 'set `{PKG,SRC,SRCPKG,LOG}DEST` in `/etc/makepkg.conf`'
+ bullet 'set `PACKAGER` in `/etc/makepkg.conf` to reflect the value
+ outside of the chroot.'
+ bullet '(maybe) delete `/build/.makepkg.conf`'
+ bullet '(maybe) delete `/build/.ssh/config`'
+ prose 'If run from inside of a chroot, this command will:'
+ bullet '(maybe) delete `~/.makepkg.conf`'
+ bullet '(maybe) delete `~/.ssh/config`'
+ prose 'The above "maybe"s happen as part of the workarounds to make
+ distcc work in a network-less environment. They will happen if
+ both `socat` and `distcc` are installed in the chroot.'
+ echo
+ prose 'The `-n` and `-l` options behave identically to librechroot, see
+ the documentation there.'
+ echo
+ print 'Options:'
+ print ' %s options:' librechroot
+ flag "-n <$(_ CHROOT)>" 'Name of the chroot to use'
+ flag "-l <$(_ COPY)>" 'Name of, or absolute path to, the chroot copy to use'
+ flag "-w <$(_ 'PATH[:PATH]')>" 'Bind mount a file or directory, read/write'
+ flag "-r <$(_ 'PATH[:PATH]')>" 'Bind mount a file or directory, read-only'
+ print ' %s options:' libremakepkg
+ flag '-N' "Don't disable networking during build() and
+ package(). PLEASE don't use this unless you
+ have a special reason, its use is a violation
+ of Parabola policy."
+ flag '-R' 'Repackage contents of the package without rebuilding'
+ flag '-h' 'Show this message'
+}
+
+# Convenience method for use in option parsing
+err_chflag() {
+ local flag=$1
+ error 'The -%s flag does not make sense inside of a chroot' "$flag"
+ return 1
+}
+
+main() {
+ # Initial variable values ##############################################
+ local copy=$([[ $LIBREUSER == root ]] && echo copy || echo "$LIBREUSER")
+ local makepkg_args=(-s --noconfirm -L)
+ local repack=false
+ local chroot=''
+
+ # Parse command line options ###########################################
+ while getopts 'n:l:w:r:NRh' flag ; do
+ case "${flag}" in
+ n) if $INCHROOT; then err_chflag "$flag"; else
+ chroot=$OPTARG; fi;;
+ l) if $INCHROOT; then err_chflag "$flag"; else
+ copy=$OPTARG; fi;;
+ w|r) if $INCHROOT; then err_chflag "$flag"; else
+ librechroot_flags+=(-$flag "$OPTARG"); fi;;
+ N) NONET=false;;
+ R) repack=true; makepkg_args+=(-R);;
+ h) usage; return 0;;
+ *) usage >&2; return 1;;
+ esac
+ done
+ shift $(($OPTIND - 1))
+ if [[ $# != 0 ]]; then
+ error 'Extra arguments: %s' "$*"
+ return 1
+ fi
+
+ # Resolve the chroot path ##############################################
+ local copydir
+ if $INCHROOT; then
+ copydir='/'
+ else
+ load_files chroot
+ check_vars chroot CHROOTDIR CHROOT
+ [[ -z ${chroot} ]] || CHROOT=$chroot
+ if [[ ${copy:0:1} = / ]]; then
+ copydir=$copy
+ else
+ copydir="${CHROOTDIR}/${CHROOT}/${copy}"
+ fi
+ unset CHROOTDIR CHROOTEXTRAPKG
+ fi
+ unset chroot
+
+ # Load makepkg configuration ###########################################
+ # Note that all of these are globals
+ PKGDEST="$(get_var makepkg PKGDEST "$PWD")"
+ SRCDEST="$(get_var makepkg SRCDEST "$PWD")"
+ SRCPKGDEST="$(get_var makepkg SRCPKGDEST "$PWD")"
+ LOGDEST="$(get_var makepkg LOGDEST "$PWD")"
+ MAKEFLAGS="$(get_var makepkg MAKEFLAGS '')"
+ PACKAGER="$(get_var makepkg PACKAGER '')"
+
+ # Quick sanity check ###################################################
+
+ if (( EUID )); then
+ error "This program must be run as root"
+ exit 1
+ fi
+
+ if [[ ! -f PKGBUILD ]]; then
+ # This is the message used by makepkg
+ error "PKGBUILD does not exist."
+ exit 1
+ fi
+
+ # Make sure that the various *DEST directories exist
+ mkdir -p -- "$PKGDEST" "$SRCDEST" "$SRCPKGDEST" "$LOGDEST"
+
+ # OK, we are starting now ##############################################
+
+ if $INCHROOT; then
+ lock 9 "/build/.lock" \
+ "Waiting for existing lock on build directory to be released"
+ else
+ librechroot_flags+=(
+ -r "$PWD:/startdir_host"
+ -r "$SRCDEST:/srcdest_host"
+ -n "$CHROOT"
+ -l "$copy"
+ )
+
+ # Obtain a lock on the chroot
+ lock 9 "$copydir.lock" \
+ "Waiting for existing lock on chroot copy to be released: [%s]" "$copy"
+ # Create the chroot if it does not exist
+ msg 'Initializing the chroot...'
+ librechroot "${librechroot_flags[@]}" make |& indent
+ fi
+
+ # Set target CARCH
+ # note that we waited until after locking/creating the chroot to do this
+ export CARCH="$(MAKEPKG_CONF=$copydir/etc/makepkg.conf get_var makepkg CARCH)"
+
+ # Pre-build
+ msg 'Starting pre-build activities...'
+ run_hook check_pkgbuild
+ msg 'Downloading sources...'
+ download_sources "$copydir" "$LIBREUSER" |& indent
+
+ # Build
+ msg 'Starting to build the package...'
+ trap "exit_copy '$copydir' '$LIBREUSER'" EXIT
+ build "$copydir" "$repack" "${makepkg_args[@]}"
+
+ # Post-build
+ msg 'Starting post-build activities...'
+ run_hook check_pkg
+ add_to_local_repo "$copydir" "$copydir"/pkgdest/*.pkg.tar* |& indent
+}
+
+main "$@"
diff --git a/src/chroot-tools/makechrootpkg.sh.patch b/src/chroot-tools/makechrootpkg.sh.patch
new file mode 100644
index 0000000..d5c6fca
--- /dev/null
+++ b/src/chroot-tools/makechrootpkg.sh.patch
@@ -0,0 +1,342 @@
+--- makechrootpkg.sh.in 2016-02-08 17:17:11.848386338 -0500
++++ makechrootpkg.sh.ugly 2016-04-14 22:01:05.923288181 -0400
+@@ -1,4 +1,6 @@
+ #!/bin/bash
++# License: GNU GPLv2
++#
+ # This program is free software; you can redistribute it and/or modify
+ # it under the terms of the GNU General Public License as published by
+ # the Free Software Foundation; version 2 of the License.
+@@ -12,6 +14,7 @@
+
+ shopt -s nullglob
+
++init_variables() {
+ default_makepkg_args=(-s --noconfirm -L --holdver)
+ makepkg_args=("${default_makepkg_args[@]}")
+ repack=false
+@@ -29,9 +32,10 @@
+ bindmounts_rw=()
+
+ copy=$USER
+-[[ -n $SUDO_USER ]] && copy=$SUDO_USER
++[[ -n ${SUDO_USER:-} ]] && copy=$SUDO_USER
+ [[ -z "$copy" || $copy = root ]] && copy=copy
+ src_owner=${SUDO_USER:-$USER}
++}
+
+ usage() {
+ echo "Usage: ${0##*/} [options] -r <chrootdir> [--] [makepkg args]"
+@@ -68,28 +72,51 @@
+ }
+
+ # {{{ functions
++# Usage: load_vars $makepkg_conf
++# Globals:
++# - SRCDEST
++# - SRCPKGDEST
++# - PKGDEST
++# - LOGDEST
++# - MAKEFLAGS
++# - PACKAGER
+ load_vars() {
+ local makepkg_conf="$1" var
+
+ [[ -f $makepkg_conf ]] || return 1
+
+ for var in {SRC,SRCPKG,PKG,LOG}DEST MAKEFLAGS PACKAGER; do
+- [[ -z ${!var} ]] && eval $(grep "^${var}=" "$makepkg_conf")
++ [[ -z ${!var:-} ]] && eval $(grep "^${var}=" "$makepkg_conf")
+ done
+
+ return 0
+ }
+
+-create_chroot() {
+- # Lock the chroot we want to use. We'll keep this lock until we exit.
+- lock 9 "$copydir.lock" "Locking chroot copy [%s]" "$copy"
++# Usage: sync_chroot $CHROOTDIR/$CHROOT <$CHROOTCOPY|$copydir>
++sync_chroot() {
++ local chrootdir=$1
++ local copy=$2
++ local copydir=''
++ if [[ ${copy:0:1} = / ]]; then
++ copydir=$copy
++ else
++ copydir="$chrootdir/$copy"
++ fi
++
++ if [[ "$chrootdir/root" -ef "$copydir" ]]; then
++ error 'Cannot sync copy with itself: %s' "$copydir"
++ return 1
++ fi
++
++ # Detect chrootdir filesystem type
++ local chroottype=$(stat -f -c %T "$chrootdir")
+
+- if [[ ! -d $copydir ]] || $clean_first; then
+ # Get a read lock on the root chroot to make
+ # sure we don't clone a half-updated chroot
+- slock 8 "$chrootdir/root.lock" "Locking clean chroot"
++ slock 8 "$chrootdir/root.lock" \
++ "Locking clean chroot [%s]" "$chrootdir/root"
+
+- stat_busy "Creating clean working copy [%s]" "$copy"
++ stat_busy "Synchronizing chroot copy [%s] -> [%s]" "$chrootdir/root" "$copydir"
+ if [[ "$chroottype" == btrfs ]] && ! mountpoint -q "$copydir"; then
+ if [[ -d $copydir ]]; then
+ btrfs subvolume delete "$copydir" >/dev/null ||
+@@ -105,14 +132,18 @@
+
+ # Drop the read lock again
+ lock_close 8
+- fi
+
+ # Update mtime
+ touch "$copydir"
+ }
+
+-clean_temporary() {
+- stat_busy "Removing temporary copy [%s]" "$copy"
++# Usage: delete_chroot $copydir
++delete_chroot() {
++ local copydir=$1
++ # Detect chrootdir filesystem type
++ local chroottype=$(stat -f -c %T "$copydir")
++
++ stat_busy "Removing chroot copy [%s]" "$copydir"
+ if [[ "$chroottype" == btrfs ]] && ! mountpoint -q "$copydir"; then
+ btrfs subvolume delete "$copydir" >/dev/null ||
+ die "Unable to delete subvolume %s" "$copydir"
+@@ -127,9 +158,14 @@
+ stat_done
+ }
+
++# Usage: install_packages $copydir $pkgs...
+ install_packages() {
++ local copydir=$1
++ local install_pkgs=("${@:2}")
++ declare -i ret=0
+ local pkgname
+
++ local install_pkg
+ for install_pkg in "${install_pkgs[@]}"; do
+ pkgname="${install_pkg##*/}"
+ cp "$install_pkg" "$copydir/$pkgname"
+@@ -142,11 +178,19 @@
+ rm "$copydir/$pkgname"
+ done
+
+- # If there is no PKGBUILD we are done
+- [[ -f PKGBUILD ]] || exit $ret
++ return $ret
+ }
+
++# Usage: prepare_chroot $copydir $HOME $repack $run_namcap
++# Globals:
++# - MAKEFLAGS
++# - PACKAGER
+ prepare_chroot() {
++ local copydir=$1
++ local USER_HOME=$2
++ local repack=$3
++ local run_namcap=$4
++
+ $repack || rm -rf "$copydir/build"
+
+ mkdir -p "$copydir/build"
+@@ -193,12 +237,12 @@
+ printf 'builduser:x:%d:100:builduser:/:/usr/bin/nologin\n' "$builduser_uid" >>"$copydir/etc/passwd"
+ chown -R "$builduser_uid" "$copydir"/{build,pkgdest,srcpkgdest,logdest,srcdest,startdir}
+
+- if [[ -n $MAKEFLAGS ]]; then
++ if [[ -n ${MAKEFLAGS:-} ]]; then
+ sed -i '/^MAKEFLAGS=/d' "$copydir/etc/makepkg.conf"
+ echo "MAKEFLAGS='${MAKEFLAGS}'" >> "$copydir/etc/makepkg.conf"
+ fi
+
+- if [[ -n $PACKAGER ]]; then
++ if [[ -n ${PACKAGER:-} ]]; then
+ sed -i '/^PACKAGER=/d' "$copydir/etc/makepkg.conf"
+ echo "PACKAGER='${PACKAGER}'" >> "$copydir/etc/makepkg.conf"
+ fi
+@@ -211,10 +255,25 @@
+ chmod 440 "$copydir/etc/sudoers.d/builduser-pacman"
+ fi
+
++ if ! grep -q '^\[repo\]' "$copydir/etc/pacman.conf"; then
++ local line=$(grep -n '^\[' "$copydir/etc/pacman.conf" |grep -Fv ':[options]'|sed 's/:.*//;1q')
++ local ins='[repo]
++SigLevel = Optional TrustAll
++Server = file:///repo
++'
++ sed -i "${line}i${ins//$'\n'/\\n}" "$copydir/etc/pacman.conf"
++ fi
++
+ # This is a little gross, but this way the script is recreated every time in the
+ # working copy
+ {
+ printf '#!/bin/bash\n'
++ declare -f _chrootprepare
++ printf '_chrootprepare "$@"\n'
++ } > "$copydir/chrootprepare"
++ chmod +x "$copydir/chrootprepare"
++ {
++ printf '#!/bin/bash\n'
+ declare -f _chrootbuild
+ printf '_chrootbuild "$@" || exit\n'
+
+@@ -231,13 +290,19 @@
+ chmod +x "$copydir/chrootbuild"
+ }
+
++# Usage: download_sources $copydir $src_owner
++# Globals:
++# - SRCDEST
+ download_sources() {
++ local copydir=$1
++ local src_owner=$2
++
+ local builddir="$(mktemp -d)"
+ chmod 1777 "$builddir"
+
+ # Ensure sources are downloaded
+- if [[ -n $SUDO_USER ]]; then
+- sudo -u $SUDO_USER env SRCDEST="$SRCDEST" BUILDDIR="$builddir" \
++ if [[ $USER != $src_owner ]]; then
++ sudo -u $src_owner env SRCDEST="$SRCDEST" BUILDDIR="$builddir" \
+ makepkg --config="$copydir/etc/makepkg.conf" --verifysource -o
+ else
+ ( export SRCDEST BUILDDIR="$builddir"
+@@ -247,10 +312,10 @@
+ (( $? != 0 )) && die "Could not download sources."
+
+ # Clean up garbage from verifysource
+- rm -rf $builddir
++ rm -rf "$builddir"
+ }
+
+-_chrootbuild() {
++_chrootprepare() {
+ # This function isn't run in makechrootpkg,
+ # so no global variables
+
+@@ -259,6 +324,7 @@
+ shopt -s nullglob
+
+ # XXX: Workaround makepkg disliking read-only dirs
++ rm -rf -- /srcdest/* /startdir/*
+ ln -sft /srcdest /srcdest_host/*
+ ln -sft /startdir /startdir_host/*
+
+@@ -288,15 +354,42 @@
+ exit 1
+ fi
+
+- sudo -u builduser makepkg "$@"
++ # Sync deps now, as networking may be disabled during _chrootbuild
++ cp /repo/repo.db /var/lib/pacman/sync/repo.db
++ sudo -u builduser makepkg "$@" --nobuild
++}
++
++_chrootbuild() {
++ # This function isn't run in makechrootpkg,
++ # so no global variables
++
++ . /etc/profile
++ export HOME=/build
++ shopt -s nullglob
++
++ cd /startdir
++
++ sudo -u builduser makepkg "$@" --noextract --noprepare
+ }
+
++# Usage: move_products $copydir $owner
++# Globals:
++# - PKGDEST
++# - LOGDEST
+ move_products() {
++ local copydir=$1
++ local src_owner=$2
++
++ local pkgfile
+ for pkgfile in "$copydir"/pkgdest/*; do
+ chown "$src_owner" "$pkgfile"
+ mv "$pkgfile" "$PKGDEST"
++ if [[ $PKGDEST != $PWD ]]; then
++ ln -sf "$PKGDEST/${pkgfile##*/}" .
++ fi
+ done
+
++ local l
+ for l in "$copydir"/logdest/*; do
+ [[ $l == */logpipe.* ]] && continue
+ chown "$src_owner" "$l"
+@@ -310,6 +403,9 @@
+ }
+ # }}}
+
++main() {
++init_variables
++
+ orig_argv=("$@")
+
+ while getopts 'hcur:I:l:nTD:d:' arg; do
+@@ -375,30 +471,45 @@
+ [[ -d $SRCPKGDEST ]] || SRCPKGDEST=$PWD
+ [[ -d $LOGDEST ]] || LOGDEST=$PWD
+
+-create_chroot
++# Lock the chroot we want to use. We'll keep this lock until we exit.
++lock 9 "$copydir.lock" "Locking chroot copy [%s]" "$copy"
++
++if [[ ! -d $copydir ]] || $clean_first; then
++ sync_chroot "$chrootdir" "$copy"
++fi
+
+ $update_first && arch-nspawn "$copydir" \
+ "${bindmounts_ro[@]}" "${bindmounts_rw[@]}" \
+ pacman -Syu --noconfirm
+
+-[[ -n ${install_pkgs[*]} ]] && install_packages
++if [[ -n ${install_pkgs[*]:-} ]]; then
++ install_packages "$copydir" "${install_pkgs[@]}"
++ ret=$?
++ # If there is no PKGBUILD we have done
++ [[ -f PKGBUILD ]] || exit $ret
++fi
+
+-download_sources
++download_sources "$copydir" "$src_owner"
+
+-prepare_chroot
++prepare_chroot "$copydir" "$USER_HOME" "$repack"
+
+ if arch-nspawn "$copydir" \
+ --bind-ro="$PWD:/startdir_host" \
+ --bind-ro="$SRCDEST:/srcdest_host" \
+ "${bindmounts_ro[@]}" "${bindmounts_rw[@]}" \
++ /chrootprepare "${makepkg_args[@]}" &&
++ arch-nspawn "$copydir" \
++ --bind-ro="$PWD:/startdir_host" \
++ --bind-ro="$SRCDEST:/srcdest_host" \
++ "${bindmounts_ro[@]}" "${bindmounts_rw[@]}" \
+ /chrootbuild "${makepkg_args[@]}"
+ then
+- move_products
++ move_products "$copydir" "$src_owner"
+ else
+ (( ret += 1 ))
+ fi
+
+-$temp_chroot && clean_temporary
++$temp_chroot && delete_chroot "$copydir"
+
+ if (( ret != 0 )); then
+ if $temp_chroot; then
+@@ -409,3 +520,4 @@
+ else
+ true
+ fi
++}
diff --git a/src/chroot-tools/mkarchroot.patch b/src/chroot-tools/mkarchroot.patch
new file mode 100644
index 0000000..3882ec6
--- /dev/null
+++ b/src/chroot-tools/mkarchroot.patch
@@ -0,0 +1,67 @@
+--- mkarchroot.in 2016-04-15 17:38:00.221067734 -0400
++++ mkarchroot.ugly 2016-05-09 22:36:18.284175885 -0400
+@@ -1,4 +1,6 @@
+ #!/bin/bash
++# License: GNU GPLv2
++#
+ # This program is free software; you can redistribute it and/or modify
+ # it under the terms of the GNU General Public License as published by
+ # the Free Software Foundation; version 2 of the License.
+@@ -14,23 +16,29 @@
+
+ working_dir=''
+
++files=()
++
+ usage() {
+ echo "Usage: ${0##*/} [options] working-dir package-list..."
+ echo ' options:'
+ echo ' -C <file> Location of a pacman config file'
+ echo ' -M <file> Location of a makepkg config file'
+ echo ' -c <dir> Set pacman cache'
++ echo ' -f <file> Copy file from the host to the chroot'
++ echo ' -s Do not run setarch'
+ echo ' -h This message'
+ exit 1
+ }
+
+ orig_argv=("$@")
+
+-while getopts 'hC:M:c:' arg; do
++while getopts 'hC:M:c:f:s' arg; do
+ case "$arg" in
+ C) pac_conf="$OPTARG" ;;
+ M) makepkg_conf="$OPTARG" ;;
+ c) cache_dir="$OPTARG" ;;
++ f) files+=("$OPTARG") ;;
++ s) nosetarch=1 ;;
+ h|?) usage ;;
+ *) error "invalid argument '%s'" "$arg"; usage ;;
+ esac
+@@ -68,6 +76,16 @@
+ chmod 0755 "$working_dir"
+ fi
+
++for file in "${files[@]}"; do
++ mkdir -p "$(dirname "$working_dir$file")"
++ cp "$file" "$working_dir$file"
++done
++
++_env=()
++while read -r varname; do
++ _env+=("$varname=${!varname}")
++done < <(declare -x | sed -r 's/^declare -x ([^=]*)=.*/\1/' | grep -i '_proxy$')
++env -i "${_env[@]}" \
+ pacstrap -GMcd ${pac_conf:+-C "$pac_conf"} "$working_dir" \
+ "${cache_dirs[@]/#/--cachedir=}" "$@" || die 'Failed to install all packages'
+
+@@ -75,7 +93,8 @@
+ echo 'LANG=C' > "$working_dir/etc/locale.conf"
+ echo "$CHROOT_VERSION" > "$working_dir/.arch-chroot"
+
+-exec arch-nspawn \
++exec "$(librelib chroot/arch-nspawn)" \
++ ${nosetarch:+-s} \
+ ${pac_conf:+-C "$pac_conf"} \
+ ${makepkg_conf:+-M "$makepkg_conf"} \
+ ${cache_dir:+-c "$cache_dir"} \
diff --git a/src/dagpkg b/src/dagpkg
new file mode 100755
index 0000000..1989b4d
--- /dev/null
+++ b/src/dagpkg
@@ -0,0 +1,222 @@
+#!/usr/bin/env bash
+#
+# dagpkg - create a directed graph of package dependencies and build
+# them in topological order
+
+# Copyright (C) 2014 Nicolás Reynolds <fauno@parabola.nu>
+# Copyright (C) 2014 Michał Masłowski <mtjm@mtjm.eu>
+#
+# License: GNU GPLv3+
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+set -e
+
+source libremessages
+source "$(librelib conf)"
+
+# Source variables from libretools
+load_files libretools
+check_vars libretools FULLBUILDCMD || exit 1
+#check_vars libretools HOOKPREBUILD HOOKLOCALRELEASE || exit 1 # optional
+
+# Source variables from makepkg
+load_files makepkg
+check_vars makepkg CARCH || exit 1
+
+# Globals:
+# - temp_dir
+# - log
+# - name (sort of, it's also local to visit_pkgbuild() )
+# - prev
+# - I
+# - marks
+# - various PKGBUILD variables:
+# - pkgbase/pkgname
+# - epoch/pkgver/pkgrel
+# - arch
+# - {,make,check}depends
+
+# End inmediately but print an useful message
+trap_exit() {
+ local signal=$1; shift
+ local msg=("$@")
+ term_title "error!"
+ echo
+ error "(%s) %s (leftovers on %s)" \
+ "${0##*/}" "$(print "${msg[@]}")" "${temp_dir}"
+ trap -- "$signal"
+ kill "-$signal" "$$"
+}
+
+setup_traps trap_exit
+
+source_pkgbuild() {
+ # Source this PKGBUILD, if it doesn't exist, exit
+ if ! load_PKGBUILD &>/dev/null; then
+ error "No PKGBUILD in %s" "$PWD"
+ exit 1
+ fi
+
+ # Save resources
+ # This is intentionally less exhaustive than unset_PKGBUILD()
+ # XXX: document which things we actually *want* to not be unset.
+ unset pkgdesc license groups backup install md5sums sha1sums \
+ sha256sums source options &>/dev/null
+
+ unset build package &>/dev/null
+
+ local _pkg
+ for _pkg in "${pkgname[@]}"; do
+ unset "package_${_pkg}" &>/dev/null || true
+ done
+
+ # This is the name of the package
+ name="${pkgbase:-${pkgname[0]}}"
+}
+
+source_pkgbuild
+
+# A temporary work dir and log file
+temp_dir="${1:-$(mktemp -dt ${name}-testpkg-XXXX)}"
+log="${temp_dir}/buildorder"
+
+# Mark array for DFS-based topological sort. See
+# https://en.wikipedia.org/wiki/Topological_sort for an explanation of
+# the algorithm. Key: package name, value: 0 for unvisited package, 1
+# during visit, 2 after visit.
+declare -A marks
+
+# Visit a PKGBUILD for graph building.
+visit_pkgbuild() {
+ # The name of the previous package
+ prev="${1}"
+
+ local name
+ source_pkgbuild
+
+ # If it's already built we don't bother
+ if is_built "${pkgname[0]}" "$(get_full_version "${pkgname[0]}")"; then
+ return
+ fi
+
+ # Detect cycle or already visited package
+ case "${marks[$name]:-0}" in
+ 1) msg2 "cycle found with %s depending on %s" $prev $name
+ exit 1;;
+ 2) return;;
+ esac
+
+ msg "%s (%s)" ${name} ${prev}
+
+ if ! in_array "${CARCH}" "${arch[@]}"; then
+ warning "%s isn't ported to %s yet" ${name} ${CARCH}
+ fi
+
+ # If the envvar I contains this package, ignore it and exit
+ if in_array "$name" $I; then
+ msg2 "%s ignored" ${name}
+ return
+ fi
+
+ # Mark the package as being visited
+ marks[$name]=1
+
+ # Recurse into dependencies
+ local d
+ for d in "${depends[@]}" "${makedepends[@]}" "${checkdepends[@]}"; do
+ # Cleanup dependency versions
+ d=$(echo $d | sed "s/[<>=].*//")
+
+ # Where's the pkgbuild?
+ local w=$(toru-where $d)
+
+ # Skip if not available
+ test -z "$w" && continue
+
+ # Go to this dir
+ pushd $w &>/dev/null
+
+ visit_pkgbuild "$name"
+
+ popd &>/dev/null
+ done
+
+ # Mark the package as finished
+ marks[$name]=2
+ # Append it to the reversed list of packages to build.
+ echo "$name" >> "${log}"
+}
+
+# If we specified a work dir on the cli it means we want to skip
+# dependency graph creation and jump to build whatever is there
+if [ -z "${1}" ]; then
+ # Visit the root PKGBUILD to make the graph.
+ visit_pkgbuild ""
+else
+ msg "Resuming build..."
+fi
+
+# enter work dir
+pushd "${temp_dir}" &>/dev/null
+nl ${log} | while read order pkg; do
+ # skip if already built
+ if test -f "${pkg}/built_ok"; then
+ warning "tried to build %s twice" "%{pkg}"
+ continue
+ fi
+
+ # where's this package?
+ local w="$(toru-where "$pkg")"
+ test -z "$w" && continue
+
+ # copy to work dir if not already
+ # this means you can make modifications to the pkgbuild during the
+ # graph build or remove the dir after a build failure and let dagpkg
+ # copy a new version
+ test -d "$pkg" || cp -r "$w" "$pkg"
+ pushd "$pkg" &>/dev/null
+
+ term_title "%s(%s)" "$pkg" "$order"
+
+ msg "Building %s" ${pkg}
+
+ # upgrade the system
+ # this would probably have to go on HOOKPREBUILD if you're working
+ # outside chroots
+ sudo -E pacman -Syu --noconfirm
+
+ # run the pre build command from libretools.conf
+ if [[ -n "$HOOKPREBUILD" ]]; then
+ ${HOOKPREBUILD}
+ fi
+
+ # run the build command
+ ${FULLBUILDCMD}
+
+ # Run local release hook with $1 = $repo
+ if [[ -n "$HOOKLOCALRELEASE" ]]; then
+ ${HOOKLOCALRELEASE} "$(basename "$(dirname "$w")")"
+ fi
+
+ # it's built!
+ touch built_ok
+
+ popd &>/dev/null
+done
+
+popd &>/dev/null
+# cleanup
+rm -rf ${log} "${temp_dir}"
+
+term_title "done"
diff --git a/src/devtools/.gitignore b/src/devtools/.gitignore
new file mode 100644
index 0000000..d669bbd
--- /dev/null
+++ b/src/devtools/.gitignore
@@ -0,0 +1,4 @@
+*
+!Makefile
+!.gitignore
+!*.patch
diff --git a/src/devtools/Makefile b/src/devtools/Makefile
new file mode 100644
index 0000000..72907f0
--- /dev/null
+++ b/src/devtools/Makefile
@@ -0,0 +1,12 @@
+include $(dir $(lastword $(MAKEFILE_LIST)))/../../config.mk
+include $(topsrcdir)/automake.head.mk
+
+libretools-bins += checkpkg find-libdeps finddeps lddd
+devtools-files = $(addsuffix .in,$(libretools-bins))
+am_sys_files += $(bindir)/find-libprovides
+
+$(DESTDIR)$(bindir)/find-libprovides:
+ install -d $(@D)
+ ln -sf find-libdeps $@
+
+include $(topsrcdir)/automake.tail.mk
diff --git a/src/devtools/checkpkg.patch b/src/devtools/checkpkg.patch
new file mode 100644
index 0000000..5cfb794
--- /dev/null
+++ b/src/devtools/checkpkg.patch
@@ -0,0 +1,45 @@
+--- checkpkg.in 2014-03-21 13:59:31.849658036 -0400
++++ checkpkg.ugly 2014-03-21 14:20:52.340291982 -0400
+@@ -1,8 +1,30 @@
+ #!/bin/bash
++# License: Unspecified
+
+ shopt -s extglob
+
+-m4_include(lib/common.sh)
++. "$(librelib messages)"
++
++usage() {
++ print 'Usage: %s [-h]' "${0##*/}"
++ print 'Compare a locally built a package with the one in the repositories.'
++ echo
++ prose 'This should be run from a directory containing a
++ PKGBUILD. It searches for a locally built package
++ corresponding to the PKGBUILD, and downloads the last
++ version of that package from the pacman repositories.
++ It then compares the list of .so files provided by each
++ version of the package. It does this for each part of
++ a split package.'
++}
++
++if [[ $1 = '-h' ]]; then
++ usage
++ exit 0
++elif [[ $# -gt 0 ]]; then
++ usage >&2
++ exit 1
++fi
+
+ # Source makepkg.conf; fail if it is not found
+ if [[ -r '/etc/makepkg.conf' ]]; then
+@@ -17,7 +39,9 @@
+ fi
+
+ if [[ ! -f PKGBUILD ]]; then
+- die 'This must be run in the directory of a built package.'
++ error 'This must be run in the directory of a built package.'
++ usage >&2
++ exit 1
+ fi
+
+ . ./PKGBUILD
diff --git a/src/devtools/find-libdeps.patch b/src/devtools/find-libdeps.patch
new file mode 100644
index 0000000..1bcffc9
--- /dev/null
+++ b/src/devtools/find-libdeps.patch
@@ -0,0 +1,47 @@
+--- find-libdeps.in 2014-03-21 13:59:32.059649315 -0400
++++ find-libdeps.ugly 2014-03-21 14:21:18.955744982 -0400
+@@ -1,6 +1,7 @@
+ #!/bin/bash
++# License: Unspecified
+
+-m4_include(lib/common.sh)
++. "$(librelib messages)"
+
+ set -e
+ shopt -s extglob
+@@ -19,12 +20,32 @@
+ *) die "Unknown mode %s" "$script_mode" ;;
+ esac
+
++usage() {
++ print "Usage: find-lib(deps|provides) [options] <package file|extracted package dir>"
++ print "Find library dependencies or provides of a package."
++ echo
++ prose 'Prints a list of library dependencies in the format:'
++ echo
++ print ' <soname>=<soversion>-<soarch>'
++ echo
++ prose 'Where <soversion> is the shared library version, or
++ <soname> repeated if there is no version attached; and
++ <soarch> is the architecture of the library (either `32`
++ or `64`, based on the ELF Class).'
++ echo
++ print "Options:"
++ flag "--ignore-internal" "Ignore internal libraries; libraries
++ without a version attached"
++ flag "-h" "Show this message"
++}
+ if [[ -z $1 ]]; then
+- echo "${0##*/} [options] <package file|extracted package dir>"
+- echo "Options:"
+- echo " --ignore-internal ignore internal libraries"
++ usage >&2
+ exit 1
+ fi
++if [[ $1 = '-h' ]]; then
++ usage
++ exit 0
++fi
+
+ if [[ -d $1 ]]; then
+ pushd $1 >/dev/null
diff --git a/src/devtools/finddeps.patch b/src/devtools/finddeps.patch
new file mode 100644
index 0000000..caf2121
--- /dev/null
+++ b/src/devtools/finddeps.patch
@@ -0,0 +1,35 @@
+--- finddeps.in 2014-03-21 13:59:32.249641424 -0400
++++ finddeps.ugly 2014-03-21 14:21:09.949489174 -0400
+@@ -2,19 +2,26 @@
+ #
+ # finddeps - find packages that depend on a given depname
+ #
++# License: Unspecified
+
+-m4_include(lib/common.sh)
++. "$(librelib messages)"
+
+ match=$1
+
++usage() {
++ print 'Usage: %s <depname>' "${0##*/}"
++ print 'Find packages that depend on a given depname.'
++ echo
++ prose 'Run this script from the top-level directory of your ABS tree.'
++}
+ if [[ -z $match ]]; then
+- echo 'Usage: finddeps <depname>'
+- echo ''
+- echo 'Find packages that depend on a given depname.'
+- echo 'Run this script from the top-level directory of your ABS tree.'
+- echo ''
++ usage >&2
+ exit 1
+ fi
++if [[ $match = '-h' ]]; then
++ usage
++ exit 0
++fi
+
+ find . -type d | while read d; do
+ if [[ -f "$d/PKGBUILD" ]]; then
diff --git a/src/devtools/lddd.patch b/src/devtools/lddd.patch
new file mode 100644
index 0000000..6383aa4
--- /dev/null
+++ b/src/devtools/lddd.patch
@@ -0,0 +1,29 @@
+--- lddd.in 2014-03-21 13:59:32.419634364 -0400
++++ lddd.ugly 2014-03-21 14:21:31.538503947 -0400
+@@ -2,8 +2,25 @@
+ #
+ # lddd - find broken library links on your machine
+ #
++# License: Unspecified
+
+-m4_include(lib/common.sh)
++. "$(librelib messages)"
++
++usage() {
++ print "Usage: %s [-h]" "${0##*/}"
++ print "Find broken library links on your machine."
++ echo
++ prose 'Scans $PATH and library directories for ELF files with
++ references to missing shared libraries.'
++}
++
++if [[ $1 = '-h' ]]; then
++ usage
++ exit 0
++elif [[ $# -gt 0 ]]; then
++ usage >&2
++ exit 1
++fi
+
+ ifs=$IFS
+ IFS="${IFS}:"
diff --git a/src/gitget/Makefile b/src/gitget/Makefile
new file mode 100644
index 0000000..2903f4a
--- /dev/null
+++ b/src/gitget/Makefile
@@ -0,0 +1,4 @@
+include $(dir $(lastword $(MAKEFILE_LIST)))/../../config.mk
+include $(topsrcdir)/automake.head.mk
+
+include $(topsrcdir)/automake.tail.mk
diff --git a/src/gitget/gitget b/src/gitget/gitget
new file mode 100755
index 0000000..4d127c7
--- /dev/null
+++ b/src/gitget/gitget
@@ -0,0 +1,221 @@
+#!/usr/bin/env bash
+
+# Copyright (C) 2012-2013 Pacman Development Team <pacman-dev@archlinux.org>
+# Copyright (C) 2012-2013 Luke Shumaker <lukeshu@sbcglobal.net>
+#
+# License: GNU GPLv2+
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+. libremessages
+
+# from makepkg
+dir_is_empty() {
+ (
+ shopt -s dotglob nullglob
+ files=("$1"/*)
+ (( ${#files} == 0 ))
+ )
+}
+
+# from makepkg
+cd_safe() {
+ if ! cd "$1"; then
+ error "Failed to change to directory %s" "$1"
+ plain "Aborting..."
+ exit 1
+ fi
+}
+
+# from makepkg
+download_git_checkout() {
+ local url=$1
+ local ref=$2
+ local dir=$3
+ local name=$4
+ local push=${5:-}
+
+ if [[ ! -d "$dir/.git" ]] ; then
+ msg2 "Cloning %s %s repo..." "${name}" "git"
+ if ! git clone "$url" "$dir"; then
+ error "Failure while downloading %s %s repo" "${name}" "git"
+ plain "Aborting..."
+ exit 1
+ fi
+ cd_safe "$dir"
+ if [[ -n $push ]]; then
+ git config remote.origin.pushUrl "$push"
+ fi
+ git checkout "$ref"
+ else
+ cd_safe "$dir"
+ # Make sure we are fetching the right repo
+ if [[ "$url" != "$(git config --get remote.origin.url)" ]] ; then
+ if $FORCE; then
+ git config remote.origin.url "$url"
+ else
+ error "%s is not a clone of %s" "$dir" "$url"
+ plain "Aborting..."
+ exit 1
+ fi
+ fi
+ if [[ -n $push ]] ; then
+ if $FORCE; then
+ git config remote.origin.pushUrl "$push"
+ else
+ local curpush="$(git config --get remote.origin.pushUrl)"
+ if [[ $? != 0 ]] ; then
+ error "%s does not have a %s configured" pushUrl "$name"
+ plain "Aborting..."
+ exit 1
+ elif [[ $curpush != "$push" ]]; then
+ error "%s has %s configured, but it doesn't match %s" "$name" pushUrl "$push"
+ plain "Aborting..."
+ exit 1
+ fi
+ fi
+ fi
+ msg2 "Updating %s %s repo..." "${name}" "git"
+ if ! { git fetch --all -p && git checkout "$ref" && git pull origin "$ref"; } ; then
+ # only warn on failure to allow offline builds
+ warning "Failure while updating %s %s repo" "${repo}" "git"
+ fi
+ fi
+}
+
+# from makepkg
+download_git_bare() {
+ local url=$1
+ local dir=$2
+ local name=$3
+ local push=${4:-}
+
+ if [[ ! -d "$dir" ]] || dir_is_empty "$dir" ; then
+ msg2 "Cloning %s %s repo..." "${name}" "git"
+ if ! git clone --mirror "$url" "$dir"; then
+ error "Failure while downloading %s %s repo" "${name}" "git"
+ plain "Aborting..."
+ exit 1
+ fi
+ if [[ -n $push ]]; then
+ cd_safe "$dir"
+ git config remote.origin.pushUrl "$push"
+ fi
+ else
+ cd_safe "$dir"
+ # Make sure we are fetching the right repo
+ if [[ "$url" != "$(git config --get remote.origin.url)" ]] ; then
+ error "%s is not a clone of %s" "$dir" "$url"
+ plain "Aborting..."
+ exit 1
+ fi
+ if [[ -n $push ]] ; then
+ if $FORCE; then
+ git config remote.origin.pushUrl "$push"
+ else
+ local curpush="$(git config --get remote.origin.pushUrl)"
+ if [[ $? != 0 ]] ; then
+ error "%s does not have a %s configured" pushUrl "$name"
+ plain "Aborting..."
+ exit 1
+ elif [[ $curpush != "$push" ]]; then
+ error "%s has %s configured, but it doesn't match %s" "$name" pushUrl "$push"
+ plain "Aborting..."
+ exit 1
+ fi
+ fi
+ fi
+ msg2 "Updating %s %s repo..." "${name}" "git"
+ if ! git fetch --all -p; then
+ # only warn on failure to allow offline builds
+ warning "Failure while updating %s %s repo" "${name}" "git"
+ fi
+ fi
+}
+
+usage() {
+ print 'Usage: %s [OPTIONS] [bare|checkout] URL DIRECTORY' "${0##*/}"
+ print 'A URL-handler for git urls. Capable of updating or cloning.'
+ echo
+ prose "Clones or pulls from the git URL, to a local DIRECTORY. If
+ \`bare\` is specified, it will create a bare repository; if
+ \`checkout\` is specified, it will create or update a working tree."
+ echo
+ prose 'For a checkout, the tree to checkout is specified by appending
+ `#ANYTHING=TREE-ISH` to the URL. For example, `#branch=stable`,
+ or `#tag=v12.3`. Whatever is on the left side of the equal sign
+ is ignored, this is for compatibility with `makepkg` source
+ URLs.'
+ echo
+ prose "The URL may be prefixed with \`git+\`. This is also for
+ compatibility with \`makepkg\` source URLs."
+ echo
+ prose "It does safety checks, figures out whether to clone or pull, and
+ other helpful things. This exists because the same
+ \`download_git\` function from makepkg was being copied and
+ pasted again and again."
+ echo
+ print "Options:"
+ flag '-f' \
+ 'Instead of checking to make sure configured URLs match, force
+ the update, and set the URLs.'
+ flag "-p $(_ URL)" \
+ 'In addition to setting or checking `remotes.origin.url`, also
+ set or check `remotes.origin.pushUrl`'
+ flag "-n $(_ NAME)" \
+ 'In messages, instead of using the basename of DIRECTORY as the
+ repository name, use NAME'
+ flag '-h' 'Show this message'
+}
+
+FORCE=false
+main() {
+ local push=''
+ local name=''
+ while getopts 'fp:n:h' flag; do
+ case "${flag}" in
+ f) FORCE=true;;
+ p) push=$OPTARG;;
+ n) name=$OPTARG;;
+ h) usage; return 0;;
+ *) usage >&2; return 1;;
+ esac
+ done
+ shift $(($OPTIND - 1))
+ [[ $# == 3 ]] || { usage >&2; return 1; }
+ local mode=$1
+ local url=${2#git+}
+ local dir=$3
+
+ local urlmain=${url%%#*}
+ local urlfrag=${url#*#}
+ if [[ "$urlfrag" == "$urlmain" ]]; then
+ urlfrag=''
+ fi
+ local ref=${urlfrag#*=}
+
+ if [[ -z $ref ]]; then
+ ref=master
+ fi
+
+ name=${name:-${dir##*/}}
+
+ case "$mode" in
+ checkout) download_git_checkout "$urlmain" "$ref" "$dir" "$name" "$push";;
+ bare) download_git_bare "$urlmain" "$dir" "$name" "$push";;
+ *) usage >&2; return 1;;
+ esac
+}
+
+main "$@"
diff --git a/src/gitget/libregit b/src/gitget/libregit
new file mode 100755
index 0000000..e45c3a8
--- /dev/null
+++ b/src/gitget/libregit
@@ -0,0 +1,43 @@
+#!/usr/bin/env bash
+
+# Copyright (C) 2012-2013 Luke Shumaker <lukeshu@sbcglobal.net>
+#
+# License: GNU GPLv2+
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+. libremessages
+
+usage() {
+ print 'Usage: %s REPO REF DIR' "${0##*/}"
+ print 'A compatibility wrapper around `gitget checkout`'
+ echo
+ prose "This exists because gitget used to be called libregit, and took
+ the arguments in this format, and I'm sure there are a few
+ scripts floating around that use it."
+ echo
+ prose "Clones or pulls from the git URL '<REPO>', and checks out the git
+ ref '<REF>' to the directory '<DIR>'."
+}
+
+main() {
+ [[ $# == 3 ]] || { usage >&2; return 1; }
+ repo=$1
+ ref=$2
+ dir=$3
+
+ gitget checkout "${repo}#ref=${ref}" "${dir}"
+}
+
+main "$@"
diff --git a/src/is_built b/src/is_built
new file mode 100755
index 0000000..29b2744
--- /dev/null
+++ b/src/is_built
@@ -0,0 +1,69 @@
+#!/usr/bin/env bash
+# Copyright (C) 2011-2012 Nicolás Reynolds <fauno@parabola.nu>
+# Copyright (C) 2011-2012 Joshua Ismael Haase Hernández (xihh) <hahj87@gmail.com>
+# Copyright (C) 2013 Luke Shumaker <lukeshu@sbcglobal.net>
+#
+# License: GNU GPLv3+
+#
+# This file is part of Parabola.
+#
+# Parabola is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Parabola is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Parabola. If not, see <http://www.gnu.org/licenses/>.
+
+. libremessages
+
+usage() {
+ print "Usage: %s [-h] PKGNAME [PKGVER]" "${0##*/}" "${0##*/}"
+ print 'Detect iv a given package (version) is already in the repos'
+ echo
+ prose "If a version is specified, it assumed that you want a greater or
+ equal version."
+ echo
+ prose "Example usage:"
+ print " $ %s 'pcre' '20'" "${0##*/}"
+ echo
+ print "Exit status:"
+ print " 0: The package is built"
+ print " 1: The package has not built"
+ print " >1: There was an error"
+}
+
+while getopts 'h' arg; do
+ case $arg in
+ h) usage; exit 0 ;;
+ *) usage >&2; exit 2 ;;
+ esac
+done
+if [[ $# -ne 1 ]] && [[ $# -ne 2 ]]; then
+ usage >&2
+ exit 2
+fi
+
+pkg=${1}
+ver=${2:-0}
+pver=$(LC_ALL=C pacman -Sddp --print-format '%v' "${pkg}" 2>/dev/null)
+
+# if pacman fails or returns nothing
+r=$?
+
+result=$(vercmp "${pver}" "${ver}")
+# result:
+# -1 : pver < ver
+# 0 : pver = ver
+# 1 : pver > ver
+
+if [[ $result -ge 0 ]] && [[ $r -eq 0 ]]; then
+ exit 0
+else
+ exit 1
+fi
diff --git a/src/lib/.gitignore b/src/lib/.gitignore
new file mode 100644
index 0000000..650c85f
--- /dev/null
+++ b/src/lib/.gitignore
@@ -0,0 +1,3 @@
+common.sh
+common.sh.in
+conf.sh
diff --git a/src/lib/HACKING.md b/src/lib/HACKING.md
new file mode 100644
index 0000000..8bebaf6
--- /dev/null
+++ b/src/lib/HACKING.md
@@ -0,0 +1,15 @@
+Special stuff about hacking ih the /src/lib directory:
+
+ - Everything should be GPLv2 AND GPLv3 compatible. No GPLv3 only.
+ - Name a file `libre${NAME}` if it should be executable directly, or
+ `${name}.sh` if it should only be available to be sourced.
+ - When printing a message that is internal to /src/lib, and not part
+ of the programm calling the library; prefix the print command with
+ `_l`. `_l()` is defined in `common.sh` (and `librelib`, since it
+ cannot use any libraries itself).
+ - When changing the message functions, be aware that some are
+ duplicated in:
+ * /src/chroot-tools/chcleanup
+ * /src/chroot-tools/distcc-tool
+ * /src/lib/librelib
+ And that they probably need to be updated as well.
diff --git a/src/lib/Makefile b/src/lib/Makefile
new file mode 100644
index 0000000..9e9b4a8
--- /dev/null
+++ b/src/lib/Makefile
@@ -0,0 +1,45 @@
+include $(dir $(lastword $(MAKEFILE_LIST)))/../../config.mk
+include $(topsrcdir)/automake.head.mk
+
+libretools-libs += common.sh conf.sh
+devtools-files = common.sh.in
+
+# Build ##############################################################
+
+$(outdir)/conf.sh: $(var)sysconfdir $(var)pkgconfdir
+
+$(outdir)/common.sh: $(outdir)/%: $(srcdir)/%.in $(srcdir)/%.head $(srcdir)/%.tail $(outdir)/Makefile
+ @echo "OUT $@"
+ @{ \
+ cat '$(<D)/$*.head' && \
+ echo && \
+ sed -r \
+ -e '/encoding problem/d;/LANG=/d' \
+ -e 's/mesg=\$$(.)/mesg="$$(_ "$$\1")"/' \
+ -e 's/gettext /_l _ /g' \
+ -e "s/^(\s+)(msg|error) '/\1_l \2 '/" \
+ -e 's|lock\(\)\s*\{|lock()\n{|' \
+ '$(<D)/$*.in' && \
+ echo && \
+ cat '$(<D)/$*.tail' && \
+ :; } > '$@'
+
+# Translate ##########################################################
+
+$(outdir)/blacklist.sh.pot: $(srcdir)/blacklist.sh $(srcdir)/librexgettext
+ @echo "OUT $@"
+ @{ \
+ sed -n '/^# Usage:/,/()/{ /^#/ { =; p; } }' $< | \
+ sed -r -e 's/^# (.*)/msgid "\1"\nmsgstr ""\n/' \
+ -e 's|^[0-9]*$$|#. embedded usage text\n#: $<:&|' && \
+ $(<D)/librexgettext --simple=_l:2 $< && \
+ :; } | $(pofmt) > $@
+$(outdir)/common.sh.pot : LIBREXGETTEXT_FLAGS += --simple=_l:2
+$(outdir)/conf.sh.pot : LIBREXGETTEXT_FLAGS += --simple=_l:2
+$(outdir)/librelib.pot : LIBREXGETTEXT_FLAGS += --simple=_l:2
+$(outdir)/messages.sh.pot : LIBREXGETTEXT_FLAGS += --simple=_l:2
+$(outdir)/librexgettext.pot: LIBREXGETTEXT_FLAGS += --simple=errusage
+
+######################################################################
+
+include $(topsrcdir)/automake.tail.mk
diff --git a/src/lib/blacklist.sh b/src/lib/blacklist.sh
new file mode 100644
index 0000000..0a3cc39
--- /dev/null
+++ b/src/lib/blacklist.sh
@@ -0,0 +1,96 @@
+#!/usr/bin/env bash
+# This may be included with or without `set -euE`
+
+# Copyright (C) 2013-2014, 2016 Luke Shumaker <lukeshu@sbcglobal.net>
+#
+# License: GNU GPLv2+
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+# make sure XDG_CACHE_HOME is set
+. "$(librelib conf)"
+
+# Usage: blacklist-normalize <$file
+# Normalizes the syntax of the blacklist on stdin.
+blacklist-normalize() {
+ sed -e '/^#/d' -e 's/^[^:]*$/&::/' -e 's/^[^:]*:[^:]*$/&:/'
+}
+
+# Usage: blacklist-cat
+# Prints the blacklist.
+# Uses the cache, but downloads it if it doesn't exist. Also normalizes the blacklist for easier parsing.
+blacklist-cat() {
+ local file="$XDG_CACHE_HOME/libretools/blacklist.txt"
+ if ! [[ -e $file ]]; then
+ # exit on failure, whether set -e or not
+ blacklist-update || return $?
+ fi
+ blacklist-normalize < "$file"
+}
+
+# Usage: blacklist-update
+# Updates (or creates) the cached copy of the blacklist.
+blacklist-update() (
+ . libremessages
+ load_files libretools || return 1
+ check_vars libretools BLACKLIST || return 1
+
+ local remote_blacklist="$BLACKLIST"
+ local local_blacklist="$XDG_CACHE_HOME/libretools/blacklist.txt"
+
+ _l stat_busy "Downloading blacklist of proprietary software packages"
+
+ mkdir -p "${local_blacklist%/*}"
+ if wget -N -q -O "${local_blacklist}.part" "$remote_blacklist" 2>/dev/null; then
+ stat_done
+ mv -f "${local_blacklist}.part" "$local_blacklist"
+ else
+ stat_done
+ rm "${local_blacklist}.part"
+ if [[ -e "$local_blacklist" ]]; then
+ _l warning "Using local copy of blacklist"
+ else
+ _l error "Download failed, exiting"
+ return 1
+ fi
+
+ fi
+)
+
+# Usage: blacklist-cat | blacklist-lookup $pkgname
+# Filters to obtain the line for $pkgname from the blacklist on stdin.
+# Exits successfully whether a line is found or not.
+blacklist-lookup() {
+ local pkg=$1
+ # we accept that $pkg contains no regex-nes
+ blacklist-normalize | grep "^$pkg:" || true
+}
+
+# Usage: blacklist-cat | blacklist-get-pkg
+# Prints only the package name field of the blacklist line(s) on stdin.
+blacklist-get-pkg() {
+ blacklist-normalize | cut -d: -f1
+}
+
+# Usage: blacklist-cat | blacklist-get-rep
+# Prints only the replacement package field of the blacklist line(s) on stdin.
+blacklist-get-rep() {
+ blacklist-normalize | cut -d: -f2
+}
+
+# Usage: blacklist-cat | blacklist-get-reason
+# Prints only the reason field of the blacklist line(s) on stdin.
+blacklist-get-reason() {
+ blacklist-normalize | cut -d: -f3-
+}
diff --git a/src/lib/blacklist.sh.3.ronn b/src/lib/blacklist.sh.3.ronn
new file mode 120000
index 0000000..9001445
--- /dev/null
+++ b/src/lib/blacklist.sh.3.ronn
@@ -0,0 +1 @@
+libreblacklist.1.ronn \ No newline at end of file
diff --git a/src/lib/common.sh.3.ronn b/src/lib/common.sh.3.ronn
new file mode 100644
index 0000000..30003e0
--- /dev/null
+++ b/src/lib/common.sh.3.ronn
@@ -0,0 +1,16 @@
+common.sh -- common Bash routines from devtools
+===============================================
+
+## SYNOPSIS
+
+`. "$(librelib common)"`
+
+## DESCRIPTION
+
+In short, don't use this. Use `libremessages`(1) instead.
+`libremessages` uses this internally.
+
+## SEE ALSO
+
+ * librelib(7)
+ * libremessages(1)/messages.sh(3)
diff --git a/src/lib/common.sh.head b/src/lib/common.sh.head
new file mode 100644
index 0000000..23bfeb8
--- /dev/null
+++ b/src/lib/common.sh.head
@@ -0,0 +1,25 @@
+#!/hint/bash
+# This may be included with or without `set -euE`
+
+# This file is included by libremessages.
+# You should probably use libremessages instead of this.
+
+# License: Unspecified
+
+shopt -s extglob
+
+if [[ -z ${_INCLUDE_COMMON_SH:-} ]]; then
+_INCLUDE_COMMON_SH=true
+
+[[ -n ${TEXTDOMAIN:-} ]] || export TEXTDOMAIN='libretools'
+[[ -n ${TEXTDOMAINDIR:-} ]] || export TEXTDOMAINDIR='/usr/share/locale'
+
+if type gettext &>/dev/null; then
+ _() { gettext "$@"; }
+else
+ _() { echo "$@"; }
+fi
+
+_l() {
+ TEXTDOMAIN='librelib' TEXTDOMAINDIR='/usr/share/locale' "$@"
+}
diff --git a/src/lib/common.sh.tail b/src/lib/common.sh.tail
new file mode 100644
index 0000000..e133fad
--- /dev/null
+++ b/src/lib/common.sh.tail
@@ -0,0 +1 @@
+fi
diff --git a/src/lib/conf.sh.3.ronn b/src/lib/conf.sh.3.ronn
new file mode 100644
index 0000000..0974bdb
--- /dev/null
+++ b/src/lib/conf.sh.3.ronn
@@ -0,0 +1,126 @@
+conf.sh(3) -- easy loading of configuration files
+=================================================
+
+## SYNOPSIS
+
+`. "$(librelib conf)"`
+
+## DESCRIPTION
+
+`conf.sh` is a Bash(1) library to easily load various configuration
+files related to Arch Linux/Parabola(7) and libretools(7).
+
+### VARIABLES
+
+When loading configuration files in a program run with `sudo`(8), it
+is often desirable to load the configuration files from the home
+directory of the user who called `sudo`, instead of from /root.
+
+To accommodate this, instead of using the usual $<USER> and $<HOME>,
+`conf.sh` sets $<LIBREUSER> and $<LIBREHOME>, which it then uses.
+
+ * <LIBREUSER>:
+ If $<SUDO_USER> is set, then $<LIBREUSER> is set to
+ that. Otherwise, $<LIBREUSER> is set to the value of $<USER>.
+ * <LIBREHOME>:
+ If $<LIBREUSER> == $<USER>, then $<LIBREHOME> is set to the value
+ of $<HOME>. Otherwise, it is set to the default home directory
+ of the user $<LIBREUSER>.
+
+Further, `conf.sh` works with XDG; it sets and uses
+$<XDG_CONFIG_HOME> and $<XDG_CACHE_HOME> with the following values:
+
+ * <XDG_CONFIG_HOME>:
+ If it isn't already set, it is set to "$<LIBREHOME>/.config" and
+ exported.
+ * <XDG_CACHE_HOME>:
+ If it isn't already set, it is set to "$<LIBREHOME>/.cache" and
+ exported.
+
+Note that only the XDG_* variables are exported.
+
+### GENERIC ROUTINES
+
+The following routines take a "slug" to refer to a group of
+configuration files; that is the basename of the files. For example,
+<SLUG>='abs' will identify `/etc/abs.conf` and `$<LIBREHOME>/.abs.conf`.
+
+The routines you will likely actually use are:
+
+ * `load_files` <SLUG>:
+ Loads the configuration files for <SLUG>, loading the files in
+ the correct order, and checking the appropriate environmental
+ variables.
+ * `check_vars` <SLUG> <VARS>...:
+ Checks to see if all of <VARS> are defined. If any of them
+ aren't, it prints a message telling the user to configure them in
+ the configuration files for <SLUG>, and returns with a non-zero
+ status.
+ * `get_var` <SLUG> <VAR> <DEFAULT>:
+ If <VAR> is set in the configuration for <SLUG>, print its
+ value, considering environmental variables. If it is not set,
+ return <DEFAULT>. This does NOT work for array variables.
+ * `set_var` <SLUG> <VAR> <VALUE>:
+ Set the variable <VAR> equal to <VALUE> in the configuration file
+ for <SLUG> of highest precedence that already exists and is
+ writable. If no files fit this description, the routine does
+ nothing and returns a non-zero exit status. This does NOT work
+ for array variables.
+
+There are two more routines the above routines use internally that are
+used internally by by the above routines. You are unlikely to use
+them directly, but they might be useful for debugging, or at least
+describing behavior.
+
+ * `list_files` <SLUG>:
+ Lists (newline-separated) the configuration files that must be
+ considered for <SLUG>. Files listed later take precedence over
+ files listed earlier.
+ * `list_envvars` <SLUG>:
+ Lists (newline-separated) the environmental variables that take
+ precedence over the settings in the configuration files for
+ <SLUG>. For example, in `makepkg.conf`(8) (<SLUG>=makepkg), if the
+ <PACKAGER> environmental variable is set, the value in the
+ configuration file is ignored.
+
+### PKGBUILD ROUTINES
+
+These two routines deal with loading `PKGBUILD`(5) files.
+
+ * `unset_PKGBUILD`:
+ Unsets all variables and functions that might be set in a
+ `PKGBUILD`(5) file, including those specific to `librefetch`(8).
+ * `load_PKGBUILD` [<FILE>]:
+ "Safely" loads a PKGBUILD. Everything that it would normally set
+ is unset first, $<CARCH> is set according to `makepkg.conf`(5),
+ then the file is loaded. The file to be loaded is <FILE>, or
+ "./PKGBUILD" by default. This isn't safe, security wise, in that
+ the PKGBUILD is free to execute code.
+
+### SLUGS
+
+The differences in behavior for anything that takes a slug comes down
+to the differences in the output for `list_files` and `list_envvars`.
+
+The "known" slugs are "abs", "makepkg", "libretools", and anything
+beginning with "xbs". If anything else is given, then:
+
+ * `list_files` will give back "/etc/libretools.d/<SLUG>.conf" and
+ "$<XDG_CONFIG_HOME>/libretools/<SLUG>.conf"
+ * `list_envvars` will give back an empty list.
+
+The rules for <SLUG>=(abs|makepkg|libretools) are more easily
+expressed in code than prose, so it is recommended that you look at
+that.
+
+## BUGS
+
+`get_var` and `set_var` do not work with arrays.
+
+## SEE ALSO
+
+librelib(7)
+
+abs.conf(5), makepkg.conf(5), libretools.conf(5), PKGBUILD(5)
+
+chroot.conf(5), librefetch.conf(5)
diff --git a/src/lib/conf.sh.in b/src/lib/conf.sh.in
new file mode 100644
index 0000000..8394801
--- /dev/null
+++ b/src/lib/conf.sh.in
@@ -0,0 +1,243 @@
+#!/hint/bash
+# This may be included with or without `set -euE`
+
+# Copyright (C) 2012-2015 Luke Shumaker <lukeshu@sbcglobal.net>
+#
+# License: GNU GPLv2+
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+LIBREUSER="${SUDO_USER:-$USER}"
+if [[ $LIBREUSER == $USER ]]; then
+ LIBREHOME=$HOME
+else
+ eval "LIBREHOME=~$LIBREUSER"
+fi
+if [[ -z ${XDG_CONFIG_HOME:-} ]]; then
+ export XDG_CONFIG_HOME="${LIBREHOME}/.config"
+fi
+if [[ -z ${XDG_CACHE_HOME:-} ]]; then
+ export XDG_CACHE_HOME="${LIBREHOME}/.cache"
+fi
+
+# Low-level generic functions ##################################################
+
+# Usage: list_files $slug
+# Lists the configuration files to be considered for $slug.
+# Later files should take precedence over earlier files.
+list_files() {
+ local slug=$1
+ local sysconfdir=${_librelib_conf_sh_sysconfdir:-@sysconfdir@}
+ local pkgconfdir=${_librelib_conf_sh_pkgconfdir:-@pkgconfdir@}
+ case $slug in
+ abs)
+ echo "${sysconfdir}/$slug.conf"
+ echo "$LIBREHOME/.$slug.conf"
+ ;;
+ makepkg)
+ local manual="${MAKEPKG_CONF:-}"
+ local system="${sysconfdir}/$slug.conf"
+ local olduser="$LIBREHOME/.$slug.conf"
+ local newuser="$XDG_CONFIG_HOME/pacman/$slug.conf"
+ if [[ "$manual" != "$system" && -r "$manual" ]]; then
+ # Manually-specified file
+ echo "$manual"
+ else
+ # Normal file lookup
+ echo "$system"
+ if [[ -r "$olduser" && ! -r "$newuser" ]]; then
+ echo "$olduser"
+ else
+ echo "$newuser"
+ fi
+ fi
+ ;;
+ xbs*)
+ echo "${sysconfdir}/xbs/$slug.conf"
+ echo "$XDG_CONFIG_HOME/xbs/$slug.conf"
+ ;;
+ libretools)
+ echo "${sysconfdir}/$slug.conf"
+ echo "$XDG_CONFIG_HOME/libretools/$slug.conf"
+ ;;
+ *)
+ echo "${pkgconfdir}/$slug.conf"
+ echo "$XDG_CONFIG_HOME/libretools/$slug.conf"
+ ;;
+ esac
+}
+
+# Usage: list_envvars $slug
+# Lists the environmental variables that take precedence over the configuration
+# files for $slug.
+list_envvars() {
+ local slug=$1
+ case $slug in
+ makepkg)
+ printf '%s\n' \
+ PKGDEST SRCDEST SRCPKGDEST LOGDEST \
+ BUILDDIR \
+ PKGEXT SRCEXT \
+ GPGKEY PACKAGER \
+ CARCH
+ ;;
+ libretools)
+ printf '%s\n' DIFFPROG
+ ;;
+ xbs)
+ printf '%s\n' BUILDSYSTEM
+ ;;
+ *) :;;
+ esac
+}
+
+# High-level generic functions #################################################
+
+# Usage: load_files $slug
+# Loads the configuration files for $slug in the proper order.
+load_files() {
+ local slug=$1
+ local var
+ local file
+
+ # Save the existing versions at _VARNAME
+ for var in $(list_envvars $slug); do
+ [[ -n ${!var:-} ]] && eval "_$var=\${$var}"
+ done
+
+ # Load the files
+ for file in $(list_files $slug); do
+ if [[ -r $file ]]; then
+ . "$file" || return 1
+ fi
+ done
+
+ # Restore the _SAVED versions
+ for var in $(list_envvars $slug); do
+ eval "$var=\${_$var:-\${$var:-}}"
+ done
+}
+
+# Usage: check_vars $slug VAR1 VAR2...
+# Check whether the variables listed are properly set.
+# If not, it prints a message saying to set them in the configuration file(s)
+# for $slug.
+check_vars() (
+ local slug=$1; shift
+
+ local ret=0
+
+ local VAR
+ for VAR in "$@"; do
+ if [[ -z ${!VAR:-} ]]; then
+ type print &>/dev/null || . libremessages
+ if [[ $(list_files $slug|wc -l) -gt 1 ]]; then
+ _l print "Configure '%s' in one of:" "$VAR"
+ list_files $slug | sed 's/./ -> &/'
+ else
+ _l print "Configure '%s' in '%s'" "$VAR" "$(list_files $slug)"
+ fi
+ ret=1
+ fi
+ done >&2
+
+ if [[ $ret != 0 ]]; then
+ return 1
+ fi
+)
+
+# Usage: get_var <slug> <var_name> <default_value>
+# Does not work with arrays
+get_var() (
+ set +euE
+ local slug=$1
+ local setting=$2
+ local default=$3
+ load_files "$slug"
+ printf '%s' "${!setting:-${default}}"
+)
+
+# Usage: set_var <slug> <var_name> <value>
+# Does not work with arrays
+set_var() {
+ local slug=$1
+ local key=$2
+ local val=$3
+ local file
+ for file in $(list_files "$slug"|tac); do
+ if [[ -w $file ]]; then
+ sed -i "/^\s*$key=/d" "$file"
+ printf '%s=%q\n' "$key" "$val" >> "$file"
+ return 0
+ fi
+ done
+ return 1
+}
+
+# PKGBUILD (not configuration, per se) #########################################
+
+unset_PKGBUILD() {
+ # This routine is based primarily off of the PKGBUILD(5) man-page,
+ # version 4.2.0, dated 2014-12-31
+
+ # This is kinda weird, but everything is more readable with
+ # this as a utility function, but I didn't want to introduce a
+ # new global function, so I just introduced it with the name
+ # of a function that we get to unset anyway. So it can't
+ # clash with anything!
+ mksource() {
+ # For each arg, `unset -v` all variables matching ${arg} and ${arg}_*
+ local v
+ for v in "$@"; do
+ unset -v "$v" $(declare -p|sed -rn "s/^declare -\S+ (${v}_[a-zA-Z0-9_]*)=.*/\1/p")
+ done
+ }
+
+ # This line is taken from the makepkg source
+ local known_hash_algos=('md5' 'sha1' 'sha224' 'sha256' 'sha384' 'sha512')
+
+ # From the "OPTIONS AND DIRECTIVES" section (in order of mention)
+ unset -v pkgname pkgver
+ unset -f pkgver
+ unset -v pkgrel pkgdesc epoch url license install changelog
+
+ mksource source
+ unset -v validpgpkeys noextract
+ local sums=("${known_hash_algos[@]/%/sums}")
+ mksource "${sums[@]}"
+
+ unset -v groups arch backup
+ mksource depends makedepends checkdepends optdepends
+ mksource conflicts provides replaces
+ unset -v options
+
+ # From the "PACKAGING FUNCTIONS" section (in order of mention)
+ unset -f package prepare build check
+
+ # From the "PACKAGE SPLITTING" section
+ unset -f $(declare -f|sed -n 's/^\(package_\S*\) ()\s*$/\1/p')
+ unset -v pkgbase
+
+ # These are used by the `librefetch` program
+ unset -v mksource mknoextract "${sums[@]/#/mk}"
+ unset -v mkdepends
+ unset -f mksource
+}
+
+load_PKGBUILD() {
+ local file=${1:-./PKGBUILD}
+ unset_PKGBUILD
+ CARCH="$(get_var makepkg CARCH "`uname -m`")"
+ . "$file"
+}
diff --git a/src/lib/libreblacklist b/src/lib/libreblacklist
new file mode 100755
index 0000000..6c354fe
--- /dev/null
+++ b/src/lib/libreblacklist
@@ -0,0 +1,83 @@
+#!/usr/bin/env bash
+# Copyright (C) 2013-2014, 2016 Luke Shumaker <lukeshu@sbcglobal.net>
+#
+# License: GNU GPLv2+
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+if [[ "${0##*/}" != libreblacklist ]]; then
+ . "$(librelib blacklist)"
+else
+ set -euE
+
+ lib_file="$(librelib blacklist)"
+ . "$lib_file"
+
+ usage-outside() {
+ sed -n '/^# Usage:/,/()/p' "$lib_file" |
+ tr '\n' '\r' | sed 's/\s*()\s*[{(]/\n/g'
+ }
+ # The output format of this is:
+ # - The first line is "Usage:"
+ # - The second line is a brief description
+ # - The last line is the command name (prefixed with "blacklist-")
+ # - The in-between lines are the extended description.
+ usage-inside() {
+ sed 's/\r/\n/g'<<<"$1"|sed -e '/^$/d' -e 's/^# //'
+ }
+
+ usage() {
+ export TEXTDOMAIN='librelib'
+ export TEXTDOMAINDIR='/usr/share/locale'
+ . "$(librelib messages)"
+ if [[ $# -eq 0 ]]; then
+ print "Usage: %s [-h] COMMAND [ARGUMENTS]" "${0##*/}"
+ print "Tool for working with the nonfree software blacklist"
+ echo
+ print "Commands:"
+ usage-outside | while read -r sec; do sec="$(usage-inside "$sec")"
+ cmd=$(<<<"$sec" sed -n '$s/^blacklist-//p')
+ desc="$(_ "$(sed -n 2p <<<"$sec")")"
+ flag "$cmd" "${desc//blacklist-/${0##*/} }"
+ done
+ else
+ usage-outside | while read -r sec; do sec="$(usage-inside "$sec")"
+ cmd=$(<<<"$sec" sed -n '$s/^blacklist-//p')
+ if [[ "$cmd" == "$1" ]]; then
+ <<<"$sec" sed '$d' |
+ while read -r line; do print "$line"; done |
+ sed "s/blacklist-/${0##*/} /g" |
+ fmt -us
+ return 0
+ fi
+ done
+ fi
+ }
+
+ main() {
+ if [[ $# -eq 0 ]]; then
+ usage >&2
+ exit 1
+ fi
+ _blacklist_cmd=$1
+ shift
+ if [[ $_blacklist_cmd == -h ]]; then
+ usage "$@"
+ else
+ "blacklist-$_blacklist_cmd" "$@"
+ fi
+ }
+
+ main "$@"
+fi
diff --git a/src/lib/libreblacklist.1.ronn b/src/lib/libreblacklist.1.ronn
new file mode 100644
index 0000000..d56eb60
--- /dev/null
+++ b/src/lib/libreblacklist.1.ronn
@@ -0,0 +1,23 @@
+libreblacklist(1) -- Tools for working with the your-freedom blacklist
+======================================================================
+
+## SYNOPSIS
+
+`. "$(librelib blacklist)"`<br>
+`. libreblacklist`<br>
+`libreblacklist` [-h] <COMMAND> [<ARGS>...]
+
+## DESCRIPTION
+
+`libreblacklist` is a set of tools for working with the your-freedom
+blacklist.
+
+It may be included into a `Bash`(1) program as a library, which will
+expose it's routines as "blacklist-<COMMAND>", or it may be invoked on
+the command line as "libreblacklist <COMMAND>".
+
+See `libreblacklist -h` for more information.
+
+## SEE ALSO
+
+librelib(7)
diff --git a/src/lib/librelib b/src/lib/librelib
new file mode 100755
index 0000000..e9d184e
--- /dev/null
+++ b/src/lib/librelib
@@ -0,0 +1,98 @@
+#!/usr/bin/env bash
+# Copyright (C) 2013-2014 Luke Shumaker <lukeshu@sbcglobal.net>
+#
+# License: GNU GPLv2+
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+default_libdir=/usr/lib/libretools
+
+if type gettext &>/dev/null; then
+ _() { gettext "$@"; }
+else
+ _() { echo "$@"; }
+fi
+
+_l() {
+ TEXTDOMAIN='librelib' TEXTDOMAINDIR='/usr/share/locale' "$@"
+}
+
+print() {
+ local mesg="$(_ "$1")"
+ shift
+ printf -- "$mesg\n" "$@"
+}
+
+whitespace_collapse() {
+ tr '\n' '\r' | sed -r \
+ -e 's/\r/ /g' -e 's/\t/ /g' \
+ -e 's/(^|[^.!? ]) +/\1 /g' -e 's/([.!?]) +/\1 /g'
+}
+
+prose() {
+ local mesg="$(_ "$(whitespace_collapse <<<"$1")")"; shift
+ printf -- "$mesg" "$@" | fmt -u
+}
+
+cmd=${0##*/}
+usage() {
+ . libremessages
+ print 'Usage: . $(%s LIBRARY)' "$cmd"
+ print 'Usage: %s -h' "$cmd"
+ print "Finds a Bash library file"
+ echo
+ prose "While some libraries can be sourced just by their name because
+ they are installed in PATH (like libremessages), some are not
+ installed there (like conf.sh), so a path must be given.
+ Hardcoding that path is the way of the dark side."
+ echo
+ prose 'By default, it looks for the files in `%s`, but this can be
+ changed with the environmental variable LIBRETOOLS_LIBDIR.' "$default_libdir"
+ echo
+ print "Example usage:"
+ printf ' . $(%s conf)\n' "$cmd"
+ echo
+ print "Options:"
+ flag '-h' 'Show this message'
+}
+
+main() {
+ if [[ $# != 1 ]]; then
+ _l usage >&2
+ return 2
+ fi
+ if [[ $1 == '-h' ]]; then
+ _l usage
+ return 0;
+ fi
+
+ if [[ -z $LIBRETOOLS_LIBDIR ]]; then
+ export LIBRETOOLS_LIBDIR=$default_libdir
+ fi
+
+ lib=$1
+ lib=${lib#libre}
+ lib=${lib%.sh}
+
+ for file in ${lib} libre${lib} ${lib}.sh libre${lib}.sh; do
+ if [[ -f "$LIBRETOOLS_LIBDIR/$file" ]]; then
+ printf '%s\n' "$LIBRETOOLS_LIBDIR/$file"
+ return 0;
+ fi
+ done
+ _l print '%s: could not find library: %s' "$cmd" "$lib" >&2
+ return 1
+}
+
+main "$@"
diff --git a/src/lib/librelib.1.ronn b/src/lib/librelib.1.ronn
new file mode 100644
index 0000000..fe64e92
--- /dev/null
+++ b/src/lib/librelib.1.ronn
@@ -0,0 +1,55 @@
+librelib(1) -- finds a Bash library file
+========================================
+
+## SYNOPSIS
+
+`. "$(librelib LIBRARY)"`<br>
+`librelib -h`
+
+## DESCRIPTION
+
+`librelib` is a program to find a Bash(1) library file at run-time.
+This way, the path does not need to be hard-coded into the
+application; think of it as a sort of dynamic-linker for shell
+programs.
+
+There are several reasons for doing this, instead of hard-coding the
+path:
+
+ * The install path can change in the future without having to change
+ programs that use them.
+ * The install directory can be configured at runtime, by setting
+ `LIBRETOOLS_LIBDIR`, similar to `LD_PRELOAD` (this is used when
+ running the test suite).
+ * The naming scheme of a library can change (such as between
+ `libreNAME` and `NAME.sh` without changing programs that use it.
+
+By default, `librelib` looks in `/usr/lib/libretools`, but that can be
+changed by setting the `LIBRETOOLS_LIBDIR` environmental variable to
+the directory it should look in.
+
+When searching for a library, `librelib` first strips `libre` from the
+beginning of the name, and `.sh` from the end. This means that all of
+the following are equivalent:
+
+ . "$(librelib messages)"
+ . "$(librelib messages.sh)"
+ . "$(librelib libremessages)"
+ . "$(librelib libremessages.sh)"
+
+Once it has the 'base' name of the library it is looking for, it looks
+for a file with that 'base' name (allowing for, but not requiring
+`libre` to be prepended, or `.sh` to be appended) in whichever
+directory it is looking in.
+
+If it cannot find a suitable library file, it will print an error
+message to standard error, and exit with a code of 1.
+
+## Examples
+
+ . "$(librelib messages)"
+ . "$(librelib conf)"
+
+## SEE ALSO
+
+librelib(7)
diff --git a/src/lib/librelib.7.ronn b/src/lib/librelib.7.ronn
new file mode 100644
index 0000000..33b0c55
--- /dev/null
+++ b/src/lib/librelib.7.ronn
@@ -0,0 +1,49 @@
+librelib(7) -- Suite of Bash libraries
+======================================
+
+## SYNOPSIS
+
+Overview of the librelib Bash library suite.
+
+## DESCRIPTION
+
+There are three parts to librelib:
+
+ 1. The `librelib`(1) executable.
+ 2. The non-executable libraries installed in `/usr/lib/libretools`
+ 3. The executable libraries installed in `/usr/bin`
+
+The `librelib` executable isn't very exciting, it just finds the
+libraries installed in `/usr/lib/libretools`. Think of it as a sort
+of dynamic-linker.
+
+The 'core' of librelib are the libraries installed in
+`/usr/lib/libretools`. These are `Bash`(1) libraries that may be
+sourced in Bash programs.
+
+Some of these libraries also make sense as stand-alone programs, where
+if they are invoked directly, the first argument is the library
+routine to be executed. For example, the `messages` library may be
+included, or executed:
+
+ . "$(librelib messages)"
+ msg2 "Foo was found: %s" "$foo"
+ # or
+ libremessages msg2 "Foo was found: %s" "$foo"
+
+The `blacklist` library is similar:
+
+ . "$(librelib blacklist)"
+ blacklist-update
+ # or
+ libreblacklist update
+
+
+
+## SEE ALSO
+
+ * librelib(1)
+ * libremessages(1)/messages.sh(3)
+ * libreblacklist(1)/blacklist.sh(3)
+ * conf.sh(3)
+ * common.sh(3)
diff --git a/src/lib/libremessages b/src/lib/libremessages
new file mode 100755
index 0000000..647204a
--- /dev/null
+++ b/src/lib/libremessages
@@ -0,0 +1,25 @@
+#!/usr/bin/env bash
+# Copyright (C) 2012-2014, 2016 Luke Shumaker <lukeshu@sbcglobal.net>
+#
+# License: GNU GPLv2+
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+if [[ "${0##*/}" != libremessages ]]; then
+ . "$(librelib messages)"
+else
+ set -euE
+ . "$(librelib messages)"
+ "$@"
+fi
diff --git a/src/lib/libremessages.1.ronn b/src/lib/libremessages.1.ronn
new file mode 100644
index 0000000..d4fac85
--- /dev/null
+++ b/src/lib/libremessages.1.ronn
@@ -0,0 +1,241 @@
+libremessages(1) -- common Bash routines
+========================================
+
+## SYNOPSIS
+
+`. "$(librelib messages)"`<br>
+`. libremessages`<br>
+`libremessages` <COMMAND>
+
+## DESCRIPTION
+
+`libremessages` is a shell library containing many common routines.
+The name is a bit of a misnomer, it mostly deals with printing
+messages, but also has other things.
+
+`libremessages` uses `common.sh`(3) internally for a large portion of
+it's functionality. The authors make no promises that functionality
+that is implemented in `libremessages` won't move into `common.sh` or
+vice-versa. So, it is recommended that you use `libremessages`, not
+`common.sh`.
+
+### STAND ALONE USAGE
+
+The "normal" way to use libremessages is to source it, then call the
+provided routines.
+
+However, if you call libremessages directly, the first argument is
+taken as the function to call, and the remaining arguments are passed
+to it. The only cases where this doesn't work are the lockfile
+routines (`lock`, `slock`, and `lock_close`), because lockfiles are
+managed as file descriptors.
+
+### VARIABLES
+
+The following variables for printing terminal color codes are set:
+`ALL_OFF`, `BOLD`, `BLUE`, `GREEN`, `RED`, `YELLOW`. If standard
+error is not a terminal (see `isatty`(3)), they are set, but empty.
+They are marked as readonly, so it is an error to try to set them
+afterwords.
+
+### MESSAGE FORMAT
+
+All routines feed the message/format string through `gettext`(1), if
+it is available.
+
+The descriptions will frequently reference `printf`(1)--this isn't
+really that `printf`. The program described by the manual page
+`printf`(1) is probably the version from GNU coreutils, every time it
+is used here, it is `bash`(1)'s internal implementation; try running
+the command `help printf` from a Bash shell for more information.
+
+### GENERAL ROUTINES
+
+Unless otherwise noted, these do not implicitly call `gettext`.
+
+ * `_` <MESSAGE>:
+ If `gettext` is available, calls `gettext`, otherwise just prints
+ the arguments given.
+
+ * `in_array` <NEEDLE> <HAYSTACK>...:
+ Evaluates whether <HAYSTACK> includes <NEEDLE>; returns 0 if it
+ does, non-zero if it doesn't.
+
+ * `panic`:
+ For the times when you can't reasonably continue, similar to
+ "assert" in some programming languages.
+
+ * `setup_traps` [<HANDLER>]:
+ Sets traps on TERM, HUP, QUIT and INT signals, as sell as the ERR
+ event, similar to makepkg. If <HANDLER> is specified, instead of
+ using the default handler (which is good for most purposes), it
+ will call <HANDLER> with the arguments
+ `<HANDLER> <SIGNAL_NAME> <MESSAGE> [<MESSAGE_ARGS>...]`, where
+ <MESSAGE> is a `printf`(1)-formatted string that is fed through
+ `gettext`, and <MESSAGE_ARGS> are its arguments.
+
+ * `whitespace_collapse`:
+ Collapses whitespace on stadard I/O, similar to HTML whitespace
+ collapsing, with the exception that it puts two spaces between
+ sentences. It considers newline, tab, and space to be
+ whitespace.
+
+### PROSE ROUTINES
+
+These routines print to standard output, and are useful for printing
+word-wrapped prose.
+
+For each of these, <MESSAGE> is fed through `gettext` automatically.
+
+To generate gettext `.pot` files for programs using these routines,
+plain `xgettext`(1) won't work because it doesn't know about
+word-wrapping. Instead, you should use the `librexgettext`(1) program
+which knows how do handle word-wrapping, and knows about each of these
+routines by default.
+
+ * `prose` <MESSAGE> [<ARGS>...]:
+ Takes a `printf`(1)-formatted string, collapses whitespace
+ (HTML-style), and then word-wraps it.
+
+ * `bullet` <MESSAGE> [<ARGS>...]:
+ Similar to `prose`, but prints a bullet point before the first
+ line, and indents the remaining lines.
+
+ * `flag` [<FLAG> <DESCRIPTION>|<HEADING>:]...:
+ Print a flag and description formatted for `--help` text. For
+ example:<br>
+ `flag '-N' 'Disable networking in the chroot'`<br>
+ The description is fed through `gettext`, the flag is not, so if
+ part of the flag needs to be translated, you must do that
+ yourself:<br>
+ `flag "-C <$(_ FILE)>" 'Use this file instead of pacman.conf'`<br>
+ Newlines in the description are ignored; it is
+ whitespace-collapsed (so newlines are stripped), then it is
+ re-word-wrapped, in the same way as `prose` and `bullet`. If you
+ pass in multiple flag/description pairs to the same invocation,
+ the descriptions are all aligned together. The ability to do
+ insert headings without resetting the alignment is the motivation
+ for also allowing headings to be in the list. In order to tell
+ the difference between a flag and a heading, a heading must end
+ with a colon (':'), and a flag must not.
+
+### NOTIFICATION ROUTINES
+
+These routines print to standard error, and all take arguments in the
+same format as `printf`(1), except for `stat_done`, which doesn't take
+any arguments. Each of these print to stderr, not stdout.
+
+For each of these, <MESSAGE> is fed through `gettext` automatically.
+
+To generate gettext `.pot` files for programs using these routines,
+plain `xgettext`(1) will work if given correct flags, but you'll find
+it easier to use `librexgettext`(1), which will handle each of these
+without any extra flags.
+
+ * `plain` <MESSAGE> [<ARGS>...]:
+ Prints a "plain" message in bold, indented with 4 spaces.
+
+ * `msg` <MESSAGE> [<ARGS>...]:
+ Prints a top-level priority notification.
+
+ * `msg2` <MESSAGE> [<ARGS>...]:
+ Prints a secondary notification.
+
+ * `warning` <MESSAGE> [<ARGS>...]:
+ Prints a warning.
+
+ * `error` <MESSAGE> [<ARGS>...]:
+ Prints an error message.
+
+ * `stat_busy` <MESSAGE> [<ARGS>...]:
+ Prints a "working..." type message without a trailing newline.
+
+ * `stat_done`:
+ Prints a "done" type message to terminate `stat_busy`.
+
+ * `print` <MESSAGE> [<ARGS>...]:
+ Like `printf`(1), but `gettext`-aware, and automatically prints a
+ trailing newline.
+
+ * `term_title` <MESSAGE> [<ARGS>...]:
+ Sets the terminal title to the specified message.
+
+### TEMPORARY DIRECTORY MANAGEMENT
+
+These are used by devtools, and not used within the rest of
+libretools.
+
+They work by creating and removing the directory referred to by the
+variable $<WORKDIR>; `libretools.conf`(5) uses the same variable to
+where the user saves all their work. If you aren't careful with
+these, you could end up deleting a lot of someone's work.
+
+ * `setup_workdir`:
+ Creates a temporary directory, and sets the environmental
+ variable $<WORKDIR> to it. Also sets traps for the signals INT,
+ QUIT, TERM and HUP to run `abort`; and EXIT to run `cleanup`
+ (see `signal`(7)).
+
+ * `cleanup` [<EXIT_STATUS>]:
+ *If* `setup_workdir` has been run, `rm -rf "$WORKDIR"`. If given
+ a numeric argument, it will then call `exit`(1) with that
+ argument, otherwise it calls `exit`(1) with a status of 0.
+
+ * `abort`:
+ Calls `msg` with the message "Aborting...", then calls
+ `cleanup 255`.
+
+ * `die` <MESSAGE> [<ARGS>...]:
+ Exactly like `error`, but calls `cleanup` and calls `exit`(1)
+ with a status of 255.
+
+### LOCKFILE ROUTINES
+
+ * `lock` <FD> <LOCKFILE> <MESSAGE> [<MSG_ARGS>...]:
+ Opens (creating if necessary) the file <LOCKFILE> with file
+ descriptor <FD> in the current process, and gets an exclusive
+ lock on it. If another program already has a lock on the file,
+ and this program needs to wait for the lock to be released, then
+ it uses `stat_busy`/`stat_done` to print <MESSAGE>.
+
+ * `slock` <FD> <LOCKFILE> <MESSAGE> [<MSG_ARGS>...]:
+ Identical like `lock`, but opens a shared lock. This is also
+ known as a "read lock". Many programs can have a shared lock at
+ the same time, as long as no one has an exclusive lock on it.
+
+ * `lock_close` <FD>:
+ Closes file descriptor <FD>, releasing the lock opened on it.
+
+### MAKEPKG ROUTINES
+
+These routines relate to `makepkg`(8).
+
+ * `find_cached_package` <PKGNAME> <PKGVER>[-<PKGREL] <ARCH>:
+ Searches for a locally built copy of the specified package, in
+ <PKGDEST> and the current working directory. If <PKGREL> is not
+ specified, any value will match. If multiple matching files are
+ found (not counting duplicate links), then an error is printed to
+ stderr and nothing is printed to stdout.
+
+ * `get_full_version` [<PKGNAME>]:
+ Inspects variables that are set, and prints the full version
+ spec, including <epoch> if necessary, <pkgver>, and <pkgrel>. By
+ default, it will print the information for <pkgbase>, following
+ the normal rules for finding <pkgbase>. If <PKGNAME> is given,
+ it will print the information for that sub-package. The versions
+ for different parts of a split package don't have to be the same!
+
+## BUGS
+
+`term_title` currently only knows about the terminals screen, tmux,
+xterm and rxvt (and their various <TERM> values;
+"rxvt-unicode-256color" is still rxvt).
+
+## SEE ALSO
+
+librexgettext(1), librelib(7), gettext(1), common.sh(3)
+
+Things that were mentioned:
+
+bash(1), xgettext(1), exit(1), isatty(3), libretools.conf(5),
+makepkg(8), printf(1), signal(7)
diff --git a/src/lib/librexgettext b/src/lib/librexgettext
new file mode 100755
index 0000000..db575d6
--- /dev/null
+++ b/src/lib/librexgettext
@@ -0,0 +1,226 @@
+#!/usr/bin/env bash
+# Copyright (C) 2013-2016 Luke Shumaker <lukeshu@sbcglobal.net>
+#
+# License: GNU GPLv2+
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+export TEXTDOMAIN='librelib'
+export TEXTDOMAINDIR='/usr/share/locale'
+
+default_simple=(
+ --keyword={eval_,}{gettext,'ngettext:1,2'}
+ --keyword={_,print,term_title}
+ --keyword={msg,msg2,warning,error,stat_busy,die}
+ --keyword={lock,slock}:3
+)
+default_prose=(--keyword={prose,bullet})
+
+readonly default_simple default_prose
+
+if ! type gettext &>/dev/null; then
+ gettext() { echo "$@"; }
+fi
+
+errusage() {
+ if [[ $# -gt 0 ]]; then
+ fmt="$(gettext "$1")"; shift
+ printf "${0##*/}: $fmt\n" "$@"
+ fi
+ usage >&2
+}
+
+usage() {
+ . libremessages
+ print 'Usage: %s [OPTIONS] FILES...' "${0##*/}"
+ print 'Generates .pot files for programs using libremessages'
+ echo
+ prose 'In librexgettext, there are 2 types of keywords:'
+ bullet 'simple: Simple keywords are just like normal xgettext'
+ bullet 'prose: Prose keywords are similar, but the text is
+ word-wrapped'
+ prose 'The keyword format is the same as in GNU xgettext.'
+ echo
+ prose 'The libremessages `flag` command is also handled
+ specially, and is not configurable as a keyword.'
+ echo
+ prose 'The default simple keywords are: %s' "${default_simple[*]#--keyword=}"
+ echo
+ prose 'The default prose keywords are: %s' "${default_prose[*]#--keyword=}"
+ echo
+ print 'Options:'
+ flag \
+ '--simple=KEYWORD' 'Look for KEYWORD as an additional simple keyword' \
+ '--prose=KEYWORD' 'Look for KEYWORD as an additional prose keyword' \
+ '-k' 'Disable using the default keywords' \
+ '-h, --help' 'Show this text'
+}
+
+xgettext-sh() {
+ xgettext --omit-header --from-code=UTF-8 -L shell -k -o - "$@"
+}
+
+xgettext-flag() {
+ {
+ # Stage 1: Generate
+ #
+ # Get all of the arguments to `flag`. Because `flag`
+ # takes an arbitrary number of arguments, just iterate
+ # through arg1, arg2, ... argN; until we've come up
+ # empty 3 times. Why 3? Because each flag takes 2
+ # arguments, and because we don't keep track of which
+ # one of those we're on, waiting for 3 empties ensures
+ # us that we've had a complete "round" with nothing.
+ #
+ # Why can't I just do i+=2, and not have to keep track
+ # of empties? Because, we also allow for arguments
+ # ending in a colon to be headings, which changes the
+ # offsets.
+ declare -i empties=0
+ declare -i i
+ for (( i=1; empties < 3; i++ )); do
+ local out
+ out="$(xgettext-sh --keyword="flag:$i,\"$i\"" "$@")"
+ if [[ -n $out ]]; then
+ printf -- '%s\n' "$out"
+ else
+ empties+=1
+ fi
+ done
+ } | whitespace-collapse | sed '/^\#, sh-format/d' | {
+ # Stage 2: Parse
+ #
+ # Read in the lines, and group them into an array of
+ # (multi-line) msgs. This just makes working with
+ # them easier.
+ local msgs=()
+ declare -i i=-1
+ local re='^#\. ([0-9]+)$'
+ IFS=''
+ local line
+ while read -r line; do
+ if [[ $line =~ $re ]]; then
+ i+=1
+ fi
+ msgs[$i]+="$line"$'\n'
+ done
+ # Stage 3: Sort
+ #
+ # Now, we have the `msgs` array, and it is
+ # sorted such that it is all of the arg1's to `flag`,
+ # then all of the arg2's, then all of the arg3's, and
+ # so on. We want to re-order them such that it's all
+ # of the args for the first invocation then all of the
+ # args for the second; and so on.
+ #
+ # We do this by simply sorting them by the location
+ # that they appear in the file. Then, when we see the
+ # argument number go back down to 1, we know that a
+ # new invocation has started!
+ IFS=$'\n'
+ local locations=($(
+ local i
+ for i in "${!msgs[@]}"; do
+ declare -i arg row
+ local lines=(${msgs[$i]})
+ arg=${lines[0]#'#. '}
+ row=${lines[1]##*:}
+ printf '%d.%d %d\n' "$row" "$arg" "$i"
+ done | sort -n
+ ))
+ # Stage 4: Output
+ #
+ # Now, we prune out the arguments that aren't
+ # localizable. Also, remove the "#." comment lines.
+ # As explained above (in stage 3), when we see $arg go
+ # to 1, that's the beginning of a new invocation.
+ local expectflag=true
+ local location
+ for location in "${locations[@]}"; do
+ IFS=' .'
+ local row arg i
+ read -r row arg i <<<"$location"
+ local msg="${msgs[$i]#*$'\n'}"
+ # Now we operate based on $row, $arg, and $msg
+ if [[ $arg == 1 ]]; then
+ expectflag=true
+ fi
+ if $expectflag; then
+ IFS=$'\n'
+ local lines=(${msg})
+ if [[ ${lines[1]} == *':"' ]]; then
+ # We expected a flag, but got
+ # a heading
+ printf -- '%s\n' "$msg"
+ else
+ # We expected a flag, and got
+ # one!
+ expectflag=false
+ fi
+ else
+ printf -- '%s\n' "$msg"
+ expectflag=true
+ fi
+ done
+ }
+}
+
+whitespace-collapse() {
+ tr '\n' '\r' | sed 's/"\r\s*"//g' | tr '\r' '\n' | # This removes the awkward word-wrapping done by xgettext
+ sed -r -e 's/(\\n|\\t|\t)/ /g' -e 's/(^|[^.!? ]) +/\1 /g' -e 's/([.!?]) +/\1 /g' # This collapses whitespace
+}
+
+main() {
+ local simple=()
+ local prose=()
+ local files=()
+ local use_defaults=true
+ local error=false
+
+ declare -i i
+ for (( i=1; i <= $#; i++ )); do
+ case "${!i}" in
+ --simple) i+=1; simple+=(--keyword="${!i}");;
+ --simple=*) simple+=(--keyword="${!i#*=}");;
+ --prose) i+=1; prose+=(--keyword="${!i}");;
+ --prose=*) prose+=(--keyword="${!i#*=}");;
+ -k) use_defaults=false;;
+ --help|-h) usage; return 0;;
+ --) i+=1; break;;
+ -*) errusage "unrecognized option: %s" "${!i}"; error=true;;
+ *) files+=("${!i}");;
+ esac
+ done
+ files+=("${@:$i}")
+ if [[ ${#files[@]} -lt 1 ]]; then
+ errusage "no input file given"
+ error=true
+ fi
+ if "$error"; then
+ return 1
+ fi
+ if "$use_defaults"; then
+ simple+=("${default_simple[@]}")
+ prose+=("${default_prose[@]}")
+ fi
+
+ # Main code
+ {
+ xgettext-sh "${simple[@]}" -- "${files[@]}"
+ xgettext-sh "${prose[@]}" -- "${files[@]}" | whitespace-collapse
+ xgettext-flag -- "${files[@]}"
+ } | sed '/^\#, sh-format/d' | msguniq -Fi --to-code=UTF-8
+}
+
+main "$@"
diff --git a/src/lib/librexgettext.1.ronn b/src/lib/librexgettext.1.ronn
new file mode 100644
index 0000000..de6abde
--- /dev/null
+++ b/src/lib/librexgettext.1.ronn
@@ -0,0 +1,18 @@
+librexgettext(1) -- Extrct libremessages gettext strings from sources
+=====================================================================
+
+## SYNOPSIS
+
+`librexgettext` [OPTIONS] FILES<br>
+`librexgettext` [-h|--help]
+
+## DESCRIPTION
+
+`librexgettext` is a tool for extracting gettext strings from programs
+using the fancy word-wrapping utilities of `libremessages`(1).
+
+See `librexgettext --help` for more information.
+
+## SEE ALSO
+
+libremessages(1)
diff --git a/src/lib/messages.sh b/src/lib/messages.sh
new file mode 100644
index 0000000..0125003
--- /dev/null
+++ b/src/lib/messages.sh
@@ -0,0 +1,212 @@
+#!/usr/bin/env bash
+# This may be included with or without `set -euE`
+
+# Copyright (C) 2011 Joshua Ismael Haase Hernández (xihh) <hahj87@gmail.com>
+# Copyright (C) 2012 Nicolás Reynolds <fauno@parabola.nu>
+# Copyright (C) 2012-2014, 2016 Luke Shumaker <lukeshu@sbcglobal.net>
+#
+# For just the setup_traps() function:
+# Copyright (C) 2002-2006 Judd Vinet <jvinet@zeroflux.org>
+# Copyright (C) 2006-2010 Pacman Development Team <pacman-dev@archlinux.org>
+# Copyright (C) 2005 Aurelien Foret <orelien@chez.com>
+# Copyright (C) 2005 Christian Hamar <krics@linuxforum.hu>
+# Copyright (C) 2006 Alex Smith <alex@alex-smith.me.uk>
+# Copyright (C) 2006 Andras Voroskoi <voroskoi@frugalware.org>
+# Copyright (C) 2006 Miklos Vajna <vmiklos@frugalware.org>
+#
+# License: GNU GPLv2+
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+################################################################################
+# Inherit most functions from devtools #
+################################################################################
+
+. "$(librelib common.sh)"
+
+################################################################################
+# Own functions #
+################################################################################
+
+# Usage: panic
+#
+# For programming errors, bails immediately with little fanfare.
+panic() {
+ echo "$(_l _ 'panic: malformed call to internal function')" >&2
+ exit 1
+}
+
+# Usage: print MESG [ARGS...]
+#
+# Like printf, but gettext-aware, and prints a trailing newline
+print() {
+ [[ $# -ge 1 ]] || panic
+ local mesg="$(_ "$1")"
+ shift
+ printf -- "$mesg\n" "$@"
+}
+
+# Usage: whitespace_collapse <<<STRING
+#
+# Collapses whitespace on stadard I/O, similar to HTML whitespace
+# collapsing, with the exception that it puts two spaces between
+# sentences. It considers newline, tab, and space to be whitespace.
+whitespace_collapse() {
+ [[ $# == 0 ]] || panic
+
+ tr '\n' '\r' | sed -r \
+ -e 's/\r/ /g' -e 's/\t/ /g' \
+ -e 's/(^|[^.!? ]) +/\1 /g' -e 's/([.!?]) +/\1 /g'
+}
+
+
+# Usage: prose MESG [ARGS...]
+#
+# Do HTML-style whitespace collapsing on the first argument, translate
+# it (gettext), then word-wrap it to 75 columns. This is useful for
+# printing a paragraph of prose in --help text.
+prose() {
+ [[ $# -ge 1 ]] || panic
+ local mesg="$(_ "$(whitespace_collapse <<<"$1")")"; shift
+ printf -- "$mesg" "$@" | fmt -u
+}
+
+# Usage: bullet MESG [ARGS...]
+# Like prose, but print a bullet "-" before the first line, and indent the
+# remaining lines.
+bullet() {
+ [[ $# -ge 1 ]] || panic
+ local mesg="$(_ "$(whitespace_collapse <<<"$1")")"; shift
+ # Wrap the text to 71 columns; 75 (the default) minus a 4 column indent
+ printf -- "$mesg" "$@" | fmt -u -w 71 | sed -e '1s/^/ - /' -e '2,$s/^/ /'
+}
+
+# Usage: flag [FLAG DESCRIPTION|HEADING:]...
+#
+# Print a flag and description formatted for --help text.
+#
+# ex: flag '-C <FILE>' 'Use this file instead of pacman.conf'
+#
+# The descriptions and headings are fed through gettext, the flags ar
+# not, so if part of a flag needs to be translated, you must do that
+# yourself:
+#
+# ex: flag "-C <$(_ FILE)>" 'Use this file instead of pacman.conf'
+#
+# If you want to line-break the description in the source, so it isn't
+# crazy-long, feel free, it is reflowed/wrapped the same way as prose
+# and bullet. If you pass in multiple flag/description pairs at once,
+# the descriptions are all alligned together.
+#
+# A heading MUST end with a colon (':'), this is how it knows that it
+# is a heading. Similarly, a flag MUST NOT end with a colon.
+flag() {
+ local args=("$@")
+
+ declare -i flaglen=0
+ while [[ $# -gt 0 ]]; do
+ if [[ $1 == *: ]]; then
+ shift 1
+ else
+ if [[ ${#1} -gt $flaglen ]]; then
+ flaglen=${#1}
+ fi
+ shift 2
+ fi
+ done
+ set -- "${args[@]}"
+
+ # Unless the $flaglen is extra-wide, the $desc should start at
+ # column 16 (that is two literal-tabs). If $flaglen is wide,
+ # this should be increased in increments of 8 (that is, a
+ # literal-tab). Everything should be wrapped to 75 columns.
+
+ # The printf-format we use has 4 spaces built into it (two at
+ # the beginning, and two for a seperator). Therefore, the
+ # default indent is 16-4=12 columns. And the width left for
+ # $desc is (75-4)-indent = 71-indent.
+
+ declare -i indent=12
+ while [[ $indent -lt $flaglen ]]; do
+ indent+=8
+ done
+ local fmt2 fmt1
+ fmt2=" %-${indent}s %s\n"
+ printf -v fmt1 " %-${indent}s %%s\n" ''
+
+ while [[ $# -gt 0 ]]; do
+ if [[ $1 == *: ]]; then
+ printf -- ' %s\n' "$(_ "$1")"
+ shift
+ else
+ [[ $# -gt 1 ]] || panic
+ local flag=$1
+ local desc="$(_ "$(whitespace_collapse <<<"$2")")"
+ shift 2
+
+ local lines
+ IFS=$'\n' lines=($(fmt -u -w $((71-indent)) <<<"$desc"))
+ printf -- "$fmt2" "$flag" "${lines[0]}"
+ [[ ${#lines[@]} -lt 2 ]] || printf -- "$fmt1" "${lines[@]:1}"
+ fi
+ done
+}
+
+# Usage: term_title MESG [ARGS...]
+#
+# Sets the terminal title.
+term_title() {
+ [[ $# -ge 1 ]] || panic
+ local fmt=''
+ case "$TERM" in
+ screen|tmux) fmt='\ek%s\e\\';;
+ xterm*|rxvt*) fmt='\e]0;%s\a';;
+ esac
+ printf "$fmt" "$(printf -- "$@")"
+}
+
+# Usage: setup_traps [handler]
+#
+# Sets up traps on TERM, HUP, QUIT and INT signals, as well as the ERR
+# event, similar to makepkg.
+#
+# If `handler` is specified, instead of using the default handler
+# (which is good for most purposes), it will call the command handler
+# with the arguments:
+#
+# ${handler} SIGNAL_NAME MESSAGE_FMT [MESSAGE_PARAMS...]
+#
+# where MESSAGE_* are printf-like stuff.
+#
+# This function is based on code from pacman:makepkg
+setup_traps() {
+ [[ $# -le 1 ]] || panic
+ if [[ $# == 1 ]]; then
+ eval "_libremessages_trap_exit() { $1 \"\$@\"; }"
+ else
+ _libremessages_trap_exit() {
+ local signal=$1; shift
+ echo
+ error "$@"
+ trap -- "$signal"
+ kill "-$signal" "$$"
+ }
+ fi
+ set -E
+ for signal in TERM HUP QUIT; do
+ trap "_libremessages_trap_exit $signal '%s signal caught. Exiting...' $signal" $signal
+ done
+ trap '_libremessages_trap_exit INT "Aborted by user! Exiting..."' INT
+ trap '_libremessages_trap_exit USR1 "An unknown error has occurred. Exiting..."' ERR
+}
diff --git a/src/lib/messages.sh.3.ronn b/src/lib/messages.sh.3.ronn
new file mode 120000
index 0000000..391ecbd
--- /dev/null
+++ b/src/lib/messages.sh.3.ronn
@@ -0,0 +1 @@
+libremessages.1.ronn \ No newline at end of file
diff --git a/src/librefetch/.gitignore b/src/librefetch/.gitignore
new file mode 100644
index 0000000..d34a2fc
--- /dev/null
+++ b/src/librefetch/.gitignore
@@ -0,0 +1,2 @@
+librefetch-install
+librefetch-makepkg.conf
diff --git a/src/librefetch/Makefile b/src/librefetch/Makefile
new file mode 100644
index 0000000..26ee2ee
--- /dev/null
+++ b/src/librefetch/Makefile
@@ -0,0 +1,13 @@
+include $(dir $(lastword $(MAKEFILE_LIST)))/../../config.mk
+include $(topsrcdir)/automake.head.mk
+
+libretools-bins = librefetch librefetch-install
+libretools-confs += librefetch-makepkg.conf
+libretools-libexecs += $(filter librefetchdir/%,$(detect-exec))
+libretools-libs += $(filter-out $(libretools-libexecs),$(filter librefetchdir/%,$(detect-all)))
+pots = $(libretools-bins)
+
+$(outdir)/librefetch-install: $(var)pkgconfdir
+$(outdir)/librefetch-makepkg.conf: $(var)bindir
+
+include $(topsrcdir)/automake.tail.mk
diff --git a/src/librefetch/librefetch b/src/librefetch/librefetch
new file mode 100755
index 0000000..2b8af61
--- /dev/null
+++ b/src/librefetch/librefetch
@@ -0,0 +1,400 @@
+#!/usr/bin/env bash
+# librefetch
+#
+# Copyright (C) 2013-2016 Luke Shumaker <lukeshu@sbcglobal.net>
+#
+# For just the create_signature() function:
+# Copyright (C) 2006-2013 Pacman Development Team <pacman-dev@archlinux.org>
+# Copyright (C) 2002-2006 Judd Vinet <jvinet@zeroflux.org>
+# Copyright (C) 2005 Aurelien Foret <orelien@chez.com>
+# Copyright (C) 2006 Miklos Vajna <vmiklos@frugalware.org>
+# Copyright (C) 2005 Christian Hamar <krics@linuxforum.hu>
+# Copyright (C) 2006 Alex Smith <alex@alex-smith.me.uk>
+# Copyright (C) 2006 Andras Voroskoi <voroskoi@frugalware.org>
+#
+# License: GNU GPLv3+
+#
+# This file is part of LibreFetch.
+#
+# LibreFetch is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# LibreFetch is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with LibreFetch. If not, see <http://www.gnu.org/licenses/>.
+
+# create_signature() is taken from pacman:makepkg, which is GPLv2+,
+# so we take the '+' to combine it with our GPLv3+.
+
+. "$(librelib conf)"
+. "$(librelib messages)"
+setup_traps
+
+tmpfiles=()
+tmpdirs=()
+trap 'rm -f -- "${tmpfiles[@]}"; rm -rf -- "${tmpdirs[@]}"' EXIT
+
+cmd=${0##*/}
+usage() {
+ print "Usage: %s [OPTIONS] SOURCE_URL [OUTPUT_FILE]" "$cmd"
+ print "Usage: %s -[g|S|M|h]" "$cmd"
+ print "Downloads or creates a liberated source tarball."
+ echo
+ prose "The default mode is to create OUTPUT_FILE, first by trying
+ download mode, then create mode."
+ echo
+ prose "If OUTPUT_FILE isn't specified, it defaults to the non-directory
+ part of SOURCE_URL, in the current directory."
+ echo
+ prose "Unless '-C' is specified, if SOURCE_URL does not begin with a
+ configured mirror, create mode is inhibited."
+ echo
+ prose "In download mode, it simply tries to download SOURCE_URL. At the
+ beginning of a URL, 'libre://' expands to the first configured
+ mirror."
+ echo
+ prose "In create mode, it either looks at a build script and uses that
+ to create the source tarball, or it uses GPG to create a
+ signature (if OUTPUT_FILE ends with \`.sig\` or \`.sig.part\`).
+ If it is using GPG to create a signature, but the file that it is
+ trying to sign doesn't exist yet, it recurses on itself to first
+ create that file. SOURCE_URL is ignored, except that it is used
+ to set the default value of OUTPUT_FILE, and that it may be used
+ when recursing."
+ echo
+ prose "The default build script is 'PKGBUILD', or 'SRCBUILD' if it
+ exists."
+ echo
+ prose "Other options, if they are valid \`makepkg\` options, are passed
+ straight to makepkg."
+ echo
+ print "Example usage:"
+ print ' $ %s https://repo.parabola.nu/other/mypackage/mypackage-1.0.tar.gz' "$cmd"
+ echo
+ print "Options:"
+ flag 'Settings:' \
+ "-C" "Force create mode (don't download)" \
+ "-D" "Force download mode (don't create)" \
+ "-p <$(_ FILE)>" "Use an alternate build script (instead of
+ 'PKGBUILD'). If an SRCBUILD exists in the same
+ directory, it is used instead" \
+ 'Alternate modes:' \
+ "-g, --geninteg" "Generate integrity checks for source files" \
+ "-S, --srcbuild" "Print the effective build script (SRCBUILD)" \
+ "-M, --makepkg" "Generate and print the location of the
+ effective makepkg script" \
+ "-h, --help" "Show this message"
+}
+
+main() {
+ BUILDFILE="$(realpath -Lm PKGBUILD)"
+ makepkg_opts=()
+ extra_opts=()
+ mode=download-create
+ if ! parse_options "$@"; then
+ usage >&2
+ exit 1
+ fi
+
+ doit
+}
+
+doit() {
+ # Mode: help ###########################################################
+
+ if [[ $mode =~ help ]]; then
+ usage
+ exit 0
+ fi
+
+ ########################################################################
+
+ makepkg="$(modified_makepkg)"
+
+ # Mode: makepkg ########################################################
+
+ if [[ $mode =~ makepkg ]]; then
+ printf '%s\n' "$makepkg"
+ exit 0
+ else
+ tmpdirs+=("${makepkg%/*}")
+ fi
+
+ ########################################################################
+
+ local BUILDFILEDIR="${BUILDFILE%/*}"
+ if [[ -f "${BUILDFILEDIR}/SRCBUILD" ]]; then
+ BUILDFILE="${BUILDFILEDIR}/SRCBUILD"
+ fi
+ if [[ ! -f "$BUILDFILE" ]]; then
+ error "%s does not exist." "$BUILDFILE"
+ exit 1
+ fi
+ case "$BUILDFILE" in
+ */SRCBUILD) srcbuild="$(modified_srcbuild "$BUILDFILE")";;
+ *) srcbuild="$(modified_pkgbuild "$BUILDFILE")";;
+ esac
+ tmpfiles+=("$srcbuild")
+
+ # Mode: checksums ######################################################
+
+ if [[ $mode =~ checksums ]]; then
+ "$makepkg" "${makepkg_opts[@]}" -g -p "$srcbuild" |
+ case ${BUILDFILE##*/} in
+ PKGBUILD) sed -e 's/^[a-z]/mk&/' -e 's/^\s/ &/';;
+ SRCBUILD) cat;;
+ esac
+ exit 0
+ fi
+
+ # Mode: srcbuild #######################################################
+
+ if [[ $mode =~ srcbuild ]]; then
+ cat "$srcbuild"
+ exit 0
+ fi
+
+ ########################################################################
+
+ local src="${extra_opts[0]}"
+ local dst="${extra_opts[1]:-${src##*/}}"
+
+ # Don't canonicalize $src unless mode =~ download, and we've validated
+ # that $MIRRORS is configured.
+
+ # Canonicalize $dst
+ dst="$(realpath -Lm -- "$dst")"
+
+ # Mode: download #######################################################
+
+ if [[ $mode =~ download ]]; then
+ load_files librefetch
+ check_vars librefetch MIRRORS DOWNLOADER || exit 1
+
+ # Canonicalize $src
+ if [[ "$src" == libre://* ]]; then
+ src="${MIRRORS[0]}/${src#libre://}"
+ fi
+
+ # check to see if $src is a candidate for create mode
+ local inmirror=false;
+ local mirror
+ for mirror in "${MIRRORS[@]}"; do
+ if [[ "$src" == "$mirror"* ]]; then
+ inmirror=true
+ break
+ fi
+ done
+ if ! $inmirror; then
+ # inhibit create
+ mode=download
+ fi
+
+ local dlcmd="${DOWNLOADER}"
+ [[ $dlcmd = *%u* ]] || dlcmd="$dlcmd %u"
+ dlcmd="${dlcmd//\%o/\"\$dst\"}"
+ dlcmd="${dlcmd//\%u/\"\$src\"}"
+
+ { eval "$dlcmd"; } >&2 && exit 0
+ fi
+
+ # Mode: create #########################################################
+
+ if [[ $mode =~ create ]]; then
+ local base_dst=${dst%.part}
+ local suffix=${dst#"$base_dst"}
+
+ if [[ $base_dst == *.sig ]]; then
+ if ! [[ -e $base_dst ]]; then
+ extra_opts=("${src%.sig}" "${base_dst%.sig}")
+ doit || exit $?
+ fi
+ create_signature "${base_dst%.sig}" || exit $?
+ if [[ -n $suffix ]]; then
+ mv -f "$base_dst" "$dst"
+ fi
+ else
+ export PKGEXT=${base_dst##*/}
+ export PKGDEST=${dst%/*}
+ export pkg_file=$dst
+
+ cd "$BUILDFILEDIR"
+ "$makepkg" "${makepkg_opts[@]}" -p "$srcbuild" >&2 || exit $?
+ fi
+ fi
+}
+
+# sets the variables BUILDFILE, makepkg_opts, extra_opts, mode
+parse_options() {
+ declare -i ret=0
+ local {shrt,long}{1,2}
+
+ # makepkg options
+ local makepkg_orig="$(which makepkg)"
+ shrt1=($(LC_ALL=C "${makepkg_orig}" -h | sed -rn 's/^ +-(.)(,| [^<]).*/\1/p'))
+ shrt2=($(LC_ALL=C "${makepkg_orig}" -h | sed -rn 's/^ +-(.) <.*/\1/p'))
+ long1=($(LC_ALL=C "${makepkg_orig}" -h | sed -rn -e 's/^ +(-., )?--(\S*) [^<].*/\2/p'))
+ long2=($(LC_ALL=C "${makepkg_orig}" -h | sed -rn 's/^ +--(\S*) <.*/\1/p'))
+
+ # librefetch options
+ shrt1+=(C D g S M h)
+ shrt2+=(p)
+ long1+=(geninteg srcbuild makepkg help)
+ long2+=()
+
+ # Feed the options through getopt (sanitize them)
+ local shrt="$({ printf '%s\0' "${shrt1[@]}"; printf '%s:\0' "${shrt2[@]}"; } | sort -zu | xargs -0 printf '%s')"
+ local long="$({ printf '%s\0' "${long1[@]}"; printf '%s:\0' "${long2[@]}"; } | sort -zu | xargs -0 printf '%s,')"
+ local args="$(getopt -n "$cmd" -o "$shrt" -l "${long%,}" -- "$@")"
+ ret=$?
+ eval set -- "$args"
+ unset shrt long args
+
+ # Parse the options.
+ local opt optarg have_optarg
+ while [[ $# -gt 0 ]]; do
+ opt=$1; shift
+ have_optarg=false
+
+ if { [[ $opt == --?* ]] && in_array "${opt#--}" "${long2[@]}"; } \
+ || { [[ $opt == -? ]] && in_array "${opt#-}" "${shrt2[@]}"; }
+ then
+ optarg=$1; shift
+ have_optarg=true
+ fi
+
+ case "$opt" in
+ -C) mode=create;;
+ -D) mode=download;;
+ -g|--geninteg) mode=checksums;;
+ -S|--srcbuild) mode=srcbuild;;
+ -M|--makepkg) mode=makepkg;;
+ -p) BUILDFILE="$(realpath -Lm -- "$optarg")";;
+ -h|--help) mode=help;;
+ --) break;;
+ *)
+ makepkg_opts+=("$opt")
+ if $have_optarg; then makepkg_opts+=("$optarg"); fi
+ ;;
+ esac
+ done
+ extra_opts+=("$@")
+
+ # check the number of extra_opts
+ case "$mode" in
+ help) # don't worry about it
+ :;;
+ checksums|srcbuild|makepkg) # don't take any extra arguments
+ if [[ ${#extra_opts[@]} != 0 ]]; then
+ print "%s: found extra non-flag arguments: %s" "$cmd" "${extra_opts[*]}" >&2
+ ret=1
+ fi
+ ;;
+ *download*|*create*) # take 1 or 2 extra arguments
+ if [[ ${#extra_opts[@]} != 1 ]] && [[ ${#extra_opts[@]} != 2 ]]; then
+ print "%s: %d non-flag arguments found, expected 1 or 2: %s" "$cmd" ${#extra_opts[@]} >&2
+ ret=1
+ fi
+ ;;
+ esac
+
+ return $ret
+}
+
+# Modify makepkg ###############################################################
+
+modified_makepkg() {
+ local dir="$(mktemp --tmpdir --directory "${cmd}.XXXXXXXXXXX.makepkg")"
+ make -s -f "$(librelib librefetchdir/Makefile)" new="$dir"
+ realpath -es "$dir/makepkg"
+}
+
+# Modify PKGBUILD ##############################################################
+
+# a string to be appended
+pkgbuild_append='
+# do not do split packages
+if [[ ${#pkgname[@]} -gt 1 ]]; then
+ if [[ -n $pkgbase ]]; then
+ pkgname=("$pkgbase")
+ else
+ pkgname=("$pkgname")
+ fi
+fi
+
+# copy source variables
+source=("${mksource[@]}") ; unset "source_${CARCH}"
+noextract=("${mknoextract[@]}")
+
+declare algo
+for algo in "${known_hash_algos[@]}"; do
+ eval "${algo}sums=(\"\${mk${algo}sums[@]}\")"
+ unset "${algo}sums_${CARCH}"
+done
+
+depends=() ; unset "depends_${CARCH}"
+checkdepends=() ; unset "checkdepends_${CARCH}"
+makedepends=("${mkdepends[@]}") ; unset "makedepends_${CARCH}"
+
+####
+options=(!strip docs libtool staticlibs emptydirs !zipman purge !upx !optipng !debug)
+PURGE_TARGETS=(.bzr/ .cvs/ .git/ .hg/ .svn/ .makepkg/)
+
+####
+if ! declare -f mksource >/dev/null; then
+ mksource() { :; }
+fi
+prepare() { :; }
+build() { mksource; }
+check() { :; }
+package() { cp -a "$srcdir"/*/ "$pkgdir/"; }
+'
+
+modified_pkgbuild() {
+ local pkgbuild=$1
+ local srcbuild="$(mktemp "${pkgbuild%/*}/${cmd}.XXXXXXXXXXX.PKGBUILD.to.SRCBUILD")"
+ printf '%s' "$pkgbuild_append" | cat "$pkgbuild" - > "$srcbuild"
+ printf '%s\n' "$srcbuild"
+}
+
+
+# Modify SRCBUILD ##############################################################
+
+modified_srcbuild() {
+ local orig=$1
+ local srcbuild="$(mktemp "${orig%/*}/${cmd}.XXXXXXXXXXX.SRCBUILD.to.SRCBUILD")"
+ sed -e '/PKGDEST=/d' -e '/PKGEXT=/d' < "$orig" > "$new"
+ printf '%s\n' "$new"
+}
+
+################################################################################
+
+# This function is taken almost verbatim from makepkg
+create_signature() {
+ local ret=0
+ local filename="$1"
+ msg "Signing package..."
+
+ local SIGNWITHKEY=()
+ if [[ -n $GPGKEY ]]; then
+ SIGNWITHKEY=(-u "${GPGKEY}")
+ fi
+
+ gpg --detach-sign --use-agent "${SIGNWITHKEY[@]}" --no-armor "$filename" &>/dev/null || ret=$?
+
+
+ if (( ! ret )); then
+ msg2 "Created signature file %s." "$filename.sig"
+ else
+ error "Failed to sign package file."
+ return $ret
+ fi
+}
+
+main "$@"
diff --git a/src/librefetch/librefetch-install.in b/src/librefetch/librefetch-install.in
new file mode 100644
index 0000000..34815a7
--- /dev/null
+++ b/src/librefetch/librefetch-install.in
@@ -0,0 +1,114 @@
+#!/usr/bin/env bash
+# lirefetch-install: (un)install librefetch to /etc/makepkg.conf
+#
+# Copyright (C) 2013-2015 Luke Shumaker <lukeshu@sbcglobal.net>
+#
+# License: GNU GPLv3+
+#
+# This file is part of Parabola.
+#
+# Parabola is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Parabola is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Parabola. If not, see <http://www.gnu.org/licenses/>.
+
+set -ueE
+. "$(librelib messages)"
+
+# This line should be installed
+new_code='. @pkgconfdir@/librefetch-makepkg.conf # This line was added by librefetch-install'
+
+# These lines were installed by previous versions of this script
+old_code=(
+ '# The following line is added by the libretools post_install script'
+ '[[ ! -x /usr/bin/librefetch ]] || DLAGENTS+=("libre::/usr/bin/librefetch -p \"\$BUILDFILE\" %u %o")'
+ '[[ ! -x /usr/bin/librefetch ]] || DLAGENTS+=({https,libre}"::/usr/bin/librefetch -p \"\$BUILDFILE\" -- %u %o")'
+ 'DLAGENTS+=({https,libre}"::/usr/bin/librefetch -p $(printf "%q" "$BUILDFILE") -- %u %o")'
+ 'DLAGENTS+=({https,libre}'\''::/usr/bin/librefetch -p "$BUILDFILE" -- %u %o'\'')'
+)
+
+# has_line $file $line
+has_line() {
+ local file=$1
+ local line=$2
+ grep -Fxq -- "$line" "$file"
+}
+# del_line $file $line
+del_line() {
+ local file=$1
+ local line=$2
+ local lineno=($(grep -Fxn -- "$line" "$file" | cut -d: -f1))
+ if [[ "${#lineno[@]}" -gt 0 ]]; then
+ sed -i "$(printf '%dd;' "${lineno[@]}")" "$file"
+ fi
+}
+
+# post_install $file
+post_install() {
+ local file=$1
+ if grep -q 'librefetch' "$file"; then
+ msg2 "%s: librefetch is already in %q" "${0##*/}" "$(realpath -s "$file")"
+ local line del=false
+ for line in "${old_code[@]}"; do
+ if has_line "$file" "$line"; then
+ msg2 "%s: ... but it's an old version" "${0##*/}"
+ pre_remove "$file"
+ post_install "$file"
+ return $?
+ fi
+ done
+ else
+ msg2 "%s: adding librefetch to %q" "${0##*/}" "$(realpath -s "$file")"
+ printf '%s\n' "$new_code" >> "$file"
+ fi
+}
+
+# pre_remove $file
+pre_remove() {
+ local file=$1
+ msg2 "%s: removing librefetch from %q" "${0##*/}" "$(realpath -s "$file")"
+
+ # Check for the old stuff
+ sed -ri 's/^#(.*) # commented out by the libretools post_install script/\1/' "$file"
+ local line
+ for line in "${old_code[@]}"; do
+ del_line "$file" "$line"
+ done
+
+ # Check for the current stuff
+ del_line "$file" "$new_code"
+}
+
+usage() {
+ print "Usage: %s [install|remove] MAKEPKG_CONF_FILE" "${0##*/}"
+ print "Adds or removes librefetch to/from makepkg.conf:DLAGENTS"
+}
+
+main() {
+ if [[ $# != 2 ]]; then
+ usage >&2
+ return 1
+ fi
+ local file=$2
+ if [[ ! -f "$file" ]]; then
+ msg2 "%s: does not exist: %q" "${0##*/}" "$(realpath -s "$file")"
+ fi
+ if [[ ! -w "$file" ]]; then
+ msg2 "%s: cannot write to file: %q" "${0##*/}" "$(realpath -s "$file")"
+ fi
+ case "$1" in
+ install) post_install "$file";;
+ remove) pre_remove "$file";;
+ *) usage >&2; return 1;;
+ esac
+}
+
+main "$@"
diff --git a/src/librefetch/librefetch-makepkg.conf.in b/src/librefetch/librefetch-makepkg.conf.in
new file mode 100644
index 0000000..723bc15
--- /dev/null
+++ b/src/librefetch/librefetch-makepkg.conf.in
@@ -0,0 +1,16 @@
+#!/hint/bash
+
+# This file should be 'source'd by makepkg.conf to enable librefetch
+edit_dlagents() {
+ local dlagent="@bindir@/librefetch -p $(printf %q "$(realpath -Lm "${BUILDFILE:-${BUILDSCRIPT:-PKGBUILD}}")") -- %u %o"
+
+ local i
+ for i in "${!DLAGENTS[@]}"; do
+ if [[ "${DLAGENTS[$i]}" = https::* ]]; then
+ DLAGENTS[$i]="https::$dlagent"
+ fi
+ done
+ DLAGENTS+=("libre::$dlagent")
+}
+edit_dlagents
+unset -f edit_dlagents
diff --git a/src/librefetch/librefetch.8.ronn b/src/librefetch/librefetch.8.ronn
new file mode 100644
index 0000000..cf009de
--- /dev/null
+++ b/src/librefetch/librefetch.8.ronn
@@ -0,0 +1,214 @@
+librefetch(8) -- downloads or creates a liberated source tarball
+================================================================
+
+## SYNOPSIS
+
+`librefetch` [<OPTIONS>] <SOURCE-URL> [<OUTPUT-FILE>]<br>
+`librefetch` `-`[`g`|`S`|`M`|`h`]
+
+## DESCRIPTION
+
+`librefetch` is a program to streamline creation of custom source
+tarballs for `PKGBUILD(5)` files.
+
+If a URL mentioned in the <source> array in a `PKGBUILD` is in a
+location that Parabola uploads "custom" source tarballs to (or
+configured locations), and no file is at that URL, librefetch will
+automatically create it for you.
+
+This works because a post-install script for the package configures
+`librefetch` as the download agent for `https://` URLs in
+`makepkg.conf`; allowing it to jump in and create a file if need be.
+Because of this, it is almost never necessary to call `librefetch`
+manually.
+
+The post-install script also configures `librefetch` as the download
+agent for `libre://` URLs, for compatibility with `PKGBUILD` files
+that used a previous version of librefetch.
+
+There are 5 modes:
+
+ * `download`: Download the tarball from the configured mirror.
+ * `create`: Create the tarball from a `PKGBUILD`/`SRCBUILD`.
+ * `checksums`: Generate integrity checks for source files.
+ * `srcbuild`: Print the effective build script.
+ * `makepkg`: Generate and print the location of the effective makepkg
+ script.
+ * `help`: Print `librefetch` usage information.
+
+The normal mode of operation is `download` mode. If `download` mode
+fails, it may choose to try `create` mode.
+
+## OPTIONS
+
+ * `-C`: Force `create` mode (don't download)
+ * `-D`: Force `download` mode (don't create)
+ * `-p` <file>: Use an alternate build script for `create` mode
+ (instead of `PKGBUILD`). If an `SRCBUILD` file exists in the same
+ directory, it is used instead.
+ * `-g` | `--geninteg`: Use `checksums` mode: Generate integrity
+ checks for source files.
+ * `-S` | `--srcbuild`: Use `srcbuild` mode: print the effective build
+ script.
+ * `-M` | `--makepkg`: Use `makepkg` mode: generate and print the
+ location of the effective makepkg script.
+ * `-h` | `--help`: Use `help` mode: Show usage information.
+
+Other options, if they are documented in `makepkg -h`, are passed to
+the modified copy of makepkg created during `create` mode.
+
+## DOWNLOAD MODE
+
+If <SOURCE-URL> begins with the string `libre://`, it is replaced with
+the first value in <MIRRORS>, as configured in `librefetch.conf(5)`;
+this is for compatibility with `PKGBUILD` files that used a previous
+version of librefetch.
+
+It uses <DOWNLOADER>, as configured in `librefetch.conf` to attempt to
+download the source tarball from that URL. If that fails, and
+following conditions are met, it proceeds to `create` mode:
+
+ * The `-D` flag has not been specified to inhibit `create` mode.
+ * The <SOURCE-URL> begins with one of the values in <MIRRORS>.
+
+The latter requirement allows librefetch to be used as a generic
+HTTP(S) download agent, that can automatically create files from
+whitelisted locations.
+
+## CREATE MODE
+
+The principle of `create` mode is that a special `PKGBUILD(5)` (called
+`SRCBUILD(5)`) installs source files to <$pkgdir>, and the resulting
+"package" is then used as a source tarball. The `SRCBUILD` exists in
+the same directory as the `PKGBUILD`. It can be created manually, or
+generated on-the-fly from the `PKGBUILD`. Extra steps are taken to
+ensure that as long as the same directory contents go in, an identical
+tarball will come out--the checksum of the file should not change
+based on when it is built or who builds it.
+
+The `SRCBUILD` is either created, or sanitized if it already exists.
+If the output filename does not end with `.sig` or `.sig.part`, then
+the `SRCBUILD` is fed to a modified version of `makepkg(8)`. If the
+output filename does end with `.sig` or `.sig.part`, then it uses GPG
+to create a signature. If the file it is trying to sign does not
+exist yet, librefetch recurses on itself to create it.
+
+The reason `makepkg` must be modified is that we need the resulting
+tarball to be deterministic (as well as not containing package
+metadata).
+
+When this documentation speaks of a file being modified, it is a
+temporary copy of the file that is modified, your original file will
+remain intact.
+
+## SRCBUILD GENERATION
+
+As explained in the `CREATE MODE` section, in `create` mode, this
+program generates an `SRCBUILD` file. For debugging purposes, this
+file can be printed instead of executed with `srcbuild` mode.
+
+### PRE-EXISTING SRCBUILD
+
+The use of `SRCBUILD` files pre-dates `librefetch`. By convention,
+they set <PKGDEST> and <PKGEXT> in `package()` in order to modify the
+behavior of `makepkg`. Because a modified version of `makepkg` is
+used, this interferes with the correct behavior. To compensate for
+this, lines containing "`PKGDEST=`" or "`PKGEXT=`" are deleted from
+the `SRCBUILD`.
+
+The general idea is that `build()` makes any modifications to the
+source, then `package()` copies it from <$srcdir> to <$pkgdir>.
+
+### SRCBUILD FROM PKGBUILD
+
+Possibly more elegant than having a separate `SRCBUILD` file is having
+an `mksource()` function in the main `PKGBUILD`. This results in less
+boilerplate and fewer files to edit.
+
+Note that this only happens if a file named `SRCBUILD` doesn't already
+exist; when migrating a package from a manually created `SRCBUILD` to
+this method, the `SRCBUILD` must be deleted (or renamed) for this to
+work.
+
+The dynamically created `SRCBUILD` is created by copying `PKGBUILD` to
+a temporary file, then re-setting variables and re-defining functions.
+Following is a table of the translations.
+
+ Variables
+ source = mksource
+ noextract = mknoextract
+ *sums = mk*sums (md5, sha1, sha224, sha256, sha384, sha512)
+ depends = <empty>
+ checkdepends = <empty>
+ makedepends = mkdepends
+ *_$CARCH = <unset>
+ Functions
+ prepare() { :; }
+ build() { mksource; }
+ check() { :; }
+ package() { cp -a "$srcdir"/*/ "$pkgdir/"; }
+
+The `mksource()` function does not need to be defined. If it isn't
+defined, then no transformations will be made to the source between it
+being extracted to <$srcdir> and copied to <$pkgdir>.
+
+In summary:
+
+ * Set <mksource=()> and <mkmd5sums=()> to act as <source=()> and
+ <md5sums=()>, respectively.
+ * Declare a `mksource()` function to make modifications to the
+ source, if necessary.
+
+Other changes:
+
+ * <pkgname> is set to <pkgbase>, or the first element of the
+ <pkgname> array (the effect is that split packaging is turned
+ off).
+ * <options=()> is set have `makepkg` avoid making changes to
+ <$pkgdir>. The exact change is:
+
+ options=(!strip docs libtool staticlibs emptydirs !zipman purge !upx)
+
+ * <PURGE_TARGETS=()> has VCS directories added to it:
+
+ PURGE_TARGETS=(.bzr/ .cvs/ .git/ .hg/ .svn/ .makepkg/)
+
+### MAKEPKG MODIFICATIONS
+
+The following modifications are made to makepkg:
+
+ * Allow us to manipulate the output file (<$pkg_file>)
+ * Do not include metadata in the output file (<${comp_files[@]}>)
+ * Force 'ustar' tar format, don't allow it to upgrade to 'pax' to
+ store extended file attributes.
+ * Don't symlink the resulting file into the current directory.
+ * <PURGE_TARGETS> interprets an item as a directory if it ends with a
+ slash ("/").
+ * Timestamps in <$pkgdir> are reset to the date specified in
+ <SOURCE_DATE_EPOCH>[1], or "1990-01-01 0:0:0 +0" if it's not set, so
+ that the resulting tarball will be the same, regardless of when it
+ was created.
+ * Sort the files included in the tarball; normally the order of files
+ in a tarball is essentially random (even if it tends to be the same
+ when re-created on the same machine).
+ * append `-libre` to <$srcdir>
+ * append `-libre` to <$pkgbasedir> (which becomes <$pkgdir>)
+ * Don't check if the package has already been built.
+
+For debugging purposes, this modified makepkg can be printed instead
+of executed with `makepkg` mode. Before it is run in create mode,
+`PKGEXT`, `PKGDEST`, and `pkg_file` are set as environment variables.
+
+## CONFIGURATION
+
+See `librefetch.conf(5)` for details on configuring librefetch using
+the `librefetch.conf` file.
+
+## SEE ALSO
+
+librefetch.conf(5), makepkg(8), PKGBUILD(5), SRCBUILD(5)
+
+## NOTES
+
+ 1. <SOURCE_DATE_EPOCH> specification for build systems
+ https://reproducible-builds.org/specs/source-date-epoch/
diff --git a/src/librefetch/librefetch.conf b/src/librefetch/librefetch.conf
new file mode 100644
index 0000000..7251ff3
--- /dev/null
+++ b/src/librefetch/librefetch.conf
@@ -0,0 +1,7 @@
+MIRRORS=(
+ 'https://repo.parabola.nu/sources/'
+ 'https://repo.parabola.nu/other/'
+ 'https://repo.parabolagnulinux.org/sources/'
+ 'https://repo.parabolagnulinux.org/other/'
+)
+DOWNLOADER='/usr/bin/curl -fLC - --retry 3 --retry-delay 3 -o %o %u'
diff --git a/src/librefetch/librefetch.conf.5.ronn b/src/librefetch/librefetch.conf.5.ronn
new file mode 100644
index 0000000..29df4d2
--- /dev/null
+++ b/src/librefetch/librefetch.conf.5.ronn
@@ -0,0 +1,42 @@
+librefetch.conf(5) -- librefetch configuration file
+===================================================
+
+## SYNOPSIS
+
+`/etc/libretools.d/librefetch.conf`, `~/.config/libretools/librefetch.conf`
+
+## DESCRIPTION
+
+Configuration for librefetch is stored in `librefetch.conf`. The
+several places it looks for the file are:
+
+ * `/etc/libretools.d/librefetch.conf`
+ * `$XDG_CONFIG_HOME/libretools/librefetch.conf`
+
+The later files take precedence over earlier files, but earlier files
+are loaded, so that later files only need to set the values they want
+to override.
+
+If `$XDG_CONFIG_HOME` is not set, a default value is set:
+
+ * if `$SUDO_USER` is set: `$(eval echo ~$SUDO_USER)/.config`
+ * else: `$HOME/.config`
+
+## OPTIONS
+
+ * `MIRRORS=( ... )`:
+ A list of locations that generated source tarballs may be located
+ at. If a URL begins with `libre://`, then `libre://` is replaced
+ with the first location listed here.
+ * `DOWNLOADER='/usr/bin/curl -fLC - --retry 3 --retry-delay 3 -o %o %u'`:
+ The HTTP client to use when downloading pre-built source tarballs
+ in download mode. The format and semantics are similar to
+ `DLAGENTS` in `makepkg.conf`(5). If present, `%u` will be replaced
+ with the download URL (correctly quoted), otherwise the download
+ URL will be appended to the end of the command. If present, `%o`
+ will be replaced with the local filename that it should be
+ downloaded to.
+
+## SEE ALSO
+
+librefetch(8), makepkg.conf(5)
diff --git a/src/librefetch/librefetchdir/Makefile b/src/librefetch/librefetchdir/Makefile
new file mode 100644
index 0000000..58116bb
--- /dev/null
+++ b/src/librefetch/librefetchdir/Makefile
@@ -0,0 +1,56 @@
+#!/usr/bin/env bash
+# librefetchdir/Makefile
+#
+# Copyright (C) 2016 Luke Shumaker <lukeshu@sbcglobal.net>
+#
+# License: GNU GPLv3+
+#
+# This file is part of LibreFetch.
+#
+# LibreFetch is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# LibreFetch is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with LibreFetch. If not, see <http://www.gnu.org/licenses/>.
+
+# new =
+librefetchdir := $(dir $(lastword $(MAKEFILE_LIST)))
+
+old_makepkg := $(shell which makepkg)
+old_library := $(shell $(shell grep LIBRARY= $(old_makepkg)); echo $$LIBRARY)
+
+new_makepkg = $(new)/makepkg
+new_library = $(new)/libmakepkg
+
+targets += $(new_makepkg)
+targets += $(patsubst $(old_library)/%,$(new_library)/%,$(shell find $(old_library) -type f))
+targets += $(new_library)/tidy/~source_date_epoch.sh
+
+all: $(targets)
+.PHONY: all
+
+.SECONDARY:
+.DELETE_ON_ERROR:
+
+$(new_makepkg): $(old_makepkg) $(librefetchdir)/makepkg.gen
+ <$^ | install -Dm755 /dev/stdin $@
+
+$(new_library)/source.sh: \
+$(new_library)/%: $(old_library)/% $(librefetchdir)/libmakepkg/%.gen
+ <$^ | install -Dm755 /dev/stdin $@
+
+$(new_library)/tidy/purge.sh $(new_library)/tidy/~source_date_epoch.sh: \
+$(new_library)/%: $(librefetchdir)/libmakepkg/%
+ mkdir -p $(@D)
+ ln -sfT $(abspath $< $@)
+
+$(new_library)/%: $(old_library)/%
+ mkdir -p $(@D)
+ ln -sfT $(abspath $< $@)
diff --git a/src/librefetch/librefetchdir/libmakepkg/source.sh.gen b/src/librefetch/librefetchdir/libmakepkg/source.sh.gen
new file mode 100755
index 0000000..269f9fd
--- /dev/null
+++ b/src/librefetch/librefetchdir/libmakepkg/source.sh.gen
@@ -0,0 +1,25 @@
+#!/usr/bin/sed -rf
+# librefetchdir/makepkg.gen
+#
+# Copyright (C) 2013, 2016 Luke Shumaker <lukeshu@sbcglobal.net>
+#
+# License: GNU GPLv3+
+#
+# This file is part of LibreFetch.
+#
+# LibreFetch is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# LibreFetch is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with LibreFetch. If not, see <http://www.gnu.org/licenses/>.
+
+/download_sources\(\) \{/ {
+ arm -rf "$srcdir"\nmkdir "$srcdir"
+}
diff --git a/src/librefetch/librefetchdir/libmakepkg/tidy/purge.sh b/src/librefetch/librefetchdir/libmakepkg/tidy/purge.sh
new file mode 100755
index 0000000..acb0030
--- /dev/null
+++ b/src/librefetch/librefetchdir/libmakepkg/tidy/purge.sh
@@ -0,0 +1,58 @@
+#!/usr/bin/bash
+#
+# purge.sh - Remove unwanted files from the package
+#
+# Copyright (c) 2008-2016 Pacman Development Team <pacman-dev@archlinux.org>
+# Copyright (c) 2013-2016 Luke Shumaker <lukeshu@sbcglobal.net>
+#
+# License: GNU GPLv2+
+#
+# This file is part of LibreFetch
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+
+[[ -n "$LIBMAKEPKG_TIDY_PURGE_SH" ]] && return
+LIBMAKEPKG_TIDY_PURGE_SH=1
+
+LIBRARY=${LIBRARY:-'/usr/share/makepkg'}
+
+source "$LIBRARY/util/message.sh"
+source "$LIBRARY/util/option.sh"
+
+
+packaging_options+=('purge')
+tidy_remove+=('tidy_purge')
+
+tidy_purge() {
+ if check_option "purge" "y" && [[ -n ${PURGE_TARGETS[*]} ]]; then
+ msg2 "$(gettext "Purging unwanted files...")"
+ local pt
+ for pt in "${PURGE_TARGETS[@]}"; do
+ if [[ ${pt} = "${pt%/}" ]]; then
+ if [[ ${pt} = "${pt//\/}" ]]; then
+ find . ! -type d -name "${pt}" -exec rm -f -- '{}' +
+ else
+ rm -f "${pt}"
+ fi
+ else
+ if [[ ${pt%/} = "${pt//\/}" ]]; then
+ find . -type d -name "${pt%/}" -exec rm -rf -- '{}' +
+ else
+ rm -rf "${pt}"
+ fi
+ fi
+ done
+ fi
+}
diff --git a/src/librefetch/librefetchdir/libmakepkg/tidy/~source_date_epoch.sh b/src/librefetch/librefetchdir/libmakepkg/tidy/~source_date_epoch.sh
new file mode 100755
index 0000000..9141d67
--- /dev/null
+++ b/src/librefetch/librefetchdir/libmakepkg/tidy/~source_date_epoch.sh
@@ -0,0 +1,38 @@
+#!/usr/bin/bash
+#
+# librefetchdir/libmakepkg/tidy/~source_date_epoch.sh
+#
+# Copyright (C) 2013-2016 Luke Shumaker <lukeshu@sbcglobal.net>
+#
+# License: GNU GPLv3+
+#
+# This file is part of LibreFetch.
+#
+# LibreFetch is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# LibreFetch is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with LibreFetch. If not, see <http://www.gnu.org/licenses/>.
+
+# This filename starts with a ~ because it sorts after every other
+# (printable) ASCII character, and we want this to run last.
+
+[[ -n "$LIBMAKEPKG_TIDY_SOURCE_DATE_EPOCH_SH" ]] && return
+LIBMAKEPKG_TIDY_SOURCE_DATE_EPOCH_SH=1
+
+tidy_modify+=('tidy_source_date_epoch')
+
+tidy_source_date_epoch() {
+ local date='1990-01-01 0:0:0 +0'
+ if [[ -n "$SOURCE_DATE_EPOCH" ]]; then
+ date="@$SOURCE_DATE_EPOCH"
+ fi
+ find . -exec touch --no-dereference --date="$date" -- {} +
+}
diff --git a/src/librefetch/librefetchdir/makepkg.gen b/src/librefetch/librefetchdir/makepkg.gen
new file mode 100755
index 0000000..8928d91
--- /dev/null
+++ b/src/librefetch/librefetchdir/makepkg.gen
@@ -0,0 +1,42 @@
+#!/usr/bin/sed -rf
+# librefetchdir/makepkg.gen
+#
+# Copyright (C) 2013-2016 Luke Shumaker <lukeshu@sbcglobal.net>
+#
+# License: GNU GPLv3+
+#
+# This file is part of LibreFetch.
+#
+# LibreFetch is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# LibreFetch is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with LibreFetch. If not, see <http://www.gnu.org/licenses/>.
+
+/LIBRARY=/iexport LIBRARY="${0%/*}/libmakepkg"
+
+/create_package\(\) \{/,/^\}$/ {
+ /pkg_file=/d # allow us to set pkg_file
+ s/"?\$\{comp_files\[@\]\}"?// # do not include .{PKGINFO,BUILDINGO,CHANGELOG,INSTALL,MTREE}
+ # This is long/gross. What it does:
+ # - pass --format=ustar to bsdtar, to inhibit it using the pax format
+ # - take the files that would be included in the tarball, and use
+ # find/sort/--files-from to order them for bsdtar
+ s/bsdtar(.*) - ([^|]*) \|/find \2 -print0 | LC_ALL=C sort --zero-terminated | bsdtar --null --files-from - --format=ustar --no-recursion \1 - |/
+ s/create_signature .*/&; return $?/ # do not procede to create symlinks
+}
+
+s|Making package:|Making source:|
+s|Checking runtime dependencies\.\.\.|Checking source dependencies...|
+ /Checking buildtime dependencies\.\.\./d
+
+s|srcdir=.*|&-libre|
+s|pkgdirbase=.*|&-libre|
+s|check_build_status$|:|
diff --git a/src/libretools.conf b/src/libretools.conf
new file mode 100644
index 0000000..fb4593c
--- /dev/null
+++ b/src/libretools.conf
@@ -0,0 +1,72 @@
+#!/hint/bash
+
+################################################################################
+# misc #
+################################################################################
+
+# The dir where you work on
+WORKDIR="$LIBREHOME/packages"
+
+## Blacklist URL
+BLACKLIST=https://projects.parabola.nu/blacklist.git/plain/blacklist.txt
+
+## Diff tool (vimdiff, gvimdiff, meld, etc)
+## Used by `aur`, `diff-unfree`
+DIFFPROG=$(which $([ -z "${DISPLAY:-}" ]||echo kdiff3 meld gvimdiff) vimdiff colordiff diff 2>/dev/null|sed 's/\s.*//;1q')
+
+## The architectures you'll be packaging for
+## Used by `librestage`, `xbs-abslibre`
+ARCHES=($(printf '%s\n' /usr/share/pacman/defaults/pacman.conf.*|sed 's|.*\.||'))
+
+## ABSLibre
+# Used by xbs-abslibre
+ABSLIBRERECV=git://projects.parabola.nu/abslibre/abslibre.git
+ABSLIBRESEND=ssh://git@projects.parabola.nu/srv/git/abslibre/abslibre.git
+ABSLIBREDEST="$WORKDIR/staging/abslibre"
+
+################################################################################
+# librerelease #
+################################################################################
+
+## Where to upload packages to
+# '/staging/' is appended; this is for compatibility with previous versions.
+REPODEST=repo@parabola.nu:staging/$LIBREUSER
+
+## These are run before and after uploading packages
+HOOKPRERELEASE="ssh -fN ${REPODEST%%:*}"
+HOOKPOSTRELEASE="sudo librechroot clean-repo"
+
+################################################################################
+# dagpkg #
+################################################################################
+
+# Note: Not being set is valid for any of the HOOK* settings.
+
+# Run a command before running FULLBUILDCMD
+HOOKPREBUILD=""
+
+## Uncomment one of those or make one of your choice
+# Normal
+FULLBUILDCMD="sudo libremakepkg"
+# Cross compiling
+#FULLBUILDCMD="sudo libremakepkg -n cross-compile-chroot"
+# Don't use a chroot
+#FULLBUILDCMD="makepkg -sL --noconfirm"
+
+# Locally release the package or any other action after running FULLBUILDCMD
+# successfully. When run, it is given a repository name as a single argument.
+HOOKLOCALRELEASE="librestage"
+
+################################################################################
+# toru #
+################################################################################
+
+TORUPATH=/var/lib/libretools/toru
+
+## The repos you'll be packaging for
+## Used by: `toru-path`
+# Tip: As early repos take precedence on $REPOS loops, you can use this as
+# inverted order of precedence. Put testing repos first so dagpkg will find new
+# PKGBUILDs first, for instance. `toru-path` uses reverse order to enforce repo
+# precedence on the path cache (the last path added replaces the rest)
+REPOS=('core' 'libre' 'extra' 'community' 'libre-testing' 'social' 'sugar' 'pcr' 'java')
diff --git a/src/pkgbuild-check-nonfree b/src/pkgbuild-check-nonfree
new file mode 100755
index 0000000..67f07bc
--- /dev/null
+++ b/src/pkgbuild-check-nonfree
@@ -0,0 +1,309 @@
+#!/usr/bin/env bash
+# -*- tab-width: 4 ; sh-basic-offset: 4 -*-
+# pkgbuild-check-nonfree
+
+# Copyright (C) 2011 Joseph Graham (Xylon) <joe@t67.eu>
+# Copyright (C) 2010-2011 Joshua Ismael Haase Hernández (xihh) <hahj87@gmail.com>
+# Copyright (C) 2010-2012 Nicolás Reynolds <fauno@parabola.nu>
+# Copyright (C) 2012-2013 Luke Shumaker <lukeshu@sbcglobal.net>
+#
+# License: GNU GPLv3+
+#
+# This file is part of Parabola.
+#
+# Parabola is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Parabola is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Parabola. If not, see <http://www.gnu.org/licenses/>.
+
+# I appologize that this program got *huge*.
+# It's not complicated, just long.
+
+. "$(librelib messages)"
+. "$(librelib conf)"
+. "$(librelib blacklist)"
+
+usage() {
+ print "Usage: %s [OPTIONS] [PKGBUILD1 PKGBUILD2 ...]" "${0##*/}"
+ print "Analyzes a PKGBUILD for freedom issues"
+ echo
+ prose 'If no PKGBUILD is specified, `./PKGBUILD` is implied.'
+ echo
+ print "Exit status (add them for combinations):"
+ print " 0: Everything OK, no freedom issues"
+ print " 1: Ran with error"
+ print "Warning-level freedom issues:"
+ print " 2: Uses unrecognized licenses, check them"
+ print " 4: Uses GPL-incompatible licenses"
+ print "Error-level freedom issues:"
+ print " 8: Uses known unacceptable licenses"
+ print " 16: Has nonfree dependencies"
+ print " 32: Is a known nonfree package"
+ echo
+ print "Options:"
+ flag '-c' 'Use the cached blacklist, do not try downloading'
+ flag '-f' 'Allow running as root user'
+ echo
+ flag '-q' 'Be quiet'
+ flag '-v' 'Be verbose'
+ echo
+ flag '-h' 'Show this message'
+}
+# Make sure these match pkgbuild-summarize-nonfree
+declare -ri _E_OK=0
+declare -ri _E_ERROR=1
+declare -ri _E_LIC_UNKNOWN=2
+declare -ri _E_LIC_NOGPL=4
+declare -ri _E_LIC_NONFREE=8
+declare -ri _E_DEP_NONFREE=16
+declare -ri _E_PKG_NONFREE=32
+
+main() {
+ # Parse flags
+ local cache=false
+ local asroot=false
+ local v=1
+ while getopts 'cfqvh' arg; do
+ case "$arg" in
+ c) cache=true;;
+ f) asroot=true;;
+ q) v=0;;
+ v) v=2;;
+ h) usage; return $_E_OK;;
+ *) usage >&2; return $_E_ERROR;;
+ esac
+ done
+ shift $(($OPTIND - 1))
+ if [[ $# -lt 1 ]]; then
+ pkgbuilds=("`pwd`/PKGBUILD")
+ else
+ pkgbuilds=("$@")
+ fi
+
+ # Do a check to see if we are running as root
+ if [[ -w / ]] && ! $asroot; then
+ error "Run as normal user, or use the -f option to run as root."
+ return 1
+ fi
+
+ # Adjust the verbosity
+ if [[ $v == 0 ]]; then
+ error() { :; }
+ warning() { :; }
+ plain() { :; }
+ info() { :; }
+ elif [[ $v == 1 ]]; then
+ info() { :; }
+ elif [[ $v == 2 ]]; then
+ info() { plain "$@"; }
+ fi
+
+ # Update the blacklist
+ $cache || blacklist-update || return $_E_ERROR
+
+ # Do the work
+ declare -i ret=0
+ local pkgbuild
+ for pkgbuild in "${pkgbuilds[@]}"; do
+ pkgbuild_check "$pkgbuild" || ret=$(($ret|$?))
+ done
+ return $ret
+}
+
+# Helper functions #############################################################
+# These should maybe be moved into lib/conf.sh
+
+# Usage: var="$(pkgbuild_get_pkg_str ${pkgname} ${varname})"
+# Gets a package-level string for a split-package
+pkgbuild_get_pkg_str() {
+ [[ $# == 2 ]] || panic 'malformed call to pkgbuild_get_pkg_str'
+ local pkg=$1
+ local var=$2
+
+ local indirect=${!var}
+ eval $(declare -f package_$pkg | sed -rn "s/^\s*${var}(\+?=)/indirect\1/p")
+ printf '%s' "${indirect}"
+}
+# Usage: eval $(pkgbuild_get_pkg_ary ${pkgname} ${varname} [$variable_name_to_set])
+# Gets a package-level array for a split-package
+pkgbuild_get_pkg_ary() {
+ [[ $# == 2 ]] || [[ $# == 3 ]] || panic 'malformed call to pkgbuild_get_pkg_ary'
+ local pkg=$1
+ local var=$2
+ local out="${3:-$var}"
+
+ local ary="${var}[@]"
+ local indirect=("${!ary}")
+ eval $(declare -f package_$pkg | sed -rn "s/^\s*${var}(\+?=)/indirect\1/p")
+ declare -p indirect|sed "s/ indirect=/ ${out}=/"
+}
+
+# Checker functions ############################################################
+
+# Usage: check_lic "${licence}"
+# Check a license name to see if it is OK
+check_lic() {
+ [[ $# == 1 ]] || panic 'malformed call to check_license'
+ local license=$1
+
+ info 'Checking license: %s' "$license"
+
+ if [[ -e "/usr/share/licenses/common/$license" ]]; then
+ return $_E_OK
+ else
+ case "${license#custom:}" in
+ WTFPL)
+ # accept as common, I think it should be in the licenses package
+ return $_E_OK;;
+ BSD1|BSD2|BSD3|MIT|X11)
+ # accept these as common; they can't be included in the
+ # 'licenses' package because some text must be customized
+ return $_E_OK;;
+ BSD4)
+ warning "The 4-clause BSD license is free but has practical problems."
+ return $_E_LIC_NOGPL;;
+ BSD)
+ warning "License 'BSD' is ambiguous, use 'BSD{1..4}' to specify the number of clauses."
+ return $_E_LIC_UNKNOWN;;
+ JSON)
+ error "License '%s' is a known non-free license." "$license"
+ return $_E_LIC_NONFREE;;
+ *)
+ warning "License '%s' is not a common (recognized) license." "$license"
+ return $_E_LIC_UNKNOWN;;
+ esac
+ fi
+ panic 'code should never be reached'
+}
+
+# Usage: check_dep "${dependency}"
+# Checks for ${dependency} in the blacklist
+check_dep() {
+ [[ $# == 1 ]] || panic 'malformed call to check_dep'
+ local pkg=$1
+
+ local line="$(blacklist-cat|blacklist-lookup "$pkg")"
+ local rep="$(blacklist-get-rep <<<"$line")"
+ if [[ -z $line ]]; then
+ # not mentioned in blacklist; free
+ info '%s: not blacklisted' "$pkg"
+ return $_E_OK
+ elif [[ -z $rep ]]; then
+ # non-free with no replacement
+ plain '%s: blacklisted' "$pkg"
+ return $_E_DEP_NONFREE
+ else
+ # non-free with free replacement
+ if [[ "$rep" == "$pkg" ]]; then
+ info '%s: repackaged with the same name' "$pkg"
+ else
+ info '%s: replaced by %s' "$pkg" "$rep"
+ fi
+ return $_E_OK
+ fi
+ panic 'code should never be reached'
+}
+
+# Usage: check_pkg "${pkgname}"
+# Checks for ${pkgname} in the blacklist
+check_pkg() {
+ [[ $# == 1 ]] || panic 'malformed call to check_pkg'
+ check_dep "$@"
+ case $? in
+ $_E_OK)
+ return $_E_OK;;
+ $_E_DEP_NONFREE)
+ return $_E_PKG_NONFREE;;
+ *)
+ panic 'unexpected return code from check_dep';;
+ esac
+ panic 'code should never be reached'
+}
+
+# Usage: pkgbuild_ckec $pkgbuild
+# Check whether a PKGBUILD has any issues (using the above)
+pkgbuild_check() (
+ [[ $# == 1 ]] || panic 'malformed call to pkgbuild_check'
+ local pkgbuild=$1
+
+ load_PKGBUILD "$pkgbuild"
+ if [[ -z $pkgname ]]; then
+ return $_E_ERROR # not a PKGBUILD
+ fi
+
+ declare -i ret=0 # the return status
+ local dep lic # iterators for us in `for` loops
+ local ck_deps ck_lics # lists of deps and licenses that have been checked
+
+ if [[ ${#pkgname[@]} == 1 ]]; then
+ msg2 'Inspecting package pkgname=%q (%s)' "$pkgname" "$(get_full_version)"
+ else
+ msg2 'Inspecting split package pkgbase=%q (%s)' "${pkgbase:-${pkgname[0]}}" "$(get_full_version)"
+ fi
+
+ # Check if this is blacklisted
+ check_pkg "${pkgbase:-${pkgname[0]}}" || ret=$(($ret|$?))
+ # Check if dependencies are blacklisted
+ for dep in "${depends[@]}" "${makedepends[@]}" "${checkdepends[@]}" \
+ "${optdepends[@]%%:*}" "${mkdepends[@]}"
+ do
+ check_dep "$dep" || ret=$(($ret|$?))
+ ck_deps+=("$dep")
+ done
+ # Check the licenses
+ for lic in "${license[@]}"; do
+ check_lic "$lic" || ret=$(($ret|$?))
+ ck_lics+=("$lic")
+ done
+
+ if [[ ${#pkgname[@]} == 1 ]]; then
+ # Non-split package
+ # Make sure a license is set
+ if [[ ${#ck_lics[@]} == 0 ]]; then
+ error "The license array is empty"
+ ret=$(($ret|$_E_ERROR))
+ fi
+ else
+ # Split package
+ # Check the individual split packages
+ local _pkgname _license _depends _optdepends
+ for _pkgname in "${pkgname[@]}"; do
+ msg2 'Inspecting split package pkgname=%q (%s)' "$_pkgname" "$(get_full_version "$_pkgname")"
+ eval $(pkgbuild_get_pkg_ary "$_pkgname" license _license)
+ eval $(pkgbuild_get_pkg_ary "$_pkgname" depends _depends)
+ eval $(pkgbuild_get_pkg_ary "$_pkgname" optdepends _optdepends)
+
+ # Check if this is blacklisted
+ check_pkg "$_pkgname" || ret=$(($ret|$?))
+ # Check if dependencies are blacklisted
+ for dep in "${_depends[@]}" "${_optdepends[@]%%:*}"; do
+ if ! in_array "$dep" "${ck_deps[@]}"; then
+ check_dep "$dep" || ret=$(($ret|$?))
+ fi
+ done
+ # Check the licenses
+ for lic in "${_license[@]}"; do
+ if ! in_array "$lic" "${ck_lics[@]}"; then
+ check_lic "$lic" || ret=$(($ret|$?))
+ fi
+ done
+
+ if [[ ${#_license[@]} == 0 ]]; then
+ error "The license array is empty"
+ ret=$(($ret|$_E_ERROR))
+ fi
+ done
+ fi
+
+ return $ret
+)
+
+main "$@"
diff --git a/src/pkgbuild-summarize-nonfree b/src/pkgbuild-summarize-nonfree
new file mode 100755
index 0000000..1e39e87
--- /dev/null
+++ b/src/pkgbuild-summarize-nonfree
@@ -0,0 +1,106 @@
+#!/usr/bin/env bash
+# Copyright (C) 2013 Luke Shumaker <lukeshu@sbcglobal.net>
+#
+# License: GNU GPLv3+
+#
+# This file is part of Parabola.
+#
+# Parabola is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Parabola is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Parabola. If not, see <http://www.gnu.org/licenses/>.
+
+. "$(librelib messages)"
+
+# Make sure these match pkgbuild-check-nonfree
+declare -ri _E_OK=0
+declare -ri _E_ERROR=1
+declare -ri _E_LIC_UNKNOWN=2
+declare -ri _E_LIC_NOGPL=4
+declare -ri _E_LIC_NONFREE=8
+declare -ri _E_DEP_NONFREE=16
+declare -ri _E_PKG_NONFREE=32
+
+usage() {
+ print "Usage: %s [OPTIONS] STATUS" "${0##*/}"
+ print "Summarizes a status code from pkgbuild-check-nonfree"
+ echo
+ prose 'It thresholds the issues it finds, only failing for error-level
+ issues, and ignoring warnings. Unless `-q` is specified, it also
+ prints a summary of the issues it found.'
+ echo
+ print 'Options:'
+ flag '-q' 'Be quiet'
+ flag '-h' 'Show this message'
+}
+
+main() {
+ local quiet=false
+ while getopts 'qh' arg; do
+ case "$arg" in
+ q) quiet=true;;
+ h) usage; return 0;;
+ *) usage >&2; return 1;;
+ esac
+ done
+ shift $(($OPTIND - 1))
+ if [[ $# -ne 1 ]]; then
+ usage >&2
+ return 1
+ fi
+ if ! [[ $1 =~ ^[0-9]+$ ]]; then
+ error 'STATUS must be an integer'
+ usage >&2
+ return 1
+ fi
+
+ if $quiet; then
+ error() { :; }
+ warning() { :; }
+ fi
+
+ parse $1;
+ return $?
+}
+
+parse() {
+ [[ $# == 1 ]] || panic 'malformed call to parse'
+ declare -i s=$1;
+
+ declare -i ret=0
+ declare -i i
+ for i in 1 2 4 8 16 32; do
+ if [[ $(($s & $i)) -gt 0 ]]; then
+ case $i in
+ $_E_ERROR)
+ # could be anything, assume the worst
+ error "There was an error processing the PKGBUILD"
+ ret=1;;
+ $_E_LIC_UNKNOWN)
+ warning "This PKGBUILD has an unknown license";;
+ $_E_LIC_NOGPL)
+ warning "This PKGBUILD has a GPL-incompatible license";;
+ $_E_LIC_NONFREE)
+ error "This PKGBUILD has a known nonfree license"
+ ret=1;;
+ $_E_DEP_NONFREE)
+ error "This PKGBUILD depends on known nonfree packages"
+ ret=1;;
+ $_E_PKG_NONFREE)
+ error "This PKGBUILD is for a known nonfree package"
+ ret=1;;
+ esac
+ fi
+ done
+ return $ret
+}
+
+main "$@"
diff --git a/src/repo-diff b/src/repo-diff
new file mode 100755
index 0000000..d11732b
--- /dev/null
+++ b/src/repo-diff
@@ -0,0 +1,75 @@
+#!/usr/bin/env bash
+# Shows a diff between repo databases
+
+# Copyright (C) 2013 Nicolás Reynolds <fauno@parabola.nu>
+#
+# License: GNU GPLv3+
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+. libremessages
+
+usage() {
+ print "Usage: %s arch/core/i686 parabola/core/i686" "${0##*/}"
+ print "Compares two repo databases using distro/repo/architecture format."
+ echo
+ print 'Shortcuts:'
+ flag 'arch' 'expands to Arch Linux repo url'
+ flag 'parabola' 'expands to Parabola GNU/Linux-libre repo url'
+}
+
+if test $# -eq 0; then
+ usage
+ exit 0
+fi
+
+b() { bsdtar ztf $1 | cut -d "/" -f1 | sort -u ; }
+n() { echo "$1".db | tr "/" "-"; }
+
+# hopefully simple way to convert
+# parabola/libre/i686
+# to
+# http://repo.parabola.nu/libre/os/i686/libre.db
+# add more distros here
+g() {
+ echo "$1" | sed -e "s,^\([^/]\+\)/\([^/]\+\)/\([^/]\+\)$,\1/\2/os/\3/\2.db," \
+ -e "s,^parabola/,http://repo.parabola.nu/," \
+ -e "s,^arch\(linux\)\?/,http://mirrors.kernel.org/archlinux/,"
+}
+
+mkdir ${0##*/}.$$
+pushd ${0##*/}.$$ >/dev/null
+
+d=""
+for i in $1 $2; do
+ n=$(n "$i")
+
+ test -z "$n" && exit 1
+
+ wget -O "$n" -nv $(g "$i")
+ b "$n" >${n}.orig
+
+ d+=" ${n}.orig"
+done
+
+{
+ printf "$(gettext "Difference between %s and %s")\n---\n" $1 $2
+ which diffstat &>/dev/null && diff -auN "${d[@]}" | diffstat
+ diff -auN "${d[@]}"
+} >../${n}.diff
+
+popd >/dev/null
+rm -r ${0##*/}.$$
+
+print "Difference save on %s" "${n}.diff"
diff --git a/src/toru/Makefile b/src/toru/Makefile
new file mode 100644
index 0000000..2903f4a
--- /dev/null
+++ b/src/toru/Makefile
@@ -0,0 +1,4 @@
+include $(dir $(lastword $(MAKEFILE_LIST)))/../../config.mk
+include $(topsrcdir)/automake.head.mk
+
+include $(topsrcdir)/automake.tail.mk
diff --git a/src/toru/toru-info b/src/toru/toru-info
new file mode 100755
index 0000000..a7081cb
--- /dev/null
+++ b/src/toru/toru-info
@@ -0,0 +1,43 @@
+#!/usr/bin/env bash
+# Prints info about a given pkgname
+
+# Copyright (C) 2012 Nicolás Reynolds <fauno@parabola.nu>
+#
+# License: GNU GPLv3+
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+. libremessages
+. "$(librelib conf)"
+
+for _pkg in "$@"; do
+ _pkgbuild="$(toru-where $_pkg)"
+
+ if [ -f "$_pkgbuild/PKGBUILD" ]; then
+ if ! load_PKGBUILD "$_pkgbuild/PKGBUILD" 2>/dev/null; then
+ warning "Errors on %s" "$_pkg"
+ continue
+ fi
+
+ deps=("${depends[@]}" "${makedepends[@]}" "${checkdepends[@]}")
+ repo="$(basename -- "$(dirname -- "$_pkgbuild")")"
+
+ msg "%s/%s %s-%s" "$repo" "$_pkg" "$pkgver" "$pkgrel"
+ msg2 '%s' "$pkgdesc"
+ msg2 '%s' "$url"
+ msg2 'Depends: %s' "${deps[*]}"
+ else
+ warning "%s doesn't exist" "$_pkg"
+ fi
+done
diff --git a/src/toru/toru-path b/src/toru/toru-path
new file mode 100755
index 0000000..4600a5c
--- /dev/null
+++ b/src/toru/toru-path
@@ -0,0 +1,92 @@
+#!/usr/bin/env bash
+
+# Copyright (C) 2011-2012 Nicolás Reynolds <fauno@parabola.nu>
+# Copyright (C) 2012 Michał Masłowski <mtjm@mtjm.eu>
+# Copyright (C) 2012 Joshua Ismael Haase Hernández (xihh) <hahj87@gmail.com>
+# Copyright (C) 2014 Luke Shumaker <lukeshu@sbcglobal.net>
+#
+# License: GNU GPLv3+
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+. libremessages
+setup_traps
+
+# TODO: better option parsing
+TORUPATH=${T:-${TORUPATH}}
+VERBOSE=${V:-false}
+FORCE=${F:-false}
+
+. "$(librelib conf.sh)"
+load_files libretools
+check_vars libretools TORUPATH REPOS || exit 1
+load_files abs
+check_vars abs ABSROOT || exit 1
+
+if [ ! -w "$TORUPATH" ]; then
+ error "Toru's path isn't writable. Please check $TORUPATH"
+ exit 1
+fi
+
+lastsyncfile=${TORUPATH}/lastsync.paths
+pathfile=${TORUPATH}/paths.tch
+
+if [ ! -e "${pathfile}" ]; then
+ tcamgr create "${pathfile}"
+fi
+
+# TODO: ability to use flags to pass in other directories to fullrepos
+
+# This loops over ${REPOS[@]} backward. This is because early entries
+# in REPOS have higher precidence, but the way this is implemented,
+# the later entries have precedence, so we need to flip the order.
+fullrepos=()
+for (( i = ${#REPOS[@]}-1 ; i >= 0 ; i-- )); do
+ $VERBOSE && msg "Processing [%s]" "${REPOS[$i]}"
+
+ # ABSROOT has trailing slash
+ if [ -d "${ABSROOT}${REPOS[$i]}" ]; then
+ fullrepos+=("${ABSROOT}${REPOS[$i]}")
+ fi
+done
+
+# Find PKGBUILDs in ${fullrepos[@]}
+find_args=("${fullrepos[@]}" -mindepth 2 -maxdepth 3 -type f -name PKGBUILD)
+if ! $FORCE && [[ -e $lastsyncfile ]]; then
+ # if lastfilesync exists, only look at things that have
+ # changed since then (unless $FORCE is on)
+ find_args+=(-newer "${lastsyncfile}")
+fi
+IFS=$'\n'
+pkgbuilds=($(find "${find_args[@]}"))
+
+# Add information from each of the PKGBUILDs to the toru cache.
+msg "Updating path cache"
+msg2 "%d PKGBUILDs to update" ${#pkgbuilds[@]}
+for pkgbuild in "${pkgbuilds[@]}"; do
+ # plain "$_pkgbuild"
+ if ! load_PKGBUILD "${_pkgbuild}" >/dev/null 2>&1; then
+ error "%q contains errors, skipping" "${_pkgbuild}"
+ continue
+ fi
+
+ fullpath="$(dirname -- "${_pkgbuild}")"
+
+ for _pkg in "${pkgbase}" "${pkgname[@]}" "${provides[@]}"; do
+ $VERBOSE && msg2 '%s -> %s' "${_pkg}" "${fullpath}"
+ tcamgr put "${pathfile}" "${_pkg%%[<>=]*}" "${fullpath}"
+ done
+done
+
+date +%s > "${lastsyncfile}"
diff --git a/src/toru/toru-where b/src/toru/toru-where
new file mode 100755
index 0000000..f6df15f
--- /dev/null
+++ b/src/toru/toru-where
@@ -0,0 +1,25 @@
+#!/usr/bin/env bash
+# Locates a PKGBUILD dir on toru's path cache
+
+# Copyright (C) 2012 Nicolás Reynolds <fauno@parabola.nu>
+#
+# License: GNU GPLv3+
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+. "$(librelib conf.sh)"
+load_files libretools
+check_vars libretools TORUPATH || exit 1
+
+tcamgr get "${TORUPATH}/paths.tch" "$1" 2>/dev/null || echo ""
diff --git a/src/workflows.md b/src/workflows.md
new file mode 100644
index 0000000..03dca4f
--- /dev/null
+++ b/src/workflows.md
@@ -0,0 +1,64 @@
+# Workflows
+
+Describe your packaging workflow here!
+
+
+## fauno's way
+
+During packaging, I don't usually restart a build from scratch if I have to make
+changes to the PKGBUILD. I use a lot of commenting out commands already ran,
+`makepkg -R`, etc. When I used `libremakepkg` I ended up using a lot more
+`librechroot` and working from inside the unconfigured chroot, because
+`makechrootpkg` (the underlying technology for `libremakepkg`) tries to be too
+smart.
+
+When I started writing `treepkg` I found that mounting what I need directly on
+the chroot and working from inside it was much more comfortable and simple than
+having a makepkg wrapper doing funny stuff (for instance, mangling
+`makepkg.conf` and breaking everything.)
+
+This is how the chroot is configured:
+
+* Create the same user (with same uid) on the chroot that the one I use
+ regularly.
+
+* Give it password-less sudo on the chroot.
+
+* Bind mount `/home` to `/chroot/home`, where I have the abslibre-mips64el
+ clone.
+
+* Bind mount `/var/cache/pacman/pkg` to `/chroot/var/cache/pacman/pkg`
+
+* Put these on system's `fstab` so I don't have to do it everytime
+
+* Configure `makepkg.conf` to `PKGDEST=CacheDir` and `SRCDEST` to something on
+ my home.
+
+Workflow:
+
+* Enter the chroot with `systemd-nspawn -D/chroot` and `su - fauno`.
+
+* From another shell (I use tmux) edit the abslibre or search for updates with
+ `git log --no-merges --numstat`.
+
+* Pick a package and run `treepkg` from its dir on the chroot, or retake a build
+ with `treepkg /tmp/package-treepkg-xxxx`.
+
+ > Note: `treepkg` has been deprecated in favor of `dagpkg`.
+
+What this allows:
+
+* Not having to worry about the state of the chroot. `chcleanup` removes and
+ adds packages in a smart way so shared dependencies stay and others move along
+ (think of installing and removing qt for a complete kde rebuild).
+
+* Building many packages in a row without recreating a chroot for every one of
+ them.
+
+* Knowing that any change you made to the chroot stays as you want (no one
+ touches your makepkg.conf)
+
+* Hability to run regular commands, not through a chroot wrapper. I can `cd` to
+ a dir and use `makepkg -whatever` on it and nothing breaks.
+
+* No extra code spent on wrappers.
diff --git a/src/xbs-abs/.gitignore b/src/xbs-abs/.gitignore
new file mode 100644
index 0000000..d472431
--- /dev/null
+++ b/src/xbs-abs/.gitignore
@@ -0,0 +1,3 @@
+commitpkg*
+archrelease*
+!*.patch
diff --git a/src/xbs-abs/Makefile b/src/xbs-abs/Makefile
new file mode 100644
index 0000000..248848f
--- /dev/null
+++ b/src/xbs-abs/Makefile
@@ -0,0 +1,27 @@
+include $(dir $(lastword $(MAKEFILE_LIST)))/../../config.mk
+include $(topsrcdir)/automake.head.mk
+pkgconfdir = $(sysconfdir)/xbs
+pkglibexecdir = $(libexecdir)/xbs
+
+_helpers = archrelease commitpkg
+libretools-bins =
+libretools-libexecs = helper-abs
+pots += $(_helpers)
+devtools-files += $(addsuffix .in,$(_helpers))
+am_out_files += $(_helpers)
+am_sys_files += $(addprefix $(pkglibexecdir)/helper-abs.d/,$(_helpers))
+
+$(outdir)/commitpkg: $(srcdir)/commitpkg.in
+ @echo "OUT $@"
+ @{ \
+ echo '#!/usr/bin/env bash'; \
+ echo '. "$$(librelib common)"'; \
+ echo '. ./PKGBUILD'; \
+ echo 'repo=$$1; arch=$$2;'; \
+ sed -n "/== 'any'/,\$$p" $<; \
+ } | install -Dm755 /dev/stdin $@
+
+$(DESTDIR)$(pkglibexecdir)/helper-abs.d/%: $(srcdir)/%
+ install -Dm755 '$<' '$@'
+
+include $(topsrcdir)/automake.tail.mk
diff --git a/src/xbs-abs/archrelease.patch b/src/xbs-abs/archrelease.patch
new file mode 100644
index 0000000..bdf2deb
--- /dev/null
+++ b/src/xbs-abs/archrelease.patch
@@ -0,0 +1,8 @@
+--- archrelease.in 2014-06-20 09:42:13.367553434 -0400
++++ archrelease.ugly 2014-06-27 17:42:14.392917837 -0400
+@@ -1,4 +1,5 @@
+ #!/bin/bash
++# License: Unspecified
+
+ m4_include(lib/common.sh)
+ m4_include(lib/valid-tags.sh)
diff --git a/src/xbs-abs/helper-abs b/src/xbs-abs/helper-abs
new file mode 100755
index 0000000..d7e2499
--- /dev/null
+++ b/src/xbs-abs/helper-abs
@@ -0,0 +1,201 @@
+#!/usr/bin/env bash
+
+# Copyright (C) 2013-2014 Luke Shumaker <lukeshu@sbcglobal.net>
+#
+# For code from db-functions (arch_svn):
+# Copyright (C) 2012 Pierre Schmitz <pierre@archlinux.de>
+# For code from db-move+db-remove (move+unrelease):
+# Copyright (C) 2008-2009 Aaron Griffin <aaronmgriffin@gmail.com>
+# Copyright (C) 2009 Abhishek Dasgupta <abhidg@gmail.com>
+# Copyright (C) 2009, 2011 Dan McGee <dan@archlinux.org>
+# Copyright (C) 2009-2012 Pierre Schmitz <pierre@archlinux.de>
+# For code just from db-move (move):
+# Copyright (C) 2011 Rémy Oudompheng <remyoudompheng@gmail.com>
+# Copyright (C) 2012 Florian Pritz <bluewind@xinu.at>
+# For code just from db-remove (unrelease):
+# Copyright (C) 2009 Eric Bélanger <snowmaniscool@gmail.com>
+# Copyright (C) 2011 Florian Pritz <bluewind@xinu.at>
+#
+# License: GNU GPLv2+
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+load_config() {
+ . "$(librelib conf.sh)"
+ load_files xbs-abs
+ # SVNUSER is optional
+ check_vars xbs-abs SVNDIR SVNREPOS ARCHES || exit 1
+}
+
+# This is taken from dbscripts:db-fuctions
+arch_svn() {
+ if [[ -z "${SVNUSER}" ]]; then
+ /usr/bin/svn "${@}"
+ else
+ sudo -u "${SVNUSER}" -- /usr/bin/svn --username "${USER}" "${@}"
+ fi
+}
+
+pac2svn() {
+ local pacrepo=$1
+
+ # Figure out which svn repo we need
+ local svnrepoStr
+ for svnrepoStr in "${SVNREPOS[@]}"; do
+ local svnrepoAry=($svnrepoStr)
+ local svnrepo=${svnrepoAry[0]}
+ local svnurl=${svnrepoAry[1]}
+ local pacrepos=("${svnrepoAry[@]:2}")
+
+ if in_array "$pacrepo" "${pacrepos[@]}"; then
+ echo "$svnrepo"
+ return 0
+ fi
+ done
+ return 1
+}
+
+status() {
+ load_config
+ [[ -z $(arch_svn status -q) ]]
+}
+
+download() {
+ load_config
+
+ local svnrepoStr
+ for svnrepoStr in "${SVNREPOS[@]}"; do
+ local svnrepoAry=($svnrepoStr)
+ local svnrepo=${svnrepoAry[0]}
+ local svnurl=${svnrepoAry[1]}
+ local pacrepos=("${svnrepoAry[@]:2}")
+
+ if [[ -d "$SVNDIR/$svnrepo/.svn" ]]; then
+ arch_svn -q up "$SVNDIR/$svnrepo"/*
+ else
+ # checkout non-recursive, then lazy load
+ # necessary because:
+ # > DO NOT CHECK OUT THE ENTIRE SVN REPO. Your address
+ # > may be blocked.
+ arch_svn -q checkout -N "$svnurl" "$SVNDIR/$svnrepo"
+ fi
+ done
+}
+
+release-client() {
+ local repo=$1
+ local arch=$2
+
+ # Hack to use arch_svn as 'svn' in external scripts
+ local tmpdir="$(mktemp -dt "xbs-abs-release.XXXXXXXXXX")"
+ trap "$(printf 'rm -rf -- %q' "$tmpdir")" EXIT
+ printf '%s\n' \
+ '#!/bin/bash' \
+ "$(declare -p SVNUSER 2>/dev/null)" \
+ "$(declare -f arch_svn)" \
+ 'arch_svn "$@"' \
+ > "$tmpdir/svn"
+ chmod 755 "$tmpdir/svn"
+ export PATH="$tmpdir:$PATH"
+
+ "${0}.d/archrelease" -f "${repo}-${arch}"
+ "${0}.d/commitpkg" "${repo}" "${arch}"
+}
+
+release-server() {
+ # Do nothing
+ :
+}
+
+unrelease() {
+ local pkgbase=$1
+ local repo=$2
+ local arch=$3
+
+ local tag="$repo-$arch"
+
+ load_config
+ local svndir="${SVNDIR}/$(pac2svn "$repo")/${pkgbase}"
+ arch_svn up -q "$svndir"
+
+ # This is based off code from dbscripts:db-remove
+ arch_svn rm --force -q "${svndir}/repos/${tag}"
+ arch_svn commit -q "${svndir}" -m "${0##*/}: $pkgbase removed by $(id -un)"
+}
+
+move() {
+ local repo_from=$1
+ local repo_to=$2
+ local pkgbase=$3
+
+ load_config
+ local svndir="${SVNDIR}/$(pac2svn "$repo")/${pkgbase}"
+ arch_svn up -q "$svndir"
+
+ local tag_list=""
+ local pkgarch
+ local arches=()
+ # this is based off code from dbscripts:db-move
+ for pkgarch in "${ARCHES[@]}" 'any'; do
+ local dir_from="${svndir}/repos/${repo_from}-${pkgarch}"
+ local dir_to="${svndir}/repos/${repo_to}-${pkgarch}"
+
+ if [ -f "${dir_from}/PKGBUILD" ]; then
+ if [ -d "${dir_to}" ]; then
+ for file in $(arch_svn ls "${dir_to}"); do
+ arch_svn rm -q "${dir_to}/$file@"
+ done
+ else
+ mkdir "${dir_to}"
+ arch_svn add -q "${dir_to}"
+ fi
+
+ local file
+ for file in $(arch_svn ls "${dir_from}"); do
+ arch_svn mv -q -r HEAD "${dir_from}/$file@" "${dir_to}/"
+ done
+ arch_svn rm --force -q "${dir_from}"
+ tag_list="$tag_list, $pkgarch"
+ arches+=("$pkgarch")
+ fi
+ done
+ tag_list="${tag_list#, }"
+ arch_svn commit -q "${svndir}" -m "${0##*/}: moved ${pkgbase} from [${repo_from}] to [${repo_to}] (${tag_list})"
+ echo "${arches[*]}"
+}
+
+releasepath() {
+ local pkgbase=$1
+ local repo=$2
+ local arch=$3
+
+ load_config
+ local svndir="${SVNDIR}/$(pac2svn "$repo")/${pkgbase}"
+ arch_svn up -q "${svndir}"
+ local releasepath="${svndir}/repos/${repo}-${arch}"
+ if [[ -f "${releasepath}/PKGBUILD" ]]; then
+ printf '%s\n' "$releasepath"
+ return 0
+ fi
+ return 1
+}
+
+name() {
+ echo SVN
+}
+
+case "$1" in
+ status|download|release-client|release-server|unrelease|move|releasepath|name) "$@";;
+ *) exit 127;;
+esac
diff --git a/src/xbs-abs/xbs-abs.conf b/src/xbs-abs/xbs-abs.conf
new file mode 100644
index 0000000..6b1585f
--- /dev/null
+++ b/src/xbs-abs/xbs-abs.conf
@@ -0,0 +1,13 @@
+SVNDIR=/var/lib/xbs-abs
+#SVNUSER=
+
+_packages_repos=(core extra testing staging {gnome,kde}-unstable)
+_community_repos=({community,multilib}{,-testing,-staging})
+
+# name url repos...
+SVNREPOS=(
+ "packages svn://svn.archlinux.org/packages ${_packages_repos[*]}"
+ "community svn://svn.archlinux.org/community ${_community_repos[*]}"
+)
+
+ARCHES=(i686 x86_64)
diff --git a/src/xbs-abslibre/Makefile b/src/xbs-abslibre/Makefile
new file mode 100644
index 0000000..c946d5d
--- /dev/null
+++ b/src/xbs-abslibre/Makefile
@@ -0,0 +1,8 @@
+include $(dir $(lastword $(MAKEFILE_LIST)))/../../config.mk
+include $(topsrcdir)/automake.head.mk
+pkglibexecdir = $(libexecdir)/xbs
+
+libretools-bins =
+libretools-libexecs = helper-abslibre
+
+include $(topsrcdir)/automake.tail.mk
diff --git a/src/xbs-abslibre/helper-abslibre b/src/xbs-abslibre/helper-abslibre
new file mode 100755
index 0000000..91e7361
--- /dev/null
+++ b/src/xbs-abslibre/helper-abslibre
@@ -0,0 +1,202 @@
+#!/usr/bin/env bash
+
+# Copyright (C) 2012-2016 Luke Shumaker <lukeshu@sbcglobal.net>
+#
+# License: GNU GPLv2+
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+. libremessages
+setup_traps
+set -u
+
+lockarch() {
+ local arch=$1
+ lock 9 "${ABSLIBREDEST}/${arch}.lock" \
+ "Waiting for a lock on the ABSLibre release directory for %s" "${arch}"
+}
+
+unlockarch() {
+ lock_close 9
+}
+
+checkgit() {
+ if [[ ! -d "${ABSLIBREDEST}/${arch}/.git" ]]; then
+ error 'Not a git repository: %s' "${ABSLIBREDEST}/${arch}"
+ exit 1
+ fi
+}
+
+conf() {
+ . "$(librelib conf)"
+ load_files libretools
+ check_vars libretools "$@" || exit 1
+}
+
+################################################################################
+
+# Operates on the `abslibre` format[0]. On the client/developer side,
+# release-client *copies* files from the `abslibre` tree to an
+# `abstree`[1] tree located at `devbox:${ABSLIBREDEST}/${arch}`. On
+# the server side, after those `abstree`s have been uploaded
+# (ssh/rsync), release-server *copies* them out of the `abstree` into
+# a different `abslibre` on the server, similarly located at
+# `repobox:${ABSLIBREDEST}/${arch}`.
+#
+# [0]: https://projects.parabola.nu/packages/pbs-tools.git/tree/docs/format-abslibre.md
+# [1]: https://projects.parabola.nu/packages/pbs-tools.git/tree/docs/format-abstree.md
+
+# The number of arguments and CWD constraints are enforced by the main
+# XBS program, no need to check those things here!
+
+# Args: none
+# CWD: a directory with a PKGBUILD, in an 'abslibre' tree.
+status() {
+ [[ -z $(git status -s .) ]]
+}
+
+# Args: none
+# CWD: anywhere
+# Host: developer box
+download() {
+ conf WORKDIR ABSLIBRERECV ABSLIBRESEND
+
+ gitget -f -p "$ABSLIBRESEND" checkout "$ABSLIBRERECV" "$WORKDIR/abslibre"
+}
+
+# Args: REPO ARCH
+# CWD: a directory with a PKGBUILD, in an 'abslibre' tree.
+# Host: developer box
+release-client() {
+ local repo=$1
+ local arch=$2
+
+ conf ABSLIBREDEST
+ local pkgbase="$(load_PKGBUILD >/dev/null; printf '%s\n' "${pkgbase:-${pkgname}}")"
+ local pkgdir="${ABSLIBREDEST}/${arch}/${repo}/${pkgbase}"
+ lockarch "$arch"
+
+ if [[ -e $pkgdir ]]; then
+ rm -rf -- "$pkgdir"
+ fi
+
+ mkdir -p -- "$pkgdir"
+ git ls-files -z | xargs -0 -I{} cp -- {} "$pkgdir"
+}
+
+# Args: REPO ARCH
+# CWD: a directory with a PKGBUILD, outside of ${ABSLIBREDEST}
+# Host: repo
+release-server() {
+ local repo=$1
+ local arch=$2
+
+ conf ABSLIBREDEST
+ local pkgbase="$(load_PKGBUILD >/dev/null; printf '%s\n' "${pkgbase:-${pkgname}}")"
+ local pkgdir="${ABSLIBREDEST}/${arch}/${repo}/${pkgbase}"
+ lockarch "$arch"
+ checkgit
+
+ if [[ -e $pkgdir ]]; then
+ rm -rf -- "$pkgdir"
+ fi
+
+ mkdir -p -- "$pkgdir"
+ cp -- * "$pkgdir"
+
+ cd "$pkgdir"
+ git add .
+ git commit -q -m "xbs-abslibre: Release ${repo}/${pkgbase} for ${arch} (by $(id -un))"
+}
+
+# Args: PKGBASE REPO ARCH
+# CWD: anywhere
+# Host: repo
+unrelease() {
+ local pkgbase=$1
+ local repo=$2
+ local arch=$3
+
+ conf ABSLIBREDEST
+ local pkgdir="${ABSLIBREDEST}/${arch}/${repo}/${pkgbase}"
+ lockarch "$arch"
+ checkgit
+
+ if [[ -f "${pkgdir}/PKGBUILD" ]]; then
+ git rm -qrf -- "$pkgdir"
+ git commit -q -m "xbs-abslibre: Remove ${repo}/${pkgbase} from ${arch} (by $(id -un))"
+ fi
+}
+
+# Args: FROMREPO TOREPO PKGBASE
+# CWD: anywhere
+# Host: repo
+move() {
+ local repo_from=$1
+ local repo_to=$2
+ local pkgbase=$3
+
+ conf ABSLIBREDEST ARCHES
+
+ # Execute each iteration in a subshell so that 'checkgit'
+ # bailing for an architecture doesn't abort the entire thing.
+ local arch
+ for arch in "${ARCHES[@]}" any; do (
+ lockarch "$arch"
+ checkgit
+
+ local dir_from="${ABSLIBREDEST}/${arch}/${repo_from}/${pkgbase}"
+ local dir_to="${ABSLIBREDEST}/${arch}/${repo_to}/${pkgbase}"
+
+ if [[ -f "${dir_from}/PKGBUILD" ]]; then
+ if [[ -e "${dir_to}" ]]; then
+ git rm -qrf -- "$dir_to"
+ fi
+ mkdir -p -- "${dir_to%/*}"
+ git mv -- "$dir_from" "$dir_to"
+ git commit -q -m "xbs-abslibre: Move ${pkgbase} from ${repo_from} to ${repo_to} on ${arch} (by $(id -un))"
+ fi
+ unlock arch
+ ) done
+}
+
+# Args: PKGBASE REPO ARCH
+# CWD: anywhere
+# Host: repo
+releasepath() {
+ local pkgbase=$1
+ local repo=$2
+ local arch=$3
+
+ conf ABSLIBREDEST
+ local pkgdir="${ABSLIBREDEST}/${arch}/${repo}/${pkgbase}"
+
+ lockarch "$arch"
+
+ if [[ -f "${pkgdir}/PKGBUILD" ]]; then
+ printf '%s\n' "$pkgdir"
+ return 0
+ fi
+
+ return 1
+}
+
+name() {
+ echo ABSLibre
+}
+
+case "$1" in
+ status|download|release-client|release-server|unrelease|move|releasepath|name) "$@" || exit $?;;
+ *) exit 127;;
+esac
diff --git a/src/xbs/Makefile b/src/xbs/Makefile
new file mode 100644
index 0000000..974586e
--- /dev/null
+++ b/src/xbs/Makefile
@@ -0,0 +1,5 @@
+include $(dir $(lastword $(MAKEFILE_LIST)))/../../config.mk
+include $(topsrcdir)/automake.head.mk
+pkgconfdir = $(sysconfdir)/xbs
+
+include $(topsrcdir)/automake.tail.mk
diff --git a/src/xbs/xbs b/src/xbs/xbs
new file mode 100755
index 0000000..6319110
--- /dev/null
+++ b/src/xbs/xbs
@@ -0,0 +1,186 @@
+#!/usr/bin/env bash
+
+# Copyright (C) 2013-2015 Luke Shumaker <lukeshu@sbcglobal.net>
+#
+# License: GNU GPLv2+
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+default_libdir=/usr/lib/xbs
+
+. libremessages
+. "$(librelib conf)"
+
+errusage() {
+ if [[ $# -gt 0 ]]; then
+ error "$@"
+ fi
+ usage >&2
+ exit 1
+}
+
+usage() {
+ print 'Usage: %s [-b BUILDSYSTEM|-h] COMMAND [ARGUMENTS]' "${0##*/}"
+ print 'Tool for working with arbitrary ABS-like build systems'
+ echo
+ prose 'This is a pluggable tool. The BUILDSYSTEM it uses is
+ configured in:'
+ bullet '/etc/xbs.conf'
+ bullet '${XDG_CONFIG_HOME}/xbs.conf'
+ bullet 'with the `-b` flag'
+ prose 'Later items take precedence over earlier ones.'
+ echo
+ prose 'It looks for a helper programs named helper-${BUILDSYSTEM}, in
+ the directory `%q` by default, but this directory can be changed
+ with the environmental variable XBS_LIBDIR.' "$default_libdir"
+ echo
+ print 'Options:'
+ flag "-b $(_ BUILDSYSTEM)" 'Use BUILDSYSTEM instead of the one
+ configured in xbs.conf' \
+ '-h' 'Show this message'
+ echo
+ prose "Whether a command is intended for use on a developer's box or on
+ the server is indicated by (dev), (srv), or (any) before the
+ command"
+ echo
+ print 'Commands:'
+ flag "$(_ '(dev) status')" \
+ 'Are there uncommitted changes in `.`?' \
+ "$(_ '(dev) download')" \
+ 'Download or update the tree' \
+ "$(_ '(dev) release-client REPO ARCH')" \
+ 'Release `.` (for developer boxes)' \
+ "$(_ '(srv) release-server REPO ARCH')" \
+ 'Release `.` (for server boxes)' \
+ "$(_ '(srv) unrelease PKGBASE REPO ARCH')" \
+ 'Unrelease a pkgbase' \
+ "$(_ '(srv) move FROMREPO TOREPO PKGBASE')" \
+ 'Move a pkgbase from one repo to another' \
+ "$(_ '(srv) releasepath PKGBASE REPO ARCH')" \
+ 'Print the path to the staged version of pkgbase, or exit with
+ non-zero if not released' \
+ "$(_ '(any) name')" \
+ 'Print a human-friendly version of the BUILDSYSTEM name' \
+ "$(_ '(any) help')" \
+ 'Show this message'
+}
+
+status() {
+ if [[ ! -f PKGBUILD ]]; then
+ error 'PKGBUILD not found'
+ # Though in this file in general it doesn't matter, in
+ # this case it does: Using "exit" instead of "return"
+ # is imporant because it prevents flow returning to
+ # release-client.
+ exit 1
+ fi
+ "$HELPER" status "$@"
+}
+
+download() {
+ "$HELPER" download "$@"
+}
+
+release-client() {
+ if ! status; then
+ error 'You have not committed your changes yet!'
+ exit 1
+ fi
+ "$HELPER" release-client "$@"
+}
+
+release-server() {
+ if [[ ! -f PKGBUILD ]]; then
+ error 'PKGBUILD not found'
+ exit 1
+ fi
+ "$HELPER" release-server "$@"
+}
+
+unrelease() {
+ "$HELPER" unrelease "$@"
+}
+
+move() {
+ "$HELPER" move "$@"
+}
+
+releasepath() {
+ "$HELPER" releasepath "$@"
+}
+
+help() {
+ usage
+}
+
+name() {
+ "$HELPER" name "$@"
+}
+
+main() {
+ BUILDSYSTEM=''
+ while getopts 'b:h' arg; do
+ case $arg in
+ b) BUILDSYSTEM=$OPTARG;;
+ h) usage; return 0;;
+ *) errusage;;
+ esac
+ done
+ shift $(($OPTIND - 1))
+
+ if [[ -z $BUILDSYSTEM ]]; then
+ load_files xbs || return 1
+ check_vars xbs BUILDSYSTEM || {
+ prose 'or specify the `-b` flag.' >&2
+ return 1
+ }
+ fi
+
+ if [[ -z $XBS_LIBDIR ]]; then
+ export XBS_LIBDIR=$default_libdir
+ fi
+
+ HELPER="${XBS_LIBDIR}/helper-${BUILDSYSTEM}"
+ if [[ ! -x "$HELPER" ]]; then
+ error 'No helper for build system found: %s' "$BUILDSYSTEM"
+ return 1;
+ fi
+
+ if [[ $# -lt 1 ]]; then
+ errusage "Must specify a command"
+ fi
+
+ if [[ -w / ]]; then
+ error 'Run as a normal user'
+ fi
+
+ local cmd=$1; shift
+ case "$cmd" in
+ status|download|name|help)
+ [[ $# -eq 0 ]] || errusage 'bad number of argments'
+ "$cmd" "$@"
+ ;;
+ release-client|release-server)
+ [[ $# -eq 2 ]] || errusage 'bad number of argments'
+ "$cmd" "$@"
+ ;;
+ move|unrelease|releasepath)
+ [[ $# -eq 3 ]] || errusage 'bad number of argments'
+ "$cmd" "$@"
+ ;;
+ *) errusage 'unknown command: %s' "$cmd";;
+ esac
+}
+
+main "$@"
diff --git a/src/xbs/xbs.conf b/src/xbs/xbs.conf
new file mode 100644
index 0000000..556c133
--- /dev/null
+++ b/src/xbs/xbs.conf
@@ -0,0 +1 @@
+BUILDSYSTEM=abslibre
diff --git a/test/.gitignore b/test/.gitignore
new file mode 100644
index 0000000..2226ac6
--- /dev/null
+++ b/test/.gitignore
@@ -0,0 +1 @@
+.roundup.*
diff --git a/test/aur-test.sh b/test/aur-test.sh
new file mode 100644
index 0000000..5de590b
--- /dev/null
+++ b/test/aur-test.sh
@@ -0,0 +1,30 @@
+#!/usr/bin/env roundup
+
+describe aur
+
+. ./test-common.sh
+
+before() {
+ _before
+}
+
+after() {
+ _after
+}
+
+it_displays_help() {
+ LC_ALL=C aur -h >$tmpdir/stdout 2>$tmpdir/stderr
+
+ [[ "$(sed 1q $tmpdir/stdout)" =~ Usage:.* ]]
+ empty $tmpdir/stderr
+}
+
+it_fails_with_0_args() {
+ aur >$tmpdir/stdout 2>$tmpdir/stderr || stat=$?
+
+ [[ $stat != 0 ]]
+ empty $tmpdir/stdout
+ not empty $tmpdir/stderr
+}
+
+# TODO: Actually test
diff --git a/test/is_built-test.sh b/test/is_built-test.sh
new file mode 100644
index 0000000..f7f6c65
--- /dev/null
+++ b/test/is_built-test.sh
@@ -0,0 +1,60 @@
+#!/usr/bin/env roundup
+
+describe is_built
+
+. ./test-common.sh
+
+before() {
+ _before
+}
+
+after() {
+ _after
+}
+
+it_displays_help() {
+ LC_ALL=C is_built -h >$tmpdir/stdout 2>$tmpdir/stderr
+
+ [[ "$(sed 1q $tmpdir/stdout)" =~ Usage:.* ]]
+ empty $tmpdir/stderr
+}
+
+it_fails_with_0_args() {
+ is_built >$tmpdir/stdout 2>$tmpdir/stderr || stat=$?
+
+ [[ $stat -gt 1 ]]
+ empty $tmpdir/stdout
+ not empty $tmpdir/stderr
+}
+
+it_succeeds_with_1_arg() {
+ is_built sh >$tmpdir/stdout 2>$tmpdir/stderr
+
+ empty $tmpdir/stdout
+ empty $tmpdir/stderr
+}
+
+it_returns_1_for_non_existent_package() {
+ is_built phony-ne-package 100 >$tmpdir/stdout 2>$tmpdir/stderr || stat=$?
+
+ [[ $stat == 1 ]]
+ empty $tmpdir/stdout
+ empty $tmpdir/stderr
+}
+
+it_returns_1_for_future_packages() {
+ # If emacs ever goes rapid release, we might need to change this :P
+ is_built emacs 100 >$tmpdir/stdout 2>$tmpdir/stderr || stat=$?
+
+ [[ $stat == 1 ]]
+ empty $tmpdir/stdout
+ empty $tmpdir/stderr
+}
+
+it_returns_0_for_past_packages() {
+ # If emacs ever goes rapid release, we might need to change this :P
+ is_built emacs 1 >$tmpdir/stdout 2>$tmpdir/stderr
+
+ empty $tmpdir/stdout
+ empty $tmpdir/stderr
+}
diff --git a/test/lib-blacklist-test.sh b/test/lib-blacklist-test.sh
new file mode 100644
index 0000000..7b06f84
--- /dev/null
+++ b/test/lib-blacklist-test.sh
@@ -0,0 +1,134 @@
+#!/usr/bin/env roundup
+
+describe libreblacklist
+
+. ./test-common.sh
+
+_blacklist_url=https://projects.parabola.nu/blacklist.git/plain/blacklist.txt
+
+before() {
+ _before
+}
+
+after() {
+ _after
+}
+
+it_works_with_just_pkgname() {
+ v="$(libreblacklist normalize <<<skype)"; [[ $v == 'skype::' ]]
+ v="$(libreblacklist get-pkg <<<skype)"; [[ $v == skype ]]
+ v="$(libreblacklist get-rep <<<skype)"; [[ -z $v ]]
+ v="$(libreblacklist get-reason <<<skype)"; [[ -z $v ]]
+}
+
+it_works_with_everything_set() {
+ line='linux:linux-libre:nonfree blobs and firmwares'
+ v="$(libreblacklist normalize <<<"$line")"; [[ $v == "$line" ]]
+ v="$(libreblacklist get-pkg <<<"$line")"; [[ $v == 'linux' ]]
+ v="$(libreblacklist get-rep <<<"$line")"; [[ $v == 'linux-libre' ]]
+ v="$(libreblacklist get-reason <<<"$line")"; [[ $v == 'nonfree blobs and firmwares' ]]
+}
+
+it_normalizes_correctly() {
+ v="$(libreblacklist normalize <<<pkg)"; [[ $v == 'pkg::' ]]
+ v="$(libreblacklist normalize <<<pkg:)"; [[ $v == 'pkg::' ]]
+ v="$(libreblacklist normalize <<<pkg::)"; [[ $v == 'pkg::' ]]
+ v="$(libreblacklist normalize <<<pkg:rep)"; [[ $v == 'pkg:rep:' ]]
+ v="$(libreblacklist normalize <<<pkg:rep:)"; [[ $v == 'pkg:rep:' ]]
+ v="$(libreblacklist normalize <<<pkg:rep:reason)"; [[ $v == 'pkg:rep:reason' ]]
+ v="$(libreblacklist normalize <<<pkg:rep:reason:)"; [[ $v == 'pkg:rep:reason:' ]]
+}
+
+it_works_with_colons_in_reason() {
+ line='package:replacement:my:reason'
+ v="$(libreblacklist normalize <<<"$line")"; [[ $v == "$line" ]]
+ v="$(libreblacklist get-pkg <<<"$line")"; [[ $v == 'package' ]]
+ v="$(libreblacklist get-rep <<<"$line")"; [[ $v == 'replacement' ]]
+ v="$(libreblacklist get-reason <<<"$line")"; [[ $v == 'my:reason' ]]
+}
+
+it_fails_update_with_no_blacklist_or_network() {
+ mkdir -p $XDG_CONFIG_HOME/libretools
+ echo "BLACKLIST='phony://example.com'" >$XDG_CONFIG_HOME/libretools/libretools.conf
+
+ libreblacklist update >$tmpdir/stdout 2>$tmpdir/stderr || stat=$?
+
+ [[ $stat != 0 ]]
+ empty $tmpdir/stdout
+ not empty $tmpdir/stderr
+}
+
+it_fails_cat_with_no_blacklist_or_network() {
+ mkdir -p $XDG_CONFIG_HOME/libretools
+ echo "BLACKLIST='phony://example.com'" >$XDG_CONFIG_HOME/libretools/libretools.conf
+
+ libreblacklist cat >$tmpdir/stdout 2>$tmpdir/stderr || stat=$?
+
+ [[ $stat != 0 ]]
+ empty $tmpdir/stdout
+ not empty $tmpdir/stderr
+}
+
+it_fails_update_when_BLACKLIST_is_unset() {
+ mkdir -p $XDG_CONFIG_HOME/libretools
+ echo "BLACKLIST=" >$XDG_CONFIG_HOME/libretools/libretools.conf
+
+ libreblacklist update >$tmpdir/stdout 2>$tmpdir/stderr || stat=$?
+
+ [[ $stat != 0 ]]
+ empty $tmpdir/stdout
+ not empty $tmpdir/stderr
+}
+
+it_fails_cat_when_syntax_error_in_conf() {
+ mkdir -p $XDG_CONFIG_HOME/libretools
+ # there is a stray single quote in there
+ printf "BLACKLIST='%q\n" "${_blacklist_url}" >$XDG_CONFIG_HOME/libretools/libretools.conf
+
+ libreblacklist cat >$tmpdir/stdout 2>$tmpdir/stderr || stat=$?
+
+ [[ $stat != 0 ]]
+ empty $tmpdir/stdout
+ not empty $tmpdir/stderr
+}
+
+it_downloads_the_blacklist_as_needed() {
+ require network || return 0
+ mkdir -p $XDG_CONFIG_HOME/libretools
+ printf 'BLACKLIST=%q\n' "${_blacklist_url}" >$XDG_CONFIG_HOME/libretools/libretools.conf
+
+ libreblacklist cat >$tmpdir/stdout 2>$tmpdir/stderr
+
+ not empty $tmpdir/stdout
+}
+
+it_downloads_the_blacklist_repeatedly() {
+ require network || return 0
+ mkdir -p $XDG_CONFIG_HOME/libretools
+ printf 'BLACKLIST=%q\n' "${_blacklist_url}" >$XDG_CONFIG_HOME/libretools/libretools.conf
+
+ libreblacklist update
+ libreblacklist update
+}
+
+it_displays_help_and_fails_with_no_args() {
+ LC_ALL=C libreblacklist >$tmpdir/stdout 2>$tmpdir/stderr || stat=$?
+
+ [[ $stat != 0 ]]
+ empty $tmpdir/stdout
+ [[ "$(sed 1q $tmpdir/stderr)" =~ 'Usage: libreblacklist ' ]]
+}
+
+it_displays_help_when_given_h() {
+ LC_ALL=C libreblacklist -h >$tmpdir/stdout 2>$tmpdir/stderr
+
+ [[ "$(sed 1q $tmpdir/stdout)" =~ 'Usage: libreblacklist ' ]]
+ empty $tmpdir/stderr
+}
+
+it_displays_help_when_given_h_cat() {
+ LC_ALL=C libreblacklist -h cat >$tmpdir/stdout 2>$tmpdir/stderr
+
+ [[ "$(sed 1q $tmpdir/stdout)" == 'Usage: libreblacklist cat' ]]
+ empty $tmpdir/stderr
+}
diff --git a/test/lib-conf-test.sh b/test/lib-conf-test.sh
new file mode 100644
index 0000000..efad907
--- /dev/null
+++ b/test/lib-conf-test.sh
@@ -0,0 +1,43 @@
+#!/usr/bin/env roundup
+
+describe lib/conf.sh
+
+. ./test-common.sh
+
+before() {
+ _before test-conf.sh
+}
+
+after() {
+ _after
+}
+
+it_sets_makepkg_vars_in_custom_file() {
+ unset PKGDEST
+ touch "$tmpdir/makepkg.conf"
+ . $(librelib conf.sh)
+ MAKEPKG_CONF="$tmpdir/makepkg.conf" set_var makepkg PKGDEST /pkgdest
+ . "$tmpdir/makepkg.conf"
+ [[ $PKGDEST == /pkgdest ]]
+}
+
+it_figures_out_HOME_when_root() {
+ require sudo || return 0
+ # This one is tricky, because it does the job too well, it will find
+ # the actual HOME, instead of the test environment HOME. Therefore, we
+ # will just check that [[ $HOME != /root ]]
+ cd "$tmpdir"
+ echo '. $(librelib conf.sh); echo "$LIBREHOME"' > test.sh
+ LIBREHOME=$(testsudo bash ./test.sh)
+ [[ $LIBREHOME != /root ]]
+}
+
+it_respects_custom_HOME() {
+ cd "$tmpdir"
+ echo '. $(librelib conf.sh); echo "$LIBREHOME"' > test.sh
+
+ export HOME=/foo
+ LIBREHOME=$(bash ./test.sh)
+
+ [[ $LIBREHOME == /foo ]]
+}
diff --git a/test/lib-messages-test.sh b/test/lib-messages-test.sh
new file mode 100644
index 0000000..d895d99
--- /dev/null
+++ b/test/lib-messages-test.sh
@@ -0,0 +1,64 @@
+#!/usr/bin/env roundup
+
+describe libremessages
+
+. ./test-common.sh
+
+before() {
+ _before
+}
+
+after() {
+ _after
+}
+
+it_can_be_included_twice() (
+ . libremessages
+ . libremessages
+)
+
+it_can_be_included_with_set_euE() (
+ set -euE
+ . libremessages
+)
+
+it_works_with_no_color_and_set_euE() (
+ (
+ unset TERM
+ set -euE
+ . libremessages
+ msg Foo
+ ) >$tmpdir/stdout 2>$tmpdir/stderr
+
+ empty $tmpdir/stdout
+ not empty $tmpdir/stderr
+)
+
+it_can_be_called_without_including() {
+ libremessages msg Foo >$tmpdir/stdout 2>$tmpdir/stderr
+
+ empty $tmpdir/stdout
+ not empty $tmpdir/stderr
+}
+
+it_fails_with_msg_and_no_args() {
+ ret=0
+ libremessages msg || ret=$?
+ [[ $ret != 0 ]]
+}
+
+it_allows_subheadings_to_flag() {
+ # Note that old versions of `flag` panicked if given an odd
+ # number of headings, so give an odd number here.
+ libremessages flag \
+ -a adesc \
+ -b bdesc \
+ Head1: \
+ -c cdesc > $tmpdir/out
+ printf '%s\n' \
+ ' -a adesc' \
+ ' -b bdesc' \
+ ' Head1:' \
+ ' -c cdesc' > $tmpdir/exp
+ diff -u $tmpdir/exp $tmpdir/out
+}
diff --git a/test/librechroot-test.sh b/test/librechroot-test.sh
new file mode 100644
index 0000000..667246c
--- /dev/null
+++ b/test/librechroot-test.sh
@@ -0,0 +1,129 @@
+#!/usr/bin/env roundup
+
+describe librechroot
+
+. ./test-common.sh
+
+_setup_chrootdir
+
+before() {
+ _before librechroot
+
+ mkdir -p "$XDG_CONFIG_HOME"/libretools
+
+ echo "CHROOTDIR='${chrootdir}'" > "$XDG_CONFIG_HOME"/libretools/chroot.conf
+ echo "CHROOT='default'" >> "$XDG_CONFIG_HOME"/libretools/chroot.conf
+ echo "CHROOTEXTRAPKG=()" >> "$XDG_CONFIG_HOME"/libretools/chroot.conf
+}
+
+after() (
+ _after_sudo
+)
+
+it_creates_repo_for_new_chroots() {
+ require network sudo || return 0
+ libremessages msg 'Creating a chroot, may take a few minutes' &>/dev/tty
+ testsudo librechroot -l "$roundup_test_name" run test -r /repo/repo.db
+}
+
+it_cleans_the_local_repo_correctly() {
+ require network sudo || return 0
+ libremessages msg 'Creating a chroot, may take a few minutes' &>/dev/tty
+ testsudo librechroot -l "$roundup_test_name" make
+ testsudo librechroot -l "$roundup_test_name" clean-repo
+ testsudo librechroot -l "$roundup_test_name" run test -r /repo/repo.db
+ # TODO: inspect /repo/* more
+}
+
+it_respects_exit_status_if_out_isnt_a_tty() (
+ require network sudo || return 0
+ set -o pipefail
+ libremessages msg 'Creating a chroot, may take a few minutes' &>/dev/tty
+ r=0
+ { testsudo librechroot -l "$roundup_test_name" run bash -c 'exit 3' | cat; } || r=$?
+
+ [[ $r == 3 ]]
+)
+
+it_creates_ca_certificates() {
+ require network sudo || return 0
+ libremessages msg 'Creating a chroot, may take a few minutes' &>/dev/tty
+ testsudo librechroot -l "$roundup_test_name" run test -r /etc/ssl/certs/ca-certificates.crt
+}
+
+it_disables_networking_when_requested() {
+ require network sudo || return 0
+ libremessages msg 'Creating a chroot, may take a few minutes' &>/dev/tty
+
+ testsudo librechroot -l "$roundup_test_name" run curl https://repo.parabola.nu/ >/dev/null
+ not testsudo librechroot -l "$roundup_test_name" -N run curl https://repo.parabola.nu/ >/dev/null
+}
+
+it_handles_CHROOTEXTRAPKG_correctly() {
+ requuire network sudo || return 0
+ libremessages msg 'Creating a chroot, may take a few minutes' &>/dev/tty
+
+ not testsudo librechroot -l "$roundup_test_name" run lsof
+ echo "CHROOTEXTRAPKG=(lsof)" >> "$XDG_CONFIG_HOME"/libretools/chroot.conf
+ testsudo librechroot -l "$roundup_test_name" install-name lsof
+ testsudo librechroot -l "$roundup_test_name" clean-pkgs
+ testsudo librechroot -l "$roundup_test_name" run lsof
+ echo "CHROOTEXTRAPKG=()" >> "$XDG_CONFIG_HOME"/libretools/chroot.conf
+ testsudo librechroot -l "$roundup_test_name" clean-pkgs
+ not testsudo librechroot -l "$roundup_test_name" run lsof
+}
+
+it_displays_help_as_normal_user() {
+ rm -rf "$XDG_CONFIG_HOME"
+ LC_ALL=C librechroot help >$tmpdir/stdout 2>$tmpdir/stderr
+
+ [[ "$(sed 1q $tmpdir/stdout)" =~ Usage:.* ]]
+ empty $tmpdir/stderr
+}
+
+it_otherwise_fails_as_normal_user() {
+ librechroot -l "$roundup_test_name" run true >$tmpdir/stdout 2>$tmpdir/stderr || stat=$?
+
+ [[ $stat != 0 ]]
+ empty $tmpdir/stdout
+ not empty $tmpdir/stderr
+}
+
+it_displays_help_and_fails_with_0_args() {
+ LC_ALL=C librechroot -l "$roundup_test_name" >$tmpdir/stdout 2>$tmpdir/stderr || stat=$?
+
+ [[ $stat != 0 ]]
+ empty $tmpdir/stdout
+ [[ "$(sed -n 2p $tmpdir/stderr)" =~ Usage:.* ]]
+}
+
+# requires sudo so we know it's not failing because it needs root
+it_fails_for_unknown_commands() {
+ require sudo || return 0
+ testsudo librechroot phony >$tmpdir/stdout 2>$tmpdir/stderr || stat=$?
+
+ [[ $stat != 0 ]]
+ empty $tmpdir/stdout
+ not empty $tmpdir/stderr
+}
+
+# requires sudo so we know it's not failing because it needs root
+it_fails_for_unknown_flags() {
+ require sudo || return 0
+ testsudo librechroot -q >$tmpdir/stdout 2>$tmpdir/stderr || stat=$?
+
+ [[ $stat != 0 ]]
+ empty $tmpdir/stdout
+ not empty $tmpdir/stderr
+}
+
+it_fails_when_syncing_a_copy_with_itself() {
+ require sudo || return 0
+ testsudo timeout 5 librechroot -l root sync || stat=$?
+ case $stat in
+ 0|124|137) # success|timeout+TERM|timeout+KILL
+ false;;
+ *)
+ true;;
+ esac
+}
diff --git a/test/librefetch-test.sh b/test/librefetch-test.sh
new file mode 100644
index 0000000..f10ee7f
--- /dev/null
+++ b/test/librefetch-test.sh
@@ -0,0 +1,99 @@
+#!/usr/bin/env roundup
+
+describe librefetch
+
+. ./test-common.sh
+
+KEYSERVER=hkp://pool.sks-keyservers.net
+GPG="gpg --quiet --batch --no-tty --no-permission-warning --keyserver ${KEYSERVER}"
+
+before() {
+ _before
+
+ mkdir -p "$XDG_CONFIG_HOME"/{pacman,libretools} "$tmpdir/srcdest"
+
+ cat <<EOF > "$XDG_CONFIG_HOME/pacman/makepkg.conf"
+DLAGENTS=('ftp::/usr/bin/curl -fC - --ftp-pasv --retry 3 --retry-delay 3 -o %o %u'
+ 'http::/usr/bin/curl -fLC - --retry 3 --retry-delay 3 -o %o %u'
+ 'https::/usr/bin/curl -fLC - --retry 3 --retry-delay 3 -o %o %u'
+ 'rsync::/usr/bin/rsync --no-motd -z %u %o'
+ 'scp::/usr/bin/scp -C %u %o')
+BUILDDIR=""
+SRCDEST=$tmpdir/srcdest
+. ${_librelib_conf_sh_pkgconfdir}/librefetch-makepkg.conf
+EOF
+ sed -i 's,/usr/bin/librefetch,$(which librefetch),' \
+ "${_librelib_conf_sh_pkgconfdir}/librefetch-makepkg.conf"
+
+ export MAKEPKG_CONF="$XDG_CONFIG_HOME/pacman/makepkg.conf"
+
+ printf '%s\n' \
+ 'MIRRORS=("phony://example.com/dir/")' \
+ 'DOWNLOADER=/usr/bin/false' \
+ > "$XDG_CONFIG_HOME/libretools/librefetch.conf"
+
+ printf '%s\n' \
+ 'Key-Type: RSA' \
+ 'Key-Length: 1024' \
+ 'Key-Usage: sign' \
+ 'Name-Real: Temporary LibreTools testsuite key' \
+ 'Name-Email: libretools-test@localhost' \
+ 'Expire-Date: 0' \
+ '%no-protection' \
+ '%commit' \
+ | $GPG --gen-key 2>/dev/null
+}
+
+after() {
+ _after
+}
+
+it_displays_help() {
+ LC_ALL=C librefetch -h >$tmpdir/stdout 2>$tmpdir/stderr
+
+ [[ "$(sed 1q $tmpdir/stdout)" =~ Usage:.* ]]
+ empty $tmpdir/stderr
+}
+
+# This test used to be called "it_cleans_src_libre_first", but let's
+# be honest: it checks pretty much everything related to normal
+# operation.
+it_runs() {
+ local srcball=testpkg-1.0.tar.gz
+ cp librefetch.d/* "$tmpdir/"
+ cd "$tmpdir"
+
+ # Create garbage, to verifiy that it cleans src-libre first
+ mkdir -p src-libre/foo
+ touch src-libre/foo/file
+
+ # Run librefetch
+ makepkg -g
+
+ # Verify that no temporary files were left around
+ not test -e librefetch.*
+
+ # Verify:
+ # - The srcball was created...
+ # - ... and is in the correct directory
+ # - The srcball does not contain the garbage created earlier
+ # - The files in the srcball are in the correct order (if the
+ # order isn't ensured, then this would only sometimes fail,
+ # unfortunately).
+ bsdtar tf "$tmpdir/srcdest/$srcball" > list-pkg.txt
+ diff -u list.txt list-pkg.txt
+ # Verify that the signature was created and matches
+ gpg --quiet --verify "$tmpdir/srcdest/$srcball"{.sig,} 2>/dev/null
+}
+
+it_recurses() {
+ local srcball=testpkg-1.0.tar.gz
+ cp librefetch.d/* "$tmpdir/"
+ cd "$tmpdir"
+ mv PKGBUILD{-recurse,}
+
+ makepkg -g
+ bsdtar tf "$tmpdir/srcdest/$srcball" > list-pkg.txt
+ diff -u list.txt list-pkg.txt
+ gpg --quiet --verify "$tmpdir/srcdest/$srcball"{.sig,} 2>/dev/null
+}
diff --git a/test/librefetch.d/PKGBUILD b/test/librefetch.d/PKGBUILD
new file mode 100644
index 0000000..6547e25
--- /dev/null
+++ b/test/librefetch.d/PKGBUILD
@@ -0,0 +1,18 @@
+pkgname=testpkg
+pkgver=1.0
+pkgrel=1
+pkgdesc=foo
+arch=(any)
+source=("libre://$pkgname-$pkgver.tar.gz"{,.sig})
+
+mksource() {
+ mkdir "$srcdir/bar"
+ local file
+ for file in '~foo' '~a' a A; do
+ touch "$srcdir/bar/$file"
+ done
+}
+
+package() {
+ :;
+}
diff --git a/test/librefetch.d/PKGBUILD-recurse b/test/librefetch.d/PKGBUILD-recurse
new file mode 100644
index 0000000..fad5976
--- /dev/null
+++ b/test/librefetch.d/PKGBUILD-recurse
@@ -0,0 +1,18 @@
+pkgname=testpkg
+pkgver=1.0
+pkgrel=1
+pkgdesc=foo
+arch=(any)
+source=("libre://$pkgname-$pkgver.tar.gz.sig")
+
+mksource() {
+ mkdir "$srcdir/bar"
+ local file
+ for file in '~foo' '~a' a A; do
+ touch "$srcdir/bar/$file"
+ done
+}
+
+package() {
+ :;
+}
diff --git a/test/librefetch.d/list.txt b/test/librefetch.d/list.txt
new file mode 100644
index 0000000..9bd32f4
--- /dev/null
+++ b/test/librefetch.d/list.txt
@@ -0,0 +1,5 @@
+bar/
+bar/A
+bar/a
+bar/~a
+bar/~foo
diff --git a/test/librelib-test.sh b/test/librelib-test.sh
new file mode 100644
index 0000000..24c4478
--- /dev/null
+++ b/test/librelib-test.sh
@@ -0,0 +1,75 @@
+#!/usr/bin/env roundup
+
+describe librelib
+
+. ./test-common.sh
+
+before() {
+ _before
+}
+
+after() {
+ _after
+}
+
+it_displays_help_and_fails_with_0_args() {
+ ret=0
+ librelib >$tmpdir/stdout 2>$tmpdir/stderr || ret=$?
+
+ empty $tmpdir/stdout
+ [[ "$(sed 1q $tmpdir/stderr)" =~ Usage:.* ]]
+ [[ $ret != 0 ]]
+}
+
+it_fails_with_2_args() {
+ ret=0
+ librelib a b >$tmpdir/stdout 2>$tmpdir/stderr || ret=$?
+
+ empty $tmpdir/stdout
+ not empty $tmpdir/stderr
+ [[ $ret != 0 ]]
+}
+
+it_displays_usage_text() {
+ librelib -h >$tmpdir/stdout 2>$tmpdir/stderr
+
+ [[ "$(sed 1q $tmpdir/stdout)" =~ Usage:.* ]]
+ empty $tmpdir/stderr
+}
+
+# Nothing in $(libdir) should be executable anymore (except that
+# $(libexecdir)=$(libdir), and executable things go in there. But I
+# digress, libremessages should not be executable anymore).
+it_finds_messages() {
+ v1=$(librelib messages)
+ v2=$(librelib libremessages)
+ v3=$(librelib messages.sh)
+ v4=$(librelib libremessages.sh)
+
+ [[ -r "$v1" && ! -x "$v1" ]]
+ [[ "$v1" == "$v2" ]]
+ [[ "$v1" == "$v3" ]]
+ [[ "$v1" == "$v4" ]]
+}
+
+# conf.sh is non-executable
+it_finds_conf() {
+ v1=$(librelib conf)
+ v2=$(librelib libreconf)
+ v3=$(librelib conf.sh)
+ v4=$(librelib libreconf.sh)
+
+ [[ -r "$v1" && ! -x "$v1" ]]
+ [[ "$v1" == "$v2" ]]
+ [[ "$v1" == "$v3" ]]
+ [[ "$v1" == "$v4" ]]
+}
+
+it_fails_to_find_phony() {
+ ret=0
+ librelib phony >$tmpdir/stdout 2>$tmpdir/stderr || ret=$?
+
+ empty $tmpdir/stdout
+ not empty $tmpdir/stderr
+ [[ $ret != 0 ]]
+}
diff --git a/test/libremakepkg-test.sh b/test/libremakepkg-test.sh
new file mode 100644
index 0000000..ddccab0
--- /dev/null
+++ b/test/libremakepkg-test.sh
@@ -0,0 +1,134 @@
+#!/usr/bin/env roundup
+
+describe libremakepkg
+
+. ./test-common.sh
+
+_setup_chrootdir
+
+before() {
+ _before libremakepkg
+
+ mkdir -p "$XDG_CONFIG_HOME"/libretools
+
+ echo "BLACKLIST=https://repo.parabola.nu/docs/blacklist.txt" >"$XDG_CONFIG_HOME"/libretools/libretools.conf
+
+ echo "CHROOTDIR='${chrootdir}'" > "$XDG_CONFIG_HOME"/libretools/chroot.conf
+ echo "CHROOT='default'" >> "$XDG_CONFIG_HOME"/libretools/chroot.conf
+ echo "CHROOTEXTRAPKG=()" >> "$XDG_CONFIG_HOME"/libretools/chroot.conf
+}
+
+after() {
+ _after_sudo
+}
+
+it_builds_a_trivial_package() {
+ require network sudo || return 0
+ cp libremakepkg.d/PKGBUILD-hello "$tmpdir/PKGBUILD"
+ cd "$tmpdir"
+
+ libremessages msg 'Creating a chroot, may take a few minutes' &>/dev/tty
+ testsudo libremakepkg -l "$roundup_test_name"
+
+ [[ -f $(echo libretools-hello-1.0-1-any.pkg.tar.?z) ]]
+}
+
+it_enables_networking_during_prepare() {
+ require network sudo || return 0
+ cp libremakepkg.d/PKGBUILD-netprepare "$tmpdir/PKGBUILD"
+ cd "$tmpdir"
+
+ libremessages msg 'Creating a chroot, may take a few minutes' &>/dev/tty
+ testsudo libremakepkg -l "$roundup_test_name"
+ [[ -f $(echo libretools-netprepare-1.0-1-any.pkg.tar.?z) ]]
+}
+
+it_disables_networking_during_build() {
+ require network sudo || return 0
+ cp libremakepkg.d/PKGBUILD-netbuild "$tmpdir/PKGBUILD"
+ cd "$tmpdir"
+
+ libremessages msg 'Creating a chroot, may take a few minutes' &>/dev/tty
+ not testsudo libremakepkg -l "$roundup_test_name"
+ not [[ -f $(echo libretools-netbuild-1.0-1-any.pkg.tar.?z) ]]
+ testsudo libremakepkg -l "$roundup_test_name" -N
+ [[ -f $(echo libretools-netbuild-1.0-1-any.pkg.tar.?z) ]]
+}
+
+it_disables_networking_during_package() {
+ require network sudo || return 0
+ cp libremakepkg.d/PKGBUILD-netpackage "$tmpdir/PKGBUILD"
+ cd "$tmpdir"
+
+ libremessages msg 'Creating a chroot, may take a few minutes' &>/dev/tty
+ not testsudo libremakepkg -l "$roundup_test_name"
+ not [[ -f $(echo libretools-netpackage-1.0-1-any.pkg.tar.?z) ]]
+ testsudo libremakepkg -l "$roundup_test_name" -N
+ [[ -f $(echo libretools-netpackage-1.0-1-any.pkg.tar.?z) ]]
+}
+
+it_cleans_the_chroot_before_building() {
+ require network sudo || return 0
+ # 1. First, we build testpkg1
+ # 2. Then, we build testpkg2, which depends on testpkg1
+ # Therefore, testpkg1 will be installed after testpkg2 is built, we
+ # check for that.
+ # 3. Then, we build hello, which depends on neither, so testpkg1 should
+ # be removed.
+
+ # Also, do funny things with the output of libremakepkg to get a helpful
+ # fail case.
+
+ mkdir -p "$tmpdir"/{1,2,3}
+ cp libremakepkg.d/PKGBUILD-testpkg1 "$tmpdir/1/PKGBUILD"
+ cp libremakepkg.d/PKGBUILD-testpkg2 "$tmpdir/2/PKGBUILD"
+ cp libremakepkg.d/PKGBUILD-hello "$tmpdir/3/PKGBUILD"
+
+ libremessages msg 'Creating a chroot, may take a few minutes' &>/dev/tty
+
+ cd "$tmpdir/1"
+ testsudo libremakepkg -l "$roundup_test_name" &> "$tmpdir/out" || { r=$?; tail "$tmpdir/out"|cat -v; return $r; }
+
+ cd "$tmpdir/2"
+ testsudo libremakepkg -l "$roundup_test_name" &> "$tmpdir/out" || { r=$?; tail "$tmpdir/out"|cat -v; return $r; }
+ testsudo librechroot -l "$roundup_test_name" run libretools-testpkg1 'first time, pass'
+
+ # This next line is actually a separate test, but it fits in well with this test, and chroot tests are slow..
+ # it_doesnt_cache_local_packages() {
+ not testsudo librechroot -l "$roundup_test_name" run test -e /var/cache/pacman/pkg/libretools-testpkg1-1.0-1-any.pkg.tar.?z
+
+ cd "$tmpdir/3"
+ testsudo libremakepkg -l "$roundup_test_name" &> "$tmpdir/out" || { r=$?; tail "$tmpdir/out"|cat -v; return $r; }
+ not testsudo librechroot -l "$roundup_test_name" run libretools-testpkg1 'second time, fail'
+}
+
+it_handles_PKGDEST_not_existing() {
+ require network sudo || return 0
+ cp libremakepkg.d/PKGBUILD-hello "$tmpdir/PKGBUILD"
+ cd "$tmpdir"
+
+ libremessages msg 'Creating a chroot, may take a few minutes' &>/dev/tty
+ testsudo env PKGDEST="$tmpdir/dest/pkgdest" libremakepkg -l "$roundup_test_name"
+
+ [[ -f $(echo dest/pkgdest/libretools-hello-1.0-1-any.pkg.tar.?z) ]]
+}
+
+it_displays_help_as_normal_user() {
+ rm -rf "$XDG_CONFIG_HOME"
+ LC_ALL=C libremakepkg -h >$tmpdir/stdout 2>$tmpdir/stderr
+
+ [[ "$(sed 1q $tmpdir/stdout)" =~ Usage:.* ]]
+ empty $tmpdir/stderr
+}
+
+it_otherwise_fails_as_normal_user() {
+ # I do this to give it a chance of passing
+ cp libremakepkg.d/PKGBUILD-hello "$tmpdir/PKGBUILD"
+ cd "$tmpdir"
+
+ libremakepkg >$tmpdir/stdout 2>$tmpdir/stderr || stat=$?
+
+ [[ $stat != 0 ]]
+ empty $tmpdir/stdout
+ not empty $tmpdir/stderr
+}
diff --git a/test/libremakepkg.d/PKGBUILD-hello b/test/libremakepkg.d/PKGBUILD-hello
new file mode 100644
index 0000000..5f320fe
--- /dev/null
+++ b/test/libremakepkg.d/PKGBUILD-hello
@@ -0,0 +1,19 @@
+pkgname='libretools-hello'
+pkgver=1.0
+license=('GPL')
+url='https://parabola.nu'
+
+pkgrel=1
+arch=(any)
+depends=(sh)
+
+build() {
+ cd "$srcdir"
+ echo '#!/bin/sh' > hello.sh
+ echo 'echo Hello, world!' >> hello.sh
+}
+
+package() {
+ cd "$srcdir"
+ install -Dm755 hello.sh "$pkgdir"/usr/bin/libretools-hello
+}
diff --git a/test/libremakepkg.d/PKGBUILD-netbuild b/test/libremakepkg.d/PKGBUILD-netbuild
new file mode 100644
index 0000000..4db1274
--- /dev/null
+++ b/test/libremakepkg.d/PKGBUILD-netbuild
@@ -0,0 +1,17 @@
+pkgname='libretools-netbuild'
+pkgver=1.0
+license=('GPL')
+url='https://parabola.nu'
+
+pkgrel=1
+arch=(any)
+
+build() {
+ cd "$srcdir"
+ curl https://repo.parabola.nu/ > index.html
+}
+
+package() {
+ cd "$srcdir"
+ install -Dm644 index.html "$pkgdir"/usr/share/$pkgname/index.html
+}
diff --git a/test/libremakepkg.d/PKGBUILD-netpackage b/test/libremakepkg.d/PKGBUILD-netpackage
new file mode 100644
index 0000000..6cadcf8
--- /dev/null
+++ b/test/libremakepkg.d/PKGBUILD-netpackage
@@ -0,0 +1,12 @@
+pkgname='libretools-netpackage'
+pkgver=1.0
+license=('GPL')
+url='https://parabola.nu'
+
+pkgrel=1
+arch=(any)
+
+package() {
+ install -d "$pkgdir"/usr/share/$pkgname
+ curl https://repo.parabola.nu/ > "$pkgdir"/usr/share/$pkgname/index.html
+}
diff --git a/test/libremakepkg.d/PKGBUILD-netprepare b/test/libremakepkg.d/PKGBUILD-netprepare
new file mode 100644
index 0000000..efb7a43
--- /dev/null
+++ b/test/libremakepkg.d/PKGBUILD-netprepare
@@ -0,0 +1,17 @@
+pkgname='libretools-netprepare'
+pkgver=1.0
+license=('GPL')
+url='https://parabola.nu'
+
+pkgrel=1
+arch=(any)
+
+prepare() {
+ cd "$srcdir"
+ curl https://repo.parabola.nu/ > index.html
+}
+
+package() {
+ cd "$srcdir"
+ install -Dm644 index.html "$pkgdir"/usr/share/$pkgname/index.html
+}
diff --git a/test/libremakepkg.d/PKGBUILD-testpkg1 b/test/libremakepkg.d/PKGBUILD-testpkg1
new file mode 100644
index 0000000..8da1f14
--- /dev/null
+++ b/test/libremakepkg.d/PKGBUILD-testpkg1
@@ -0,0 +1,19 @@
+pkgname='libretools-testpkg1'
+pkgver=1.0
+license=('GPL')
+url='https://parabola.nu'
+
+pkgrel=1
+arch=(any)
+depends=(sh)
+
+build() {
+ cd "$srcdir"
+ echo '#!/bin/sh' > testpkg1.sh
+ echo 'echo testpkg1' >> testpkg1.sh
+}
+
+package() {
+ cd "$srcdir"
+ install -Dm755 testpkg1.sh "$pkgdir"/usr/bin/libretools-testpkg1
+}
diff --git a/test/libremakepkg.d/PKGBUILD-testpkg2 b/test/libremakepkg.d/PKGBUILD-testpkg2
new file mode 100644
index 0000000..65d558e
--- /dev/null
+++ b/test/libremakepkg.d/PKGBUILD-testpkg2
@@ -0,0 +1,19 @@
+pkgname='libretools-testpkg2'
+pkgver=1.0
+license=('GPL')
+url='https://parabola.nu'
+
+pkgrel=1
+arch=(any)
+depends=(sh libretools-testpkg1)
+
+build() {
+ cd "$srcdir"
+ echo '#!/bin/sh' > testpkg2.sh
+ echo 'libretools-testpkg1' >> testpkg2.sh
+}
+
+package() {
+ cd "$srcdir"
+ install -Dm755 testpkg2.sh "$pkgdir"/usr/bin/libretools-testpkg2
+}
diff --git a/test/librerelease-test.sh b/test/librerelease-test.sh
new file mode 100644
index 0000000..a44b150
--- /dev/null
+++ b/test/librerelease-test.sh
@@ -0,0 +1,56 @@
+#!/usr/bin/env roundup
+
+describe librestage
+
+. ./test-common.sh
+
+before() {
+ _before
+
+ mkdir -p $XDG_CONFIG_HOME/libretools
+ {
+ echo "WORKDIR='$tmpdir/workdir'"
+ echo 'REPODEST=repo@repo:/srv/http/repo/staging-$LIBREUSER'
+ } >$XDG_CONFIG_HOME/libretools/libretools.conf
+ {
+ echo 'PKGEXT=.pkg.tar.gz'
+ echo "PKGDEST='$tmpdir/workdir/pkgdest'"
+ echo "GPGKEY=YOURKEY"
+ } > $HOME/.makepkg.conf
+ mkdir -p "$tmpdir/workdir/pkgdest"
+}
+
+after() {
+ _after
+}
+
+it_displays_usage_text() {
+ rm -rf "$XDG_CONFIG_HOME"
+ LC_ALL=C librerelease -h >"$tmpdir/stdout" 2>"$tmpdir/stderr"
+
+ [[ "$(sed 1q "$tmpdir/stdout")" =~ Usage:.* ]]
+ empty "$tmpdir/stderr"
+}
+
+it_lists_all_files() {
+ WORKDIR="$tmpdir/workdir"
+ mkdir -p "$WORKDIR/staging/repo1" "$WORKDIR/staging/repo2/sub"
+ touch \
+ "$WORKDIR/staging/repo1/file1" \
+ "$WORKDIR/staging/repo1/file2" \
+ "$WORKDIR/staging/repo2/file with spaces" \
+ "$WORKDIR/staging/repo2/sub/subfolder"
+ unset WORKDIR
+ LC_ALL=C librerelease -l &>"$tmpdir/list"
+
+ cat > "$tmpdir/list-correct" <<EOF
+ -> repo1
+ file1
+ file2
+ -> repo2
+ file with spaces
+ sub/subfolder
+EOF
+
+ diff "$tmpdir/list-correct" "$tmpdir/list"
+}
diff --git a/test/librestage-test.sh b/test/librestage-test.sh
new file mode 100644
index 0000000..affad6a
--- /dev/null
+++ b/test/librestage-test.sh
@@ -0,0 +1,72 @@
+#!/usr/bin/env roundup
+
+describe librestage
+
+. ./test-common.sh
+
+before() {
+ _before
+
+ mkdir -p $XDG_CONFIG_HOME/libretools
+ echo "WORKDIR='$tmpdir/workdir'" > $XDG_CONFIG_HOME/libretools/libretools.conf
+ echo "ARCHES=('x86_64' 'i686' 'misp64el')" >> $XDG_CONFIG_HOME/libretools/libretools.conf
+
+ mkdir -p $XDG_CONFIG_HOME/xbs
+ echo "BUILDSYSTEM=abslibre" > $XDG_CONFIG_HOME/xbs/xbs.conf
+
+ echo 'PKGEXT=.pkg.tar.gz' > $HOME/.makepkg.conf
+ echo "PKGDEST='$tmpdir/workdir/pkgdest'" >> $HOME/.makepkg.conf
+ echo "PACKAGER='Test Suite <test@localhost>'" >> $HOME/.makepkg.conf
+ mkdir -p "$tmpdir/workdir/pkgdest"
+}
+
+after() {
+ _after
+}
+
+it_displays_usage_text() {
+ rm -rf "$XDG_CONFIG_HOME"
+ LC_ALL=C librestage -h >$tmpdir/stdout 2>$tmpdir/stderr
+
+ [[ "$(sed 1q "$tmpdir/stdout")" =~ Usage:.* ]]
+ empty "$tmpdir/stderr"
+}
+
+it_fails_with_0_args() {
+ librestage >$tmpdir/stdout 2>$tmpdir/stderr || stat=$?
+
+ [[ $stat != 0 ]]
+ empty "$tmpdir/stdout"
+ not empty "$tmpdir/stderr"
+}
+
+it_fails_with_invalid_args() {
+ librestage -q >$tmpdir/stdout 2>$tmpdir/stderr || stat=$?
+
+ [[ $stat != 0 ]]
+ empty "$tmpdir/stdout"
+ not empty "$tmpdir/stderr"
+}
+
+it_guesses_the_repo() {
+ mkdir -p -- "$tmpdir/reponame/libretools-hello"
+ cp librestage.d/PKGBUILD-hello "$tmpdir/reponame/libretools-hello/PKGBUILD"
+ cd "$tmpdir/reponame/libretools-hello"
+
+ makepkg
+ librestage
+
+ [[ -f $tmpdir/workdir/staging/reponame/libretools-hello-1.0-1-any.pkg.tar.gz ]]
+}
+
+it_stages_packages_without_PKGDEST() {
+ echo "PKGDEST=''" >> $HOME/.makepkg.conf
+
+ cp librestage.d/PKGBUILD-hello "$tmpdir/PKGBUILD"
+ cd "$tmpdir"
+
+ makepkg
+ librestage repo1
+
+ [[ -f $tmpdir/workdir/staging/repo1/libretools-hello-1.0-1-any.pkg.tar.gz ]]
+}
diff --git a/test/librestage.d/PKGBUILD-hello b/test/librestage.d/PKGBUILD-hello
new file mode 100644
index 0000000..5f320fe
--- /dev/null
+++ b/test/librestage.d/PKGBUILD-hello
@@ -0,0 +1,19 @@
+pkgname='libretools-hello'
+pkgver=1.0
+license=('GPL')
+url='https://parabola.nu'
+
+pkgrel=1
+arch=(any)
+depends=(sh)
+
+build() {
+ cd "$srcdir"
+ echo '#!/bin/sh' > hello.sh
+ echo 'echo Hello, world!' >> hello.sh
+}
+
+package() {
+ cd "$srcdir"
+ install -Dm755 hello.sh "$pkgdir"/usr/bin/libretools-hello
+}
diff --git a/test/pkgbuild-check-nonfree-test.sh b/test/pkgbuild-check-nonfree-test.sh
new file mode 100644
index 0000000..2af2669
--- /dev/null
+++ b/test/pkgbuild-check-nonfree-test.sh
@@ -0,0 +1,78 @@
+#!/usr/bin/env roundup
+
+# avoid carpel tunnel
+pcn=pkgbuild-check-nonfree
+psn=pkgbuild-summarize-nonfree
+
+describe $pcn
+
+. ./test-common.sh
+
+before() {
+ _before
+
+ mkdir -p $XDG_CONFIG_HOME/libretools
+ echo "BLACKLIST='phony://example.com'" >$XDG_CONFIG_HOME/libretools/libretools.conf
+
+ local blacklist=$XDG_CACHE_HOME/libretools/blacklist.txt
+ mkdir -p "${blacklist%/*}"
+ echo 'linux:linux-libre:nonfree blobs and firmwares' >$blacklist
+ echo 'skype' >>$blacklist
+}
+
+after() {
+ _after
+}
+
+it_displays_usage_text() {
+ # This test seems silly, but it makes sure that it is executable,
+ # syntactically correct, and loading libraries works.
+ LC_ALL=C $pcn -h >$tmpdir/stdout 2>$tmpdir/stderr
+ stat=$?
+
+ [[ "$(sed 1q $tmpdir/stdout)" =~ Usage:.* ]]
+ empty $tmpdir/stderr
+ [[ $stat == 0 ]]
+}
+
+it_succeeds_for_free_depends() {
+ $pcn $pcn.d/PKGBUILD.free >$tmpdir/stdout 2>$tmpdir/stderr || stat=$?
+
+ empty $tmpdir/stdout
+ not empty $tmpdir/stderr
+ [[ $stat == 0 ]]
+}
+
+it_succeeds_for_nonfree_depend_with_replacement() {
+ $pcn $pcn.d/PKGBUILD.nonfree-replacement >$tmpdir/stdout 2>$tmpdir/stderr || stat=$?
+
+ empty $tmpdir/stdout
+ not empty $tmpdir/stderr
+ [[ $stat == 0 ]]
+}
+
+it_fails_for_nonfree_depend() {
+ $pcn $pcn.d/PKGBUILD.nonfree >$tmpdir/stdout 2>$tmpdir/stderr || stat=$?
+ [[ $stat != 0 ]]
+ empty $tmpdir/stdout
+ not empty $tmpdir/stderr
+
+ local pcn_stat=$stat
+
+ $psn $pcn_stat >$tmpdir/stdout 2>$tmpdir/stderr || stat=$?
+ [[ $stat != 0 ]]
+ empty $tmpdir/stdout
+ not empty $tmpdir/stderr
+}
+
+it_fails_when_there_is_no_blacklist() {
+ mkdir -p $XDG_CONFIG_HOME/libretools
+ echo "BLACKLIST='phony://example.com'" >$XDG_CONFIG_HOME/libretools/libretools.conf
+ rm $XDG_CACHE_HOME/libretools/blacklist.txt
+
+ $pcn $pcn.d/PKGBUILD.free >$tmpdir/stdout 2>$tmpdir/stderr || stat=$?
+
+ empty $tmpdir/stdout
+ not empty $tmpdir/stderr
+ [[ $stat != 0 ]] && [[ $stat != 15 ]]
+}
diff --git a/test/pkgbuild-check-nonfree.d/PKGBUILD.free b/test/pkgbuild-check-nonfree.d/PKGBUILD.free
new file mode 100644
index 0000000..4b8f0dd
--- /dev/null
+++ b/test/pkgbuild-check-nonfree.d/PKGBUILD.free
@@ -0,0 +1,18 @@
+pkgname=wmii
+pkgver=3.9.2
+pkgrel=3
+pkgdesc="A small, dynamic window manager for X11"
+arch=('i686' 'x86_64')
+license=('MIT')
+url="http://wmii.suckless.org/"
+depends=('libxft' 'libxrandr' 'libxinerama' 'dash')
+source=()
+md5sums=()
+
+build() {
+ :
+}
+
+package() {
+ :
+}
diff --git a/test/pkgbuild-check-nonfree.d/PKGBUILD.nonfree b/test/pkgbuild-check-nonfree.d/PKGBUILD.nonfree
new file mode 100644
index 0000000..3a7afa4
--- /dev/null
+++ b/test/pkgbuild-check-nonfree.d/PKGBUILD.nonfree
@@ -0,0 +1,18 @@
+pkgname=wmii
+pkgver=3.9.2
+pkgrel=3
+pkgdesc="A small, dynamic window manager for X11"
+arch=('i686' 'x86_64')
+license=('MIT')
+url="http://wmii.suckless.org/"
+depends=('skype') # random non-free package with no other information
+source=()
+md5sums=()
+
+build() {
+ :
+}
+
+package() {
+ :
+}
diff --git a/test/pkgbuild-check-nonfree.d/PKGBUILD.nonfree-replacement b/test/pkgbuild-check-nonfree.d/PKGBUILD.nonfree-replacement
new file mode 100644
index 0000000..7855bdc
--- /dev/null
+++ b/test/pkgbuild-check-nonfree.d/PKGBUILD.nonfree-replacement
@@ -0,0 +1,18 @@
+pkgname=wmii
+pkgver=3.9.2
+pkgrel=3
+pkgdesc="A small, dynamic window manager for X11"
+arch=('i686' 'x86_64')
+license=('MIT')
+url="http://wmii.suckless.org/"
+depends=('linux') # random non-free package with a replacement
+source=()
+md5sums=()
+
+build() {
+ :
+}
+
+package() {
+ :
+}
diff --git a/test/test-common.sh b/test/test-common.sh
new file mode 100644
index 0000000..2a8fc37
--- /dev/null
+++ b/test/test-common.sh
@@ -0,0 +1,78 @@
+#!/hint/bash
+
+if [[ $HOME == "$(eval echo ~$USER)" ]]; then
+ libremessages error "\$HOME is the default for %s; use testenv: %s" "$USER" "$HOME"
+ exit 1
+fi
+
+_before() {
+ unset PKGDEST SRCDEST SRCPKGDEST LOGDEST
+ unset BUILDDIR
+ unset PKGEXT SRCEXT
+ unset GPGKEY PACKAGER
+ tmpdir="$(mktemp -d --tmpdir "test-${roundup_desc//\//-}.XXXXXXXXXXXX")"
+ chmod 755 "$tmpdir"
+ stat=0
+}
+
+_after() {
+ rm -rf -- "$tmpdir" "$XDG_CONFIG_HOME" "$XDG_CACHE_HOME"
+}
+
+_after_sudo() {
+ if [[ $SUDO ]]; then
+ sudo rm -rf -- "$tmpdir" "$XDG_CONFIG_HOME" "$XDG_CACHE_HOME"
+ else
+ rm -rf -- "$tmpdir" "$XDG_CONFIG_HOME" "$XDG_CACHE_HOME"
+ fi
+}
+
+_setup_chrootdir() {
+ if [[ -z "$chrootdir" ]]; then
+ export chrootdir="$(mktemp -d --tmpdir "test-chrootdir.XXXXXXXXXXXX")"
+ trap "$(printf '_cleanup_chrootdir %q' "$chrootdir")" EXIT
+ fi
+}
+
+_cleanup_chrootdir() (
+ chrootdir=$1
+ shopt -s nullglob
+ if [[ $SUDO ]]; then
+ for copydir in "$chrootdir"/*/*/; do
+ local chroottype=$(stat -f -c %T "$copydir")
+ if [[ "$chroottype" == btrfs ]] && ! mountpoint -q "$copydir"; then
+ sudo btrfs subvolume delete "$copydir" >/dev/null
+ fi
+ done
+ sudo rm -rf -- "$chrootdir"
+ else
+ rm -rf -- "$chrootdir"
+ fi
+)
+
+require() (
+ set +x
+ local missing=()
+ if libremessages in_array "network" "$@" && ! [[ $NETWORK ]]; then
+ missing+=('networking')
+ fi
+ if libremessages in_array "sudo" "$@" && ! [[ $SUDO ]]; then
+ missing+=('sudo')
+ fi
+ if (( ${#missing[@]} )); then
+ libremessages warning "Next test requires %s; Skipping (passing)..." "$(echo "${missing[*]}"|sed 's/ /, /g')" &>/dev/tty
+ return 1
+ fi
+ return 0;
+)
+
+empty() (
+ set +x
+ [[ $(stat -c %s "$1") -eq 0 ]]
+)
+
+# Just using '!' doesn't trip `set -e`
+not() (
+ set +x
+ ! eval "$@"
+)
diff --git a/test/testenv b/test/testenv
new file mode 100755
index 0000000..7072326
--- /dev/null
+++ b/test/testenv
@@ -0,0 +1,69 @@
+#!/usr/bin/env bash
+
+# Parse the arguments
+NETWORK=true
+SUDO=true
+while [[ $# -gt 0 ]]; do
+ case "$1" in
+ --no-network) shift; unset NETWORK;;
+ --network) shift; NETWORK=true;;
+ --no-sudo) shift; unset SUDO;;
+ --sudo) shift; SUDO=true;;
+ --) shift; break;;
+ *) break;;
+ esac
+done
+export NETWORK SUDO
+
+if [[ $# == 0 ]]; then
+ echo 'You need to run testenv with arguments!' >&2
+ exit 1
+fi
+
+# Set up the working directory, and add the hook to clean it up
+export TMPDIR="$(mktemp --tmpdir -d libretools-test.XXXXXXXXXX)"
+trap "rm -rf '$TMPDIR'" EXIT
+
+# Set up the install to work with
+destdir=$TMPDIR/destdir
+
+old_pwd="$(pwd)"
+if [[ -f $0 ]]; then
+ cd "${0%/*}"
+fi
+if ! make -C .. install DESTDIR="$destdir" &>"$TMPDIR/make-output"; then
+ echo 'error creating local install, cannot run tests' >&2
+ cat "$TMPDIR/make-output"
+ exit 1
+fi
+cd "$old_pwd"
+
+# Set up the environment
+export PATH="$destdir/usr/bin:$PATH"
+export LIBRETOOLS_LIBDIR="$destdir/usr/lib/libretools"
+export XBS_LIBDIR="$destdir/usr/lib/xbs"
+export HOME=$TMPDIR/home
+export XDG_CACHE_HOME="$HOME/.cache"
+export XDG_CONFIG_HOME="$HOME/.config"
+export _librelib_conf_sh_sysconfdir="$destdir/etc"
+export _librelib_conf_sh_pkgconfdir="$destdir/etc/libretools.d"
+
+# Hack to respect our variables in sudo
+_sudo() {
+ local vars=(TMPDIR PATH LIBRETOOLS_LIBDIR XDG_CACHE_HOME XDG_CONFIG_HOME _librelib_conf_sh_sysconfdir)
+ local args=()
+ local var
+ for var in "${vars[@]}"; do
+ args+=("$var=${!var}")
+ done
+ sudo env "${args[@]}" "$@"
+}
+printf '%s\n' \
+ '#!/bin/bash' \
+ "$(declare -f _sudo)" \
+ '_sudo "$@"' \
+ > "$destdir/usr/bin/testsudo"
+chmod 755 "$destdir/usr/bin/testsudo"
+
+# Run the tests
+eval "$@"
diff --git a/write-ifchanged b/write-ifchanged
new file mode 100755
index 0000000..c65fa16
--- /dev/null
+++ b/write-ifchanged
@@ -0,0 +1,11 @@
+#!/usr/bin/env bash
+
+outfile=$1
+tmpfile="$(dirname "$outfile")/.tmp${outfile##*/}"
+
+cat > "$tmpfile" || exit $?
+if cmp -s "$tmpfile" "$outfile"; then
+ rm -f "$tmpfile" || :
+else
+ mv -f "$tmpfile" "$outfile"
+fi