diff --git a/Makefile b/Makefile index ccfed0d..7053e2d 100644 --- a/Makefile +++ b/Makefile @@ -9,7 +9,8 @@ datadir ?= $(prefix)/share/$(PACKAGE) abuildrepo ?= ~/.cache/abuild SCRIPTS := abuild buildrepo abuild-keygen abuild-sign newapkbuild \ - abump apkgrel buildlab apkbuild-cpan checkapk + abump apkgrel buildlab apkbuild-cpan checkapk \ + apkbuild-gem-resolver USR_BIN_FILES := $(SCRIPTS) abuild-tar abuild-sudo SAMPLES := sample.APKBUILD sample.initd sample.confd \ sample.pre-install sample.post-install diff --git a/abuild-tar.c b/abuild-tar.c index 4a88882..2f29d30 100644 --- a/abuild-tar.c +++ b/abuild-tar.c @@ -1,6 +1,6 @@ /* abuild-tar.c - A TAR mangling utility for .APK packages * - * Copyright (C) 2009 Timo Teräs + * Copyright (C) 2009-2014 Timo Teräs * All rights reserved. * * This program is free software; you can redistribute it and/or modify it @@ -89,6 +89,19 @@ static void put_octal(char *s, size_t l, size_t value) *(ptr--) = '0'; } +static void tarhdr_checksum(struct tar_header *hdr) +{ + const unsigned char *src; + size_t chksum, i; + + /* Recalculate checksum */ + memset(hdr->chksum, ' ', sizeof(hdr->chksum)); + src = (const unsigned char *) hdr; + for (i = chksum = 0; i < sizeof(*hdr); i++) + chksum += src[i]; + put_octal(hdr->chksum, sizeof(hdr->chksum)-1, chksum); +} + static int usage(void) { fprintf(stderr, @@ -120,7 +133,7 @@ static ssize_t full_read(int fd, void *buf, size_t count) } while (1); if (total == 0 && n < 0) - return -1; + return -errno; return total; } @@ -142,7 +155,7 @@ static ssize_t full_write(int fd, const void *buf, size_t count) } while (1); if (total == 0 && n < 0) - return -1; + return -errno; return total; } @@ -163,82 +176,182 @@ static ssize_t full_splice(int from_fd, int to_fd, size_t count) } while (1); if (total == 0 && n < 0) - return -1; + return -errno; return total; } -static int do_it(const EVP_MD *md, int cut) +#define BUF_INITIALIZER {0} +struct buf { + char *ptr; + size_t size; + size_t alloc; +}; + +static void buf_free(struct buf *b) +{ + free(b->ptr); +} + +static int buf_resize(struct buf *b, size_t newsize) { - struct tar_header hdr; - size_t size, aligned_size; void *ptr; - int dohash = 0, r; + + if (b->alloc >= newsize) return 0; + ptr = realloc(b->ptr, newsize); + if (!ptr) return -ENOMEM; + b->ptr = ptr; + b->alloc = newsize; + return 0; +} + +static int buf_padto(struct buf *b, size_t alignment) +{ + size_t oldsize, newsize; + oldsize = b->size; + newsize = (oldsize + alignment - 1) & -alignment; + if (buf_resize(b, newsize)) return -ENOMEM; + b->size = newsize; + memset(b->ptr + oldsize, 0, newsize - oldsize); + return 0; +} + +static int buf_read_fd(struct buf *b, int fd, size_t size) +{ + ssize_t r; + + r = buf_resize(b, size); + if (r) return r; + + r = full_read(fd, b->ptr, size); + if (r == size) { + b->size = r; + return 0; + } + b->size = 0; + if (r < 0) return r; + return -EIO; +} + +static int buf_write_fd(struct buf *b, int fd) +{ + ssize_t r; + r = full_write(fd, b->ptr, b->size); + if (r == b->size) return 0; + if (r < 0) return r; + return -ENOSPC; +} + +static int buf_add_ext_header_hexdump(struct buf *b, const char *hdr, const char *value, int valuelen) +{ + size_t oldsize = b->size; + int i, len; + + /* "%u %s=%s\n" */ + len = 1 + 1 + strlen(hdr) + 1 + valuelen*2 + 1; + for (i = len; i > 9; i /= 10) len++; + + if (buf_resize(b, b->size + len)) return -ENOMEM; + b->size += snprintf(&b->ptr[b->size], len, "%u %s=", len, hdr); + for (i = 0; i < valuelen; i++) + b->size += snprintf(&b->ptr[b->size], 3, "%02x", (int)(unsigned char)value[i]); + b->ptr[b->size++] = '\n'; + + return 0; +} + +static void add_legacy_checksum(struct tar_header *hdr, const EVP_MD *md, const char *digest) +{ struct { char id[4]; uint16_t nid; uint16_t size; } mdinfo; - if (md != NULL) { - memcpy(mdinfo.id, "APK2", 4); - mdinfo.nid = EVP_MD_nid(md); - mdinfo.size = EVP_MD_size(md); - } + memcpy(mdinfo.id, "APK2", 4); + mdinfo.nid = EVP_MD_nid(md); + mdinfo.size = EVP_MD_size(md); + memcpy(&hdr->linkname[3], &mdinfo, sizeof(mdinfo)); + memcpy(&hdr->linkname[3+sizeof(mdinfo)], digest, mdinfo.size); + tarhdr_checksum(hdr); +} + +static int do_it(const EVP_MD *md, int cut) +{ + char checksumhdr[32], digest[EVP_MAX_MD_SIZE]; + struct buf data = BUF_INITIALIZER, pax = BUF_INITIALIZER; + struct tar_header hdr, paxhdr; + size_t size, aligned_size; + int dohash = 0, r, ret = 1; + + if (md) snprintf(checksumhdr, sizeof(checksumhdr), "APK-TOOLS.checksum.%s", EVP_MD_name(md)); do { if (full_read(STDIN_FILENO, &hdr, sizeof(hdr)) != sizeof(hdr)) - return 0; + goto err; if (cut && hdr.name[0] == 0) - return 0; + break; size = GET_OCTAL(hdr.size); aligned_size = (size + 511) & ~511; - if (md != NULL) - dohash = (hdr.typeflag == '0' || hdr.typeflag == '7'); + if (hdr.typeflag == 'x') { + memcpy(&paxhdr, &hdr, sizeof(hdr)); + if (buf_read_fd(&pax, STDIN_FILENO, aligned_size)) goto err; + pax.size = size; + continue; + } + + dohash = md && (hdr.typeflag == '0' || hdr.typeflag == '7' || hdr.typeflag == '2'); if (dohash) { - const unsigned char *src; - int chksum, i; - - ptr = malloc(aligned_size); - if (full_read(STDIN_FILENO, ptr, aligned_size) != aligned_size) - return 1; - - memcpy(&hdr.linkname[3], &mdinfo, sizeof(mdinfo)); - EVP_Digest(ptr, size, &hdr.linkname[3+sizeof(mdinfo)], - NULL, md, NULL); - - /* Recalculate checksum */ - memset(hdr.chksum, ' ', sizeof(hdr.chksum)); - src = (const unsigned char *) &hdr; - for (i = chksum = 0; i < sizeof(hdr); i++) - chksum += src[i]; - put_octal(hdr.chksum, sizeof(hdr.chksum)-1, chksum); + if (buf_read_fd(&data, STDIN_FILENO, aligned_size)) goto err; + + if (hdr.typeflag != '2') { + EVP_Digest(data.ptr, size, digest, NULL, md, NULL); + add_legacy_checksum(&hdr, md, digest); + } else { + EVP_Digest(hdr.linkname, strlen(hdr.linkname), digest, NULL, md, NULL); + } + + buf_add_ext_header_hexdump(&pax, checksumhdr, digest, EVP_MD_size(md)); + PUT_OCTAL(paxhdr.size, pax.size); + tarhdr_checksum(&paxhdr); + } + + if (pax.size) { + /* write pax header + content */ + if (full_write(STDOUT_FILENO, &paxhdr, sizeof(paxhdr)) != sizeof(paxhdr) || + buf_padto(&pax, 512) || + buf_write_fd(&pax, STDOUT_FILENO)) goto err; } - if (full_write(STDOUT_FILENO, &hdr, sizeof(hdr)) != sizeof(hdr)) - return 2; + if (full_write(STDOUT_FILENO, &hdr, sizeof(hdr)) != sizeof(hdr)) goto err; if (dohash) { - if (full_write(STDOUT_FILENO, ptr, aligned_size) != aligned_size) - return 2; - free(ptr); + if (buf_write_fd(&data, STDOUT_FILENO)) goto err; } else if (aligned_size != 0) { r = full_splice(STDIN_FILENO, STDOUT_FILENO, aligned_size); if (r == -1) { while (aligned_size > 0) { if (full_read(STDIN_FILENO, &hdr, sizeof(hdr)) != sizeof(hdr)) - return 1; + goto err; if (full_write(STDOUT_FILENO, &hdr, sizeof(hdr)) != sizeof(hdr)) - return 2; + goto err; aligned_size -= sizeof(hdr); } - } else if (r != aligned_size) - return 2; + } else if (r != aligned_size) goto err; } + + memset(&paxhdr, 0, sizeof(paxhdr)); + pax.size = 0; } while (1); + + ret = 0; +err: + buf_free(&data); + buf_free(&pax); + return ret; } int main(int argc, char **argv) diff --git a/abuild.in b/abuild.in index 5459a9b..93c723d 100644 --- a/abuild.in +++ b/abuild.in @@ -616,13 +616,12 @@ postcheck() { return 1 fi fi - # look for *.la files - i=$(find "$dir" -name '*.la' | sed "s|^$dir|\t|") - if [ -n "$i" ] && ! options_has "libtool"; then - error "Libtool archives (*.la) files found and \$options has no 'libtool' flag:" - echo "$i" - return 1 + + # remove *.la files if libtool is not set + if ! options_has "libtool"; then + find "$dir" -name '*.la' -type f -delete fi + # look for /usr/lib/charset.alias if [ -e "$dir"/usr/lib/charset.alias ] \ && ! options_has "charset.alias"; then @@ -816,18 +815,13 @@ EOF echo "replaces_priority = $replaces_priority" >> "$pkginfo" fi - for i in $license; do - echo "license = $i" >> "$pkginfo" - done + echo "license = $license" >> "$pkginfo" for i in $replaces; do echo "replaces = $i" >> "$pkginfo" done for i in $deps; do echo "depend = $i" >> "$pkginfo" done - for i in $conflicts; do - echo "conflict = $i" >> "$pkginfo" - done for i in $provides; do echo "provides = $i" >> "$pkginfo" done @@ -1140,7 +1134,7 @@ create_apks() { touch .dummy set -- .dummy fi - tar -c "$@" | abuild-tar --hash | gzip -9 >"$dir"/data.tar.gz + tar --xattrs -c "$@" | abuild-tar --hash | gzip -9 >"$dir"/data.tar.gz msg "Create checksum..." # append the hash for data.tar.gz @@ -1298,6 +1292,8 @@ default_dev() { done cd "$pkgdir" || return 0 + local libdirs=usr/ + [ -d lib/ ] && libdirs="lib/ $libdirs" for i in usr/include usr/lib/pkgconfig usr/share/aclocal\ usr/share/gettext usr/bin/*-config \ usr/share/vala/vapi usr/share/gir-[0-9]*\ @@ -1305,7 +1301,7 @@ default_dev() { usr/lib/qt*/mkspecs \ usr/lib/cmake \ $(find . -name include -type d) \ - $(find lib/ usr/ -name '*.[acho]' \ + $(find $libdirs -name '*.[acho]' \ -o -name '*.prl' 2>/dev/null); do if [ -e "$pkgdir/$i" ] || [ -L "$pkgdir/$i" ]; then d="$subpkgdir/${i%/*}" # dirname $i @@ -1532,7 +1528,7 @@ deptrace() { # build and install dependencies builddeps() { - local pkg= i= missing= conflicts= + local pkg= i= missing= local hostdeps= builddeps= installed_hostdeps= installed_builddeps= [ -n "$nodeps" ] && return 0 msg "Analyzing dependencies..." @@ -1562,27 +1558,20 @@ builddeps() { # find which deps are missing for i in $builddeps; do - if [ "${i#\!}" != "$i" ]; then - $APK info --quiet --installed "${i#\!}" \ - && conflicts="$conflicts ${i#\!}" + if [ "${i#\!}" != "$i" ] && $APK info --quiet --installed "${i#\!}"; then + error "Conflicting package installed: ${i#\!}" elif ! deplist_has $i $installed_builddeps || [ -n "$upgrade" ]; then missing="$missing $i" fi done for i in $hostdeps; do - if [ "${i#\!}" != "$i" ]; then - $APK info --quiet --installed --root "$CBUILDROOT" "${i#\!}" \ - && conflicts="$conflicts ${i#\!}" + if [ "${i#\!}" != "$i" ] && $APK info --quiet --installed --root "$CBUILDROOT" "${i#\!}"; then + error "Conflicting package installed: ${i#\!}" elif ! deplist_has $i $installed_hostdeps || [ -n "$upgrade" ]; then missing="$missing $i" fi done - if [ -n "$conflicts" ]; then - error "Conflicting package(s) installed:$conflicts" - return 1 - fi - if [ -z "$install_deps" ] && [ -z "$recursive" ]; then # if we dont have any missing deps we are done now [ -z "$missing" ] && return 0 @@ -1687,7 +1676,11 @@ stripbin() { msg "Stripping binaries" scanelf --recursive --nobanner --etype "ET_DYN,ET_EXEC" . \ | sed -e 's:^ET_DYN ::' -e 's:^ET_EXEC ::' \ - | xargs -r ${CROSS_COMPILE}strip + | while read filename; do + XATTR=$(getfattr -d "${filename}") + ${CROSS_COMPILE}strip "${filename}" + [ -n "$XATTR" ] && (echo "$XATTR" | setfattr --restore=-) + done } # simply list target apks @@ -2004,6 +1997,7 @@ fi # if we want build debug package if [ -n "$DEBUG" ] || subpackage_types_has "dbg"; then CFLAGS="$CFLAGS -g" + CXXFLAGS="$CXXFLAGS -g" options="$options !strip" fi diff --git a/apkbuild-gem-resolver.in b/apkbuild-gem-resolver.in new file mode 100644 index 0000000..b93525e --- /dev/null +++ b/apkbuild-gem-resolver.in @@ -0,0 +1,302 @@ +#!/usr/bin/ruby + +# APKBUILD dependency resolver for RubyGems +# Copyright (C) 2014 Kaarle Ritvanen + +require 'augeas' +require 'optparse' +require 'rubygems/dependency' +require 'rubygems/resolver' +require 'rubygems/spec_fetcher' + +class Aport + RUBY_SUBPACKAGES = { + '2.0.0_p353' => { + 'ruby-minitest' => ['minitest', '4.3.2'], + 'ruby-rake' => ['rake', '0.9.6'], + 'ruby-rdoc' => ['rdoc', '4.0.0', 'ruby-json'] + }, + '2.0.0_p481' => { + 'ruby-minitest' => ['minitest', '4.3.2'], + 'ruby-rake' => ['rake', '0.9.6'], + 'ruby-rdoc' => ['rdoc', '4.0.0', 'ruby-json'] + }, + '2.1.5' => { + 'ruby-json' => ['json', '1.8.1'], + 'ruby-minitest' => ['minitest', '4.7.5'], + 'ruby-rake' => ['rake', '10.1.0'], + 'ruby-rdoc' => ['rdoc', '4.1.0', 'ruby-json'] + }, + '2.2.1' => { + 'ruby-json' => ['json', '1.8.1'], + 'ruby-minitest' => ['minitest', '5.4.3'], + 'ruby-rake' => ['rake', '10.4.2'], + 'ruby-rdoc' => ['rdoc', '4.2.0', 'ruby-json'], + 'ruby-io-console' => [ 'io-console', '0.4.2'] # its actually 0.4.3 but + # that version is not + # published on network + } + } + + @@aports = {} + @@subpackages = [] + + def self.initialize testing + Augeas::open(nil, nil, Augeas::NO_MODL_AUTOLOAD) do |aug| + dir = Dir.pwd + aug.transform(:lens => 'Shellvars.lns', :incl => dir + '/*/ruby*/APKBUILD') + aug.load + + apath = '/files' + dir + fail if aug.match("/augeas#{apath}//error").length > 0 + + repos = ['main'] + repos << 'testing' if testing + for repo in repos + for aport in aug.match "#{apath}/#{repo}/*" + FileAport.new(aug, aport) unless aport.end_with? '/ruby' + end + end + + for name, attrs in RUBY_SUBPACKAGES[ + aug.get("#{apath}/main/ruby/APKBUILD/pkgver") + ] + gem, version, *deps = attrs + aport = new name, gem, version + for dep in deps + aport.add_dependency dep + end + @@subpackages << aport + end + end + + @@aports.each_value do |aport| + aport.depends do |dep| + dep.add_user aport + end + end + end + + def self.get name + aport = @@aports[name] + raise 'Invalid package name: ' + name unless aport + aport + end + + def self.ruby_subpkgs + for pkg in @@subpackages + yield pkg + end + end + + def initialize name, gem, version + @name = name + @gem = gem + @version = version + @depends = [] + @users = [] + @@aports[name] = self + end + + def add_dependency name + @depends << name + end + + attr_reader :gem, :name, :version + + def depends + for dep in @depends + unless @@aports.has_key? dep + raise "Dependency for #{@name} does not exist: #{dep}" + end + yield @@aports[dep] + end + end + + def users + for user in @users + yield user + end + end + + def add_user user + @users << user + end +end + +class FileAport < Aport + def initialize aug, path + name = path.split('/')[-1] + + get = proc{ |param| + res = aug.get(path + '/APKBUILD/' + param) + raise param + ' not defined for ' + name unless res + res + } + + super name, get.call('_gemname'), get.call('pkgver') + + for dep in `echo #{get.call('depends')}`.split + # ruby-gems: workaround for v2.6 + add_dependency dep if dep.start_with?('ruby-') && dep != 'ruby-gems' + end + end +end + + +class Update + def initialize + @gems = {} + @deps = [] + end + + def require_version name, version + gem = assign(Aport.get(name).gem, name) + @deps << gem.dependency if gem.require_version version + end + + def resolve + Aport.ruby_subpkgs do |pkg| + require_version pkg.name, pkg.version unless @gems[pkg.gem] + end + + def check_deps + @gems.clone.each_value do |gem| + gem.check_deps + end + end + + check_deps + + for req in Gem::Resolver.new(@deps).resolve + spec = req.spec + gem = @gems[spec.name] + gem.require_version spec.version.version if gem + end + + check_deps + + for name, gem in @gems + if gem.updated? + gem.aport.users do |user| + ugem = @gems[user.gem] + if !ugem || ugem.aport.name != user.name + Gem::Resolver.new( + [gem.dependency, Gem::Dependency.new(user.gem, user.version)] + ).resolve + end + end + end + end + end + + def each + @gems.each_value do |gem| + obs = gem.obsolete_deps + obs = obs.length == 0 ? nil : " (obsolete dependencies: #{obs.join ', '})" + + if gem.updated? || obs + yield "#{gem.aport.name}-#{gem.version}#{obs}" + end + end + end + + def assign name, aport + aport = Aport.get aport + + if @gems.has_key? name + gem = @gems[name] + return gem if aport == gem.aport + raise "Conflicting packages for gem #{name}: #{gem.aport.name} and #{aport.name}" + end + + gem = AportGem.new self, name, aport + @gems[name] = gem + gem + end + + private + + class AportGem + def initialize update, name, aport + @update = update + @name = name + @aport = aport + end + + attr_reader :aport, :obsolete_deps + + def require_version version + if @version + return false if version == @version + raise "Conflicting versions for gem #{@name}: #{@version} and #{version}" + end + @version = version + true + end + + def version + @version || @aport.version + end + + def updated? + version != @aport.version + end + + def dependency + Gem::Dependency.new(@name, version) + end + + def check_deps + specs, errors = Gem::SpecFetcher::fetcher.spec_for_dependency(dependency) + raise "Invalid gem: #{@name}-#{version}" if specs.length == 0 + fail if specs.length > 1 + deps = specs[0][0].runtime_dependencies + + @obsolete_deps = [] + + @aport.depends do |dep| + gem = @update.assign(dep.gem, dep.name) + gem.check_deps + unless deps.reject! { |sdep| sdep.match? dep.gem, gem.version } + @obsolete_deps << dep.name + end + end + + if deps.length > 0 + raise 'Undeclared dependencies in ' + @aport.name + deps.inject('') { + |s, dep| "#{s}\n#{dep.name} #{dep.requirements_list.join ' '}" + } + end + end + end +end + + +testing = false +OptionParser.new do |opts| + opts.on('-t', '--testing') do |t| + testing = t + end +end.parse! ARGV +Aport.initialize testing + +latest = {} +for source, gems in Gem::SpecFetcher::fetcher.available_specs(:latest)[0] + for gem in gems + latest[gem.name] = gem.version.version + end +end + +update = Update.new +for arg in ARGV + match = /^(([^-]|-[^\d])+)(-(\d.*))?/.match arg + name = match[1] + update.require_version name, match[4] || latest[Aport.get(name).gem] +end + +update.resolve + +for aport in update + puts aport +end