From 41d54be72370d9fc4d93db1afae3422e6743288b Mon Sep 17 00:00:00 2001 From: Rasmus Thomsen Date: Tue, 28 Apr 2020 22:47:05 +0200 Subject: [PATCH] alpine: add backend fixes #73 --- src/asgen/backends/alpinelinux/apkpkg.d | 129 +++++++++++++ src/asgen/backends/alpinelinux/apkpkgindex.d | 183 +++++++++++++++++++ src/asgen/backends/alpinelinux/package.d | 23 +++ src/asgen/config.d | 8 +- src/asgen/engine.d | 4 + src/asgen/meson.build | 4 + 6 files changed, 350 insertions(+), 1 deletion(-) create mode 100644 src/asgen/backends/alpinelinux/apkpkg.d create mode 100644 src/asgen/backends/alpinelinux/apkpkgindex.d create mode 100644 src/asgen/backends/alpinelinux/package.d diff --git a/src/asgen/backends/alpinelinux/apkpkg.d b/src/asgen/backends/alpinelinux/apkpkg.d new file mode 100644 index 00000000..a59c38fc --- /dev/null +++ b/src/asgen/backends/alpinelinux/apkpkg.d @@ -0,0 +1,129 @@ +/* + * Copyright (C) 2020 Rasmus Thomsen + * + * Based on the archlinux backend, which is: + * Copyright (C) 2016 Matthias Klumpp + * + * Licensed under the GNU Lesser General Public License Version 3 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the license, or + * (at your option) any later version. + * + * This software 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this software. If not, see . + */ + +module asgen.backends.alpinelinux.apkpkg; + +import std.stdio; +import std.string; +import std.array : empty; + +import asgen.logging; +import asgen.zarchive; +import asgen.backends.interfaces; + +final class AlpinePackage : Package +{ +private: + string pkgname; + string pkgver; + string pkgarch; + string pkgmaintainer; + string[string] desc; + string pkgFname; + + string[] contentsL; + + ArchiveDecompressor archive; + +public: + override @property string name () const + { + return this.pkgname; + } + + @property void name (string val) + { + this.pkgname = val; + } + + override @property string ver () const + { + return this.pkgver; + } + + @property void ver (string val) + { + this.pkgver = val; + } + + override @property string arch () const + { + return this.pkgarch; + } + + @property void arch(string val) + { + this.pkgarch = val; + } + + override @property const(string[string]) description () const + { + return this.desc; + } + + @property void filename (string fname) + { + this.pkgFname = fname; + } + + override @property string getFilename () const + { + return pkgFname; + } + + override @property string maintainer () const + { + return this.pkgmaintainer; + } + + @property void maintainer (string maint) + { + this.pkgmaintainer = maint; + } + + void setDescription (string text, string locale) + { + this.desc[locale] = text; + } + + override const(ubyte)[] getFileData (string fname) + { + if (!this.archive.isOpen()) + this.archive.open(this.getFilename); + + return this.archive.readData(fname); + } + + @property override string[] contents () + { + return this.contentsL; + } + + @property void contents (string[] c) + { + this.contentsL = c; + } + + override void finish () + { + } +} diff --git a/src/asgen/backends/alpinelinux/apkpkgindex.d b/src/asgen/backends/alpinelinux/apkpkgindex.d new file mode 100644 index 00000000..e4815842 --- /dev/null +++ b/src/asgen/backends/alpinelinux/apkpkgindex.d @@ -0,0 +1,183 @@ +/* + * Copyright (C) 2020 Rasmus Thomsen + * + * Licensed under the GNU Lesser General Public License Version 3 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the license, or + * (at your option) any later version. + * + * This software 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this software. If not, see . + */ + +module asgen.backends.alpinelinux.apkpkgindex; + +import std.algorithm : canFind, filter, endsWith, remove; +import std.array : appender, join, split; +import std.conv : to; +import std.exception : enforce; +import std.file : dirEntries, exists, SpanMode; +import std.format : format; +import std.path : baseName, buildPath; +import std.range : empty; +import std.string : splitLines, startsWith, strip; +import std.utf : UTFException, validate; + +import asgen.logging; +import asgen.zarchive; +import asgen.utils : escapeXml; +import asgen.backends.interfaces; +import asgen.backends.alpinelinux.apkpkg; + +final class AlpinePackageIndex : PackageIndex +{ + +private: + string rootDir; + Package[][string] pkgCache; + +public: + + this (string dir) + { + enforce (exists (dir), format ("Directory '%s' does not exist.", dir)); + this.rootDir = dir; + } + + override void release () + { + pkgCache = null; + } + + private void setPkgDescription (AlpinePackage pkg, string pkgDesc) + { + if (pkgDesc is null) + return; + + auto desc = "

%s

".format (pkgDesc.escapeXml); + pkg.setDescription (desc, "C"); + } + + private void setPkgValues (ref AlpinePackage pkg, string[] keyValueString) + { + immutable key = keyValueString[0].strip (); + immutable value = keyValueString[1].strip (); + + switch (key) { + case "pkgname": + pkg.name = value; + break; + case "pkgver": + pkg.ver = value; + break; + case "arch": + pkg.arch = value; + break; + case "maintainer": + pkg.maintainer = value; + break; + case "pkgdesc": + setPkgDescription(pkg, value); + break; + default: + // We don't care about other entries + break; + } + } + + private Package[] loadPackages (string suite, string section, string arch) + { + auto apkRootPath = buildPath (rootDir, suite, section, arch); + ArchiveDecompressor ad; + AlpinePackage[string] pkgsMap; + + foreach (packageArchivePath; dirEntries (apkRootPath, SpanMode.shallow).filter!( + f => f.name.endsWith (".apk"))) { + auto fileName = packageArchivePath.baseName (); + AlpinePackage pkg; + if (fileName in pkgsMap) { + pkg = pkgsMap[fileName]; + } else { + pkg = new AlpinePackage (); + pkgsMap[fileName] = pkg; + } + + ad.open (packageArchivePath); + auto pkgInfoData = cast(string) ad.readData (".PKGINFO"); + + try { + validate (pkgInfoData); + } + catch (UTFException e) { + logError ("PKGINFO file in archive %s contained invalid UTF-8, skipping!", + packageArchivePath); + continue; + } + + pkg.filename = packageArchivePath; + auto lines = pkgInfoData.splitLines (); + // If the current line doesn't contain a = it's meant to extend the previous line + string[] completePair; + foreach (currentLine; lines) { + if (currentLine.canFind ("=")) { + if (completePair.empty ()) { + completePair = [currentLine]; + continue; + } + + this.setPkgValues (pkg, completePair.join (" ").split ("=")); + completePair = [currentLine]; + } else if (!currentLine.startsWith ("#")) { + completePair ~= currentLine.strip ().split ("#")[0]; + } + } + // We didn't process the last line yet + this.setPkgValues (pkg, completePair.join (" ").split ("=")); + pkg.contents = ad.readContents ().remove!("a == \".PKGINFO\" || a.startsWith (\".SIGN\")"); + } + + // perform a sanity check, so we will never emit invalid packages + auto pkgs = appender!(Package[]); + if (pkgsMap.length > 20) + pkgs.reserve ((pkgsMap.length.to!long - 10).to!size_t); + foreach (ref pkg; pkgsMap.byValue ()) + if (pkg.isValid) + pkgs ~= pkg; + else + logError ("Found an invalid package (name, architecture or version is missing). This is a bug."); + + return pkgs.data; + } + + override Package[] packagesFor (string suite, string section, string arch, bool withLongDescs = true) + { + immutable id = "%s-%s-%s".format (suite, section, arch); + if (id !in pkgCache) { + auto pkgs = loadPackages (suite, section, arch); + synchronized (this) + pkgCache[id] = pkgs; + } + + return pkgCache[id]; + } + + Package packageForFile (string fname, string suite = null, string section = null) + { + // Alpine currently doesn't have a way other than querying a web API + // to tell what package owns a file, unless that packages is installed + // on the system. + return null; + } + + bool hasChanges (DataStore dstore, string suite, string section, string arch) + { + return true; + } +} diff --git a/src/asgen/backends/alpinelinux/package.d b/src/asgen/backends/alpinelinux/package.d new file mode 100644 index 00000000..baf09b0e --- /dev/null +++ b/src/asgen/backends/alpinelinux/package.d @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2020 Rasmus Thomsen + * + * Licensed under the GNU Lesser General Public License Version 3 + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the license, or + * (at your option) any later version. + * + * This software 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 Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this software. If not, see . + */ + +module asgen.backends.alpinelinux; + +public import asgen.backends.alpinelinux.apkpkg; +public import asgen.backends.alpinelinux.apkpkgindex; diff --git a/src/asgen/config.d b/src/asgen/config.d index 622e57ba..c2976904 100644 --- a/src/asgen/config.d +++ b/src/asgen/config.d @@ -69,7 +69,8 @@ enum Backend Debian, Ubuntu, Archlinux, - RpmMd + RpmMd, + Alpinelinux } /** @@ -404,6 +405,11 @@ public: this.backend = Backend.RpmMd; this.metadataType = DataType.XML; break; + case "alpinelinux": + this.backendName = "Alpine Linux"; + this.backend = Backend.Alpinelinux; + this.metadataType = DataType.XML; + break; default: break; } diff --git a/src/asgen/engine.d b/src/asgen/engine.d index 30b70b4c..4456b767 100644 --- a/src/asgen/engine.d +++ b/src/asgen/engine.d @@ -47,6 +47,7 @@ import asgen.backends.debian; import asgen.backends.ubuntu; import asgen.backends.archlinux; import asgen.backends.rpmmd; +import asgen.backends.alpinelinux; import asgen.handlers : IconHandler, LocaleHandler; @@ -89,6 +90,9 @@ public: case Backend.RpmMd: pkgIndex = new RPMPackageIndex (conf.archiveRoot); break; + case Backend.Alpinelinux: + pkgIndex = new AlpinePackageIndex (conf.archiveRoot); + break; default: throw new Exception ("No backend specified, can not continue!"); } diff --git a/src/asgen/meson.build b/src/asgen/meson.build index 723d2230..afd3ab1d 100644 --- a/src/asgen/meson.build +++ b/src/asgen/meson.build @@ -63,6 +63,10 @@ backend_sources = [ 'backends/dummy/dummypkg.d', 'backends/dummy/pkgindex.d', + 'backends/alpinelinux/package.d', + 'backends/alpinelinux/apkpkg.d', + 'backends/alpinelinux/apkpkgindex.d', + 'backends/archlinux/package.d', 'backends/archlinux/alpkg.d', 'backends/archlinux/alpkgindex.d',