Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

alpine: add backend #75

Merged
merged 1 commit into from May 12, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
@@ -0,0 +1,129 @@
/*
* Copyright (C) 2020 Rasmus Thomsen <oss@cogitri.dev>
*
* Based on the archlinux backend, which is:
* Copyright (C) 2016 Matthias Klumpp <matthias@tenstral.net>
*
* 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 <http://www.gnu.org/licenses/>.
*/

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 ()
{
}
}
@@ -0,0 +1,183 @@
/*
* Copyright (C) 2020 Rasmus Thomsen <oss@cogitri.dev>
*
* 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 <http://www.gnu.org/licenses/>.
*/

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;
Cogitri marked this conversation as resolved.
Show resolved Hide resolved

auto desc = "<p>%s</p>".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;
}
}
@@ -0,0 +1,23 @@
/*
* Copyright (C) 2020 Rasmus Thomsen <oss@cogitri.dev>
*
* 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 <http://www.gnu.org/licenses/>.
*/

module asgen.backends.alpinelinux;

public import asgen.backends.alpinelinux.apkpkg;
public import asgen.backends.alpinelinux.apkpkgindex;
@@ -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;
}
@@ -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!");
}
@@ -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',