diff options
author | Brian Dolbec <dolsen@gentoo.org> | 2017-03-22 04:29:12 -0700 |
---|---|---|
committer | Brian Dolbec <dolsen@gentoo.org> | 2017-03-22 04:35:11 -0700 |
commit | 81c73cae27a5602fbe4134e47de2663af85f1e99 (patch) | |
tree | a2fd252c1ede8c49bfc09ffa1260049808205496 | |
parent | revdep_rebuild/assign.py: handle directory symlinks (bug 611808) (diff) | |
download | gentoolkit-81c73cae27a5602fbe4134e47de2663af85f1e99.tar.gz gentoolkit-81c73cae27a5602fbe4134e47de2663af85f1e99.tar.bz2 gentoolkit-81c73cae27a5602fbe4134e47de2663af85f1e99.zip |
Initial import of remaining gentoolkit-dev packages
Unable to merge due to confilts in history, removed gnetoolkit paths in gentoolkit-dev...
So do a basic new files commit instead.
For the previous history of the different packages, refer to the gentoolkit-dev branch.
25 files changed, 2565 insertions, 0 deletions
diff --git a/AUTHORS.gentoolkit-dev b/AUTHORS.gentoolkit-dev new file mode 100644 index 0000000..ca985ba --- /dev/null +++ b/AUTHORS.gentoolkit-dev @@ -0,0 +1,6 @@ +Christian Ruppert <idl0r@gentoo.org> +Paul Varner <fuzzyray@gentoo.org> +Karl Trygve Kalleberg <karltk@gentoo.org> + +See the AUTHOR file in the various src/<foo> subdirectories for a full +log of who's done what with whome and when. diff --git a/ebump/AUTHORS b/ebump/AUTHORS new file mode 100644 index 0000000..0cf8ad8 --- /dev/null +++ b/ebump/AUTHORS @@ -0,0 +1,4 @@ +Christian Ruppert <idl0r@gentoo.org> + +Original author: +Karl Trygve Kalleberg <karltk@gentoo.org> diff --git a/ebump/ChangeLog b/ebump/ChangeLog new file mode 100644 index 0000000..4434b94 --- /dev/null +++ b/ebump/ChangeLog @@ -0,0 +1,8 @@ +2004-06-21 Karl Trygve Kalleberg <karltk@gentoo.org> + * Fixed handling of deletion. + +2004-03-11 Karl Trygve Kalleberg <karltk@gentoo.org> + * Fixed incorrect cut'ing of wc -l output when updating ChangeLog + +2004-02-08 Karl Trygve Kalleberg <karltk@gentoo.org> + * Initial import diff --git a/ebump/Makefile b/ebump/Makefile new file mode 100644 index 0000000..61afab3 --- /dev/null +++ b/ebump/Makefile @@ -0,0 +1,20 @@ +# Copyright 2004 Karl Trygve Kalleberg <karltk@gentoo.org> +# Copyright 2004 Gentoo Technologies, Inc. +# Distributed under the terms of the GNU General Public License v2 +# +# $Header$ + +include ../../makedefs.mak + +.PHONY: all +all: + +dist: + mkdir -p ../../$(DISTDIR)/src/ebump/ + cp Makefile AUTHORS README ChangeLog ebump ebump.1 ../../$(DISTDIR)/src/ebump/ + +install: all + install -m 0755 ebump $(BINDIR)/ + install -d $(DOCDIR)/ebump + install -m 0644 AUTHORS README ChangeLog $(DOCDIR)/ebump/ + install -m 0644 ebump.1 $(MAN1DIR)/ diff --git a/ebump/README b/ebump/README new file mode 100644 index 0000000..f13592e --- /dev/null +++ b/ebump/README @@ -0,0 +1,18 @@ + +Package : ebump +Version : 0.1.1 +Author : See AUTHORS + +MOTIVATION + +The ebump utility is a Gentoo-specific tool for bumping the revision of +a given ebuild and auxiliary files in the Portage tree. It is only +useful for Gentoo developers with CVS commit access. + +MECHANICS + +N/A + +IMPROVEMENTS + +N/A diff --git a/ebump/ebump b/ebump/ebump new file mode 100755 index 0000000..47ffd86 --- /dev/null +++ b/ebump/ebump @@ -0,0 +1,389 @@ +#! /bin/sh +# Copyright (c) 2004 Karl Trygve Kalleberg <karltk@gentoo.org> +# Copyright 1999-2010 Gentoo Foundation +# Distributed under the terms of the GNU General Public License v2 + +__version__="0.1.1" +__author__="Karl Trygve Kalleberg" +__email__="<karltk@gentoo.org>" +__description__="Ebuild version bumping tool" + + + +die() { + echo $1 >&2 + exit -1 +} + +einfo() { + if [ ${opt_verbosity:-0} -eq 1 ] ; then + echo $* + fi +} + +print_version() { + echo "${__description__}, v${__version__}" + echo "Copyright (c) 2004 ${__author__} ${__email__}" + echo "Copyright 1999-2010 Gentoo Foundation" + echo "Distributed under the terms of the GNU General Public License v2" +} + +print_usage() { + echo "Usage: ebump <options> foo<.ebuild>" + echo "Ebuild version bumping tool, v${__version__}" + echo " -V|--version show version info" + echo " -v|--verbose increase verbosity" + echo " -q|--quiet turn off output" + echo " -a|--no-auxfiles don't bump auxfiles (files/*)" + echo " -c|--no-changelog do not update ChangeLog (via echangelog)" + echo " -C|--no-vcs do not add to VCS" + echo " -m|--message append message to ChangeLog" + echo " -d|--delete-old delete previous revision from VCS (DANGEROUS!)" +} + +# +# Load options from /etc/gentoolkit/ebump.conf and ${HOME}/.gentoo/ebump.conf +# Home directory file takes precedence. +# +load_options() { + # FIXME: Sourcing config files like this is really a bad idea; users may + # easily override any function in this program inside his config files. + if [ -f "/etc/gentoolkit/ebump.conf" ] ; then + . /etc/gentoolkit/ebump.conf + fi + if [ -f "${HOME}/.gentoo/gentool-env" ] ; then + . ${HOME}/.gentoo/gentool-env + fi + if [ -f "${HOME}/.gentoo/ebump.conf" ] ; then + . ${HOME}/.gentoo/ebump.conf + fi + + # FIXME: remove this warning in 2-3 releases. + if [ -n "${opt_add_cvs}" ]; then + echo "Warning: opt_add_cvs is deprecated, please use opt_add_vcs from now on!" >&2 + fi +} + +# +# Find closes ebuild to ${1}, if any +# +find_ebuild() { + local f=${1} + + if [ -f "${f}" ] ; then + echo ${f} + fi + + if [ -f "${f}.ebuild" ] ; then + echo ${f} + fi +} + +# +# splitname (version|name|revision) package-name-version-revision +# +splitname() { + case $1 in + version) + echo ${2} | sed -r "s/.*-([0-9].*)/\1/" + ;; + name) + name=$(echo ${2} | sed -r "s/(.*)-[0-9].*/\1/") + if [ ${name} = ${2} ] ; then + if [ $(echo ${2} | grep "^[0-9].*") ] ; then + # The filename starts with a version number, thus it has no + # name + name="" + else + # The filename doesn't have a recognizeable version number; + # everything is a name + name=${2} + fi + fi + echo ${name} + ;; + revision) + rev=$(echo ${2} | sed -r "s/.*-r([0-9][0-9]*)/\1/") + if [ ${rev} = ${2} ] ; then + rev=0 + fi + echo ${rev} + ;; + vernorev) + ver=$(echo ${2} | sed -r "s/.*-([0-9].*)-r[0-9]+/\1/") + if [ ${ver} = ${2} ] ; then + ver=$(echo ${2} | sed -r "s/.*-([0-9].*)/\1/") + fi + echo ${ver} + ;; + *) + echo + ;; + esac +} + +process_ebuild() { + local vcs=$1 + shift + local ebuild_arg="${*}" + shift $# + + # Files to add to VCS + local addfiles="" + # Files to remove from VCS + local delfiles="" + + if [ -z "${ebuild_arg}" ] ; then + print_usage + exit + fi + + for ebuild in $ebuild_arg; do + # + # Try to find a matching ebuild + # + local ebuild_name=$(find_ebuild ${ebuild}) + if [ -z "${ebuild_name}" ] ; then + die "Could not find ${ebuild}" + fi + + einfo "Processing ebuild ${ebuild_name}" + + # + # Bump revision suffix (or add one) + # + local PF=$(basename ${ebuild_name} .ebuild) + local PN=$(splitname name ${PF}) + local PV=$(splitname version ${PF}) + local rev=$(splitname revision ${PF}) + local PV_norev=$(splitname vernorev ${PF}) + local newPF=${PN}-${PV_norev}-r$((rev+1)) + +# echo $PF / $PN / $PV / $rev / $PV_norev / $newPF + + einfo "Bumped ${PF}.ebuild to ${newPF}.ebuild" + + if [ "${vcs}" = "svn" ]; then + svn cp ${PF}.ebuild ${newPF}.ebuild + else + cp ${PF}.ebuild ${newPF}.ebuild + fi + + einfo "Reset keywords to ~arch" + + ekeyword '~all' "${newPF}.ebuild" + + addfiles="${addfiles} ${newPF}.ebuild" + delfiles="${delfiles} ${PF}.ebuild" + + # + # (Optional) Bump relevant files in files/ + # + if [ "${opt_bump_auxfiles}" = "y" ] ; then + # Gather list of auxiliary files in files/ that has a versioned + # filename, where the version matches our current version. + local bumplist="" + for x in $(echo files/*) ; do + if [ ! -z "$(echo $x | grep "${PV}$")" ] ; then + bumplist="${bumplist} ${x}" + fi + done + + # Bump version of all matches + for x in ${bumplist} ; do + local bn=$(basename ${x}) + local dn=$(dirname ${x}) + local newbn + + PN=$(splitname name ${bn}) + PV=$(splitname version ${bn}) + rev=$(splitname revision ${bn}) + PV_norev=$(splitname vernorev ${bn}) + +# echo $PN / ${PV_norev} / ${rev} + + # Special case for when we have no name part; filename + # is just a version number + if [ -z "${PN}" ] ; then + newbn=${PV_norev}-r$((rev+1)) + else + newbn=${PN}-${PV_norev}-r$((rev+1)) + fi + + if [ -d ${dn}/${bn} ] ; then + if [ -e ${dn}/${newbn} ] ; then + echo "Directory ${dn}/${newbn} exists, not copying" >&2 + else + cp -a ${dn}/${bn} ${dn}/${newbn} + # uhm, is that necessary? +# find ${dn}/${newbn} -name CVS | xargs rm -rf + fi + else + cp ${dn}/${bn} ${dn}/${newbn} + fi + + addfiles="${addfiles} ${dn}/${newbn}" + delfiles="${delfiles} ${dn}/${bn}" + + einfo "Bumped ${dn}/${bn} to ${dn}/${newbn}" + done + fi + done + +# echo "addfiles ${addfiles}" +# echo "delfiles ${delfiles}" + + # + # (Optional) Add VCS entry for all new files + # + if [ "${opt_add_vcs}" = "y" ] ; then +# for x in ${addfiles} ; do +# if [ -d ${x} ] ; then +# find ${x} -exec ${vcs} add {} ';' +# else +# ${vcs} add ${x} +# fi +# done + $vcs add $addfiles + einfo "Added ${addfiles} to VCS" + fi + + + # + # (Optional) Delete previous entry + # + # Could we use 'rm' instead of remove for all vcs? + if [ "${opt_delete_old}" = "y" ] ; then +# for x in ${delfiles} ; do +# if [ "${vcs}" = "cvs" ]; then +# ${vcs} remove -f ${x} +# elif [ "${vcs}" = "git" ]; then +# ${vcs} rm ${x} +# else +# ${vcs} remove ${x} +# fi +# done + if [ "${vcs}" = "cvs" ]; then + $vcs remove -f $delfiles + elif [ "${vcs}" = "git" ]; then + $vcs rm $delfiles + else + $vcs remove $delfiles + fi + einfo "Removed ${delfiles} from VCS" + fi + + # + # (Optional) Add ChangeLog entry + # + if [ "${opt_add_changelog}" = "y" ] && [ "${opt_add_vcs}" = "y" ]; then + # FIXME: remove this warning in 2-3 releases + if [ -n "${AUTHORNAME}" ] || [ -n "${AUTHOREMAIL}" ]; then + echo "Warning: AUTHORNAME and AUTHOREMAIL is deprecated!" >&2 + echo "Please take a look at echangelog(1)." >&2 + echo "To avoid this warning unset AUTHORNAME and AUTHOREMAIL." >&2 + fi + + echangelog "${opt_commitmessage}" || set $? + + if [ ${1:-0} -ne 0 ]; then + einfo "Modifying ChangeLog failed!" + else + einfo "Added ChangeLog entry" + fi + fi +} + +get_vcs() { + if [ -d "CVS" ]; then + echo "cvs" + return 0 + elif [ -d ".svn" ]; then + echo "svn" + return 0 + else + if [ -x "$(which git)" ]; then + if [ -n "$(git rev-parse --git-dir 2>/dev/null)" ]; then + echo "git" + return 0 + fi + fi + + echo + return 1 + fi +} + +# +# Global options +# +opt_verbosity=0 +opt_add_changelog=y +opt_add_vcs=y +opt_bump_auxfiles=y +opt_delete_old=n +opt_commitmessage="" + +load_options + +while [ ${#} -gt 0 ] ; do + arg=${1} + shift + + case ${arg} in + -h|--help) + print_usage + exit 0 + ;; + -m|--message) + opt_commitmessage="${1}" + shift + continue + ;; + -a|--no-auxfiles) + opt_bump_auxfiles=n + continue + ;; + -c|--no-changelog) + opt_add_changelog=n + continue + ;; + -C|--no-vcs) + opt_add_vcs=n + continue + ;; + -V|--version) + print_version + exit + ;; + -v|--verbose) + opt_verbosity=1 + continue + ;; + -q|--quiet) + opt_verbosity=0 + continue + ;; + -d|--delete-old) + opt_delete_old=y + continue + ;; + *) + ebuild_arg="${ebuild_arg:+${ebuild_arg} }${arg}" + continue + ;; + esac +done + +vcs=$(get_vcs) +if [ -z "${vcs}" ]; then + echo "Warning: no cvs, git or svn repository found!" >&2 + echo "Changes can't be added to the VCS" >&2 + opt_add_vcs=n + opt_delete_old=n +fi + +process_ebuild "${vcs}" $ebuild_arg + +# TODO: +# - put cli parser into separate functions diff --git a/ebump/ebump.1 b/ebump/ebump.1 new file mode 100644 index 0000000..b1e473d --- /dev/null +++ b/ebump/ebump.1 @@ -0,0 +1,126 @@ +.TH "ebump" "1" "0.1.1" "Gentoolkit" "Gentoo Administration" +.SH "NAME" +.LP +ebump \- Gentoo: Ebuild revision bumper +.SH "SYNTAX" +.LP +ebump [\fIoption\fP] <\fIpackage-name[-version]\fP> + +.SH "DESCRIPTION" + +.LP +\fIebump\fR bumps the revision of a particular ebuild, and all auxiliary +files in the files/ directory that have a matching version suffix. + +.LP +By default, the all new revision files will be added to the VCS. + +.LP +You must stand in the directory of the ebuild to be bumped. + +.SH "OPTIONS" +.LP +\fB\-C\fR +.br +\fB--no-vcs\fB +.IP +Do not add new files to VCS. + +.LP +\fB\-V\fR +.br +\fB--version\fB +.IP +Display version information and exit. + +.LP +\fB\-v\fR +.br +\fB--verbose\fB +.IP +Increase verbosity level. May be used more than once. + +.LP +\fB\-q\fR +.br +\fB--quiet\fB +.IP +Do not output any non-essential information. + +.LP +\fB\-a\fR +.br +\fB--no-auxfiles\fB +.IP +don't bump auxfiles (files/*) + +.LP +\fB\-c\fR +.br +\fB--no-changelog\fB +.IP +do not update ChangeLog (via echangelog) + +.LP +\fB\-m\fR <\fIChangeLog text\fR> +.br +\fB\--message\fR <\fIChangeLog text\fR> +.IP +Specifies the message to add to the ChangeLog, instead of the standard +placeholder. + +.LP +\fB\-d\fR +.br +\fB\--delete-old\fR +.IP +Delete old revision and old auxiliary files from VCS. This is +\fIdangerous\fR and should only be used if you know exactly what you are +doing, because +.br +1) the old revision may be stable on a different architecture than the one you +are working on. +.br +2) the auxiliary files may be required by other versions of the ebuild. +.br +3) the new revision should usually undergo a period of testing before being marked stable. + +.SH "CONFIGURATION" + +.LP +\fB/etc/gentoolkit/ebump.conf\fR +.br +\fB~/.gentoo/ebump.conf\fR +.IP +From these files, \fIebump\fR will load the settings +.br +\fBopt_verbosity\fR (default \fI1\fR) - verbosity level 0-10 +.br +\fBopt_add_changelog\fR (default \fIy\fR) - add entry in ChangeLog +.br +\fBopt_add_vcs\fR (default \fIy\fR) - add new files to VCS +.br +\fBopt_bump_auxfiles\fR (default \fIy\fR) - bump auxiliary files in files/ +.br +\fBopt_delete_old\fR (default \fIn\fR) - delete old revision (DANGEROUS!) +.br +\fBopt_commitmessage\fR (default \fI""\fR) - default ChangeLog message + +.LP +\fB(DEPRECATED)\fR +.br +\fB~/.gentoo/gentool-env\fR +.IR +From this file, \fIebump\fR will load the env vars \fBAUTHORNAME\fR and +\fBAUTHOREMAIL\fR, which are used to generate proper ChangeLog entries. + +.SH "SEE ALSO" +.LP +The rest of the utilities in \fIapp-portage/gentoolkit-dev\fR, such as +\fIechangelog(1)\fR and \fIekeyword(1)\fR. + +.SH "AUTHORS" +.LP +Karl Trygve Kalleberg <karltk@gentoo.org> +.br +Christian Ruppert <idl0r@gentoo.org> diff --git a/pym/gentoolkit/ekeyword/.pylintrc b/pym/gentoolkit/ekeyword/.pylintrc new file mode 100644 index 0000000..cd5b31e --- /dev/null +++ b/pym/gentoolkit/ekeyword/.pylintrc @@ -0,0 +1,36 @@ +[MESSAGES CONTROL] +# Disable the message, report, category or checker with the given id(s). You +# can either give multiple identifier separated by comma (,) or put this option +# multiple times (only on the command line, not in the configuration file where +# it should appear only once). +disable= + missing-docstring, + too-many-lines, + too-many-branches, + too-many-statements, + too-few-public-methods, + too-many-instance-attributes, + too-many-public-methods, + too-many-locals, + too-many-arguments, + locally-enabled, + locally-disabled, + fixme, + bad-continuation, + invalid-name, + +[REPORTS] +reports=no + +[FORMAT] +max-line-length=80 +indent-string='\t' + +[SIMILARITIES] +min-similarity-lines=20 + +[VARIABLES] +dummy-variables-rgx=_ + +[DESIGN] +max-parents=10 diff --git a/pym/gentoolkit/ekeyword/AUTHORS b/pym/gentoolkit/ekeyword/AUTHORS new file mode 100644 index 0000000..353a45e --- /dev/null +++ b/pym/gentoolkit/ekeyword/AUTHORS @@ -0,0 +1,10 @@ +Current python version: + Mike Frysinger <vapier@gentoo.org> + +Previous perl version: + Christian Ruppert <idl0r@gentoo.org> + Paul Varner <fuzzyray@gentoo.org> + Mike Frysinger <vapier@gentoo.org> + +Original author: + Aron Griffis <agriffis@gentoo.org> diff --git a/pym/gentoolkit/ekeyword/Makefile b/pym/gentoolkit/ekeyword/Makefile new file mode 100644 index 0000000..cfa1a06 --- /dev/null +++ b/pym/gentoolkit/ekeyword/Makefile @@ -0,0 +1,20 @@ +# Copyright 2004 Karl Trygve Kalleberg <karltk@gentoo.org> +# Copyright 2004 Gentoo Technologies, Inc. +# Distributed under the terms of the GNU General Public License v2 +# +# $Header$ + +include ../../makedefs.mak + +.PHONY: all clean dist +all: + +dist: + mkdir -p ../../$(DISTDIR)/src/ekeyword + cp Makefile AUTHORS README ekeyword.py ekeyword_unittest.py \ + ../../$(DISTDIR)/src/ekeyword/ + +install: all + install -m 0755 ekeyword.py $(BINDIR)/ekeyword + install -d $(DOCDIR)/ekeyword + install -m 0644 AUTHORS README $(DOCDIR)/ekeyword/ diff --git a/pym/gentoolkit/ekeyword/README b/pym/gentoolkit/ekeyword/README new file mode 100644 index 0000000..b147e4a --- /dev/null +++ b/pym/gentoolkit/ekeyword/README @@ -0,0 +1,20 @@ +Package : ekeyword +Version : 1.0 +Author : See AUTHORS + +MOTIVATION + +Update the KEYWORDS in an ebuild. + +MECHANICS + +N/A + +IMPROVEMENTS + +- Should we allow users to pass in */-*/~*? +- Should we collapse multiple globs into one. +- Should we support multiline KEYWORDS values? No... +- Support autodetection of ~user homedir expansions. + e.g. If "arm" is a user, then "~arm" will be passed in as "/home/arm". + We should catch that and normalize it back to "~arm". diff --git a/pym/gentoolkit/ekeyword/ekeyword b/pym/gentoolkit/ekeyword/ekeyword new file mode 120000 index 0000000..8374306 --- /dev/null +++ b/pym/gentoolkit/ekeyword/ekeyword @@ -0,0 +1 @@ +ekeyword.py
\ No newline at end of file diff --git a/pym/gentoolkit/ekeyword/ekeyword.py b/pym/gentoolkit/ekeyword/ekeyword.py new file mode 100755 index 0000000..31225b0 --- /dev/null +++ b/pym/gentoolkit/ekeyword/ekeyword.py @@ -0,0 +1,538 @@ +#!/usr/bin/python +# Copyright 2014 Gentoo Foundation +# Distributed under the terms of the GNU General Public License v2 +# Written by Mike Frysinger <vapier@gentoo.org> + +"""Manage KEYWORDS in ebuilds easily. + +This tool provides a simple way to add or update KEYWORDS in a set of ebuilds. +Each command-line argument is processed in order, so that keywords are added to +the current list as they appear, and ebuilds are processed as they appear. + +Instead of specifying a specific arch, it's possible to use the word "all". +This causes the change to apply to all keywords presently specified in the +ebuild. + +The ^ leader instructs ekeyword to remove the specified arch. + +Examples: + + # Mark all existing arches in the ebuild as stable. + $ %(prog)s all foo-1.ebuild + + # Mark arm as stable and x86 as unstable. + $ %(prog)s arm ~x86 foo-1.ebuild + + # Mark hppa as unsupported (explicitly adds -hppa). + $ %(prog)s -hppa foo-1.ebuild + + # Delete alpha keywords from all ebuilds. + $ %(prog)s ^alpha *.ebuild + + # Mark sparc as stable for foo-1 and m68k as unstable for foo-2. + $ %(prog)s sparc foo-1.ebuild ~m68k foo-2.ebuild + + # Mark s390 as the same state as amd64. + $ %(prog)s s390=amd64 foo-1.ebuild +""" + +from __future__ import print_function + +import argparse +import collections +import difflib +import io +import os +import re +import subprocess +import sys + +import portage +from portage.output import colorize, nocolor + + +VERSION = '1.0 awesome' + +# Operation object that describes how to perform a change. +# Args: +# op: The operation to perform when |ref_arch| is not set: +# None: Mark |arch| stable +# '-': Mark |arch| as not applicable (e.g. -foo) +# '~': Mark |arch| as unstable (e.g. ~foo) +# '^': Delete |arch| so it isn't listed at all +# arch: The required arch to update +# ref_arch: Set |arch| status to this arch (ignoring |op|) +Op = collections.namedtuple('Op', ('op', 'arch', 'ref_arch')) + + +def warning(msg): + """Write |msg| as a warning to stderr""" + print('warning: %s' % msg, file=sys.stderr) + + +def keyword_to_arch(keyword): + """Given a keyword, strip it down to its arch value + + When an ARCH shows up in KEYWORDS, it may have prefixes like ~ or -. + Strip all that cruft off to get back to the ARCH. + """ + return keyword.lstrip('-~') + + +def sort_keywords(arches): + """Sort |arches| list in the order developers expect + + This is vaguely defined because it is kind of vaguely defined once you get + past the basic (Linux-only) keywords. + + Args: + arches: An iterable of ARCH values. + + Returns: + A sorted list of |arches| + """ + keywords = [] + + # Globs always come first. + for g in ('-*', '*', '~*'): + if g in arches: + arches.remove(g) + keywords.append(g) + + def arch_key(keyword): + """Callback for python sorting functions + + Used to turn a Gentoo keyword into a sortable form. + """ + # Sort independent of leading marker (~ or -). + arch = keyword_to_arch(keyword) + + # A keyword may have a "-" in it. We split on that and sort + # by the two resulting items. The part after the hyphen is + # the primary key. + if '-' in arch: + arch, plat = arch.split('-', 1) + else: + arch, plat = arch, '' + + return (plat, arch) + + keywords += sorted(arches, key=arch_key) + + return keywords + + +def diff_keywords(old_keywords, new_keywords, style='color-inline'): + """Show pretty diff between list of keywords + + Args: + old_keywords: The old set of KEYWORDS + new_keywords: The new set of KEYWORDS + style: The diff style + + Returns: + A string containing the diff output ready to shown to the user + """ + def show_diff(s): + output = '' + + for tag, i0, i1, j0, j1 in s.get_opcodes(): + + if tag == 'equal': + output += s.a[i0:i1] + + if tag in ('delete', 'replace'): + o = s.a[i0:i1] + if style == 'color-inline': + o = colorize('bg_darkred', o) + else: + o = '-{%s}' % o + output += o + + if tag in ('insert', 'replace'): + o = s.b[j0:j1] + if style == 'color-inline': + o = colorize('bg_darkgreen', o) + else: + o = '+{%s}' % o + output += o + + return output + + sold = str(' '.join(old_keywords)) + snew = str(' '.join(new_keywords)) + s = difflib.SequenceMatcher(str.isspace, sold, snew, autojunk=False) + return show_diff(s) + + +def process_keywords(keywords, ops, arch_status=None): + """Process |ops| for |keywords|""" + new_keywords = set(keywords).copy() + + # Process each op one at a time. + for op, oarch, refarch in ops: + # Figure out which keywords we need to modify. + if oarch == 'all': + if arch_status is None: + raise ValueError('unable to process "all" w/out profiles.desc') + old_arches = set([keyword_to_arch(a) for a in new_keywords]) + if op is None: + # Process just stable keywords. + arches = [k for k, v in arch_status.items() + if v == 'stable' and k in old_arches] + else: + # Process all possible keywords. We use the arch_status as a + # master list. If it lacks some keywords, then we might miss + # somethings here, but not much we can do. + arches = list(old_arches) + + # We ignore the glob arch as we never want to tweak it. + if '*' in arches: + arches.remove('*') + + # For keywords that are explicitly disabled, do not update. When + # people use `ekeyword ~all ...` or `ekeyword all ...`, they rarely + # (if ever) want to change a '-sparc' to 'sparc' or '-sparc' to + # '~sparc'. We force people to explicitly do `ekeyword sparc ...` + # in these cases. + arches = [x for x in arches if '-' + x not in new_keywords] + else: + arches = [oarch] + + if refarch: + # Figure out the state for this arch based on the reference arch. + # TODO: Add support for "all" keywords. + # XXX: Should this ignore the '-' state ? Does it make sense to + # sync e.g. "s390" to "-ppc" ? + refkeyword = [x for x in new_keywords if refarch == keyword_to_arch(x)] + if not refkeyword: + op = '^' + elif refkeyword[0].startswith('~'): + op = '~' + elif refkeyword[0].startswith('-'): + op = '-' + else: + op = None + + # Finally do the actual update of the keywords list. + for arch in arches: + new_keywords -= set(['%s%s' % (x, arch) for x in ('', '~', '-')]) + + if op is None: + new_keywords.add(arch) + elif op in ('~', '-'): + new_keywords.add('%s%s' % (op, arch)) + elif op == '^': + # Already deleted. Whee. + pass + else: + raise ValueError('unknown operation %s' % op) + + return new_keywords + + +def process_content(ebuild, data, ops, arch_status=None, verbose=0, + quiet=0, style='color-inline'): + """Process |ops| for |data|""" + # Set up the user display style based on verbose/quiet settings. + if verbose > 1: + disp_name = ebuild + def logit(msg): + print('%s: %s' % (disp_name, msg)) + elif quiet > 1: + def logit(_msg): + pass + else: + # Chop the full path and the .ebuild suffix. + disp_name = os.path.basename(ebuild)[:-7] + def logit(msg): + print('%s: %s' % (disp_name, msg)) + + # Match any KEYWORDS= entry that isn't commented out. + keywords_re = re.compile(r'^([^#]*\bKEYWORDS=)([\'"])(.*)(\2)(.*)') + updated = False + content = [] + + # Walk each line of the ebuild looking for KEYWORDS to process. + for line in data: + m = keywords_re.match(line) + if not m: + content.append(line) + continue + + # Ok, we've got it, now let's process things. + old_keywords = set(m.group(3).split()) + new_keywords = process_keywords( + old_keywords, ops, arch_status=arch_status) + + # Finally let's present the results to the user. + if (new_keywords != old_keywords) or verbose: + # Only do the diff work if something actually changed. + updated = True + old_keywords = sort_keywords(old_keywords) + new_keywords = sort_keywords(new_keywords) + line = '%s"%s"%s\n' % (m.group(1), ' '.join(new_keywords), + m.group(5)) + if style in ('color-inline', 'inline'): + logit(diff_keywords(old_keywords, new_keywords, style=style)) + else: + if style == 'long-multi': + logit(' '.join(['%*s' % (len(keyword_to_arch(x)) + 1, x) + for x in old_keywords])) + logit(' '.join(['%*s' % (len(keyword_to_arch(x)) + 1, x) + for x in new_keywords])) + else: + deleted_keywords = [x for x in old_keywords + if x not in new_keywords] + logit('--- %s' % ' '.join(deleted_keywords)) + added_keywords = [x for x in new_keywords + if x not in old_keywords] + logit('+++ %s' % ' '.join(added_keywords)) + + content.append(line) + + if not updated: + logit('no updates') + + return updated, content + + +def process_ebuild(ebuild, ops, arch_status=None, verbose=0, quiet=0, + dry_run=False, style='color-inline', manifest=False): + """Process |ops| for |ebuild| + + Args: + ebuild: The ebuild file to operate on & update in place + ops: An iterable of operations (Op objects) to perform on |ebuild| + arch_status: A dict mapping default arches to their stability; see the + load_profile_data function for more details + verbose: Be verbose; show various status messages + quiet: Be quiet; only show errors + dry_run: Do not make any changes to |ebuild|; show what would be done + style: The diff style + + Returns: + Whether any updates were processed + """ + with io.open(ebuild, encoding='utf8') as f: + updated, content = process_content( + ebuild, f, ops, arch_status=arch_status, + verbose=verbose, quiet=quiet, style=style) + if updated and not dry_run: + with io.open(ebuild, 'w', encoding='utf8') as f: + f.writelines(content) + if manifest: + subprocess.check_call(['ebuild', ebuild, 'manifest']) + return updated + + +def portage_settings(): + """Return the portage settings we care about.""" + # Portage creates the db member on the fly which confuses the linter. + # pylint: disable=no-member + return portage.db['/']['vartree'].settings + + +def load_profile_data(portdir=None, repo='gentoo'): + """Load the list of known arches from the tree + + Args: + portdir: The repository to load all data from (and ignore |repo|) + repo: Look up this repository by name to locate profile data + + Returns: + A dict mapping the keyword to its preferred state: + {'x86': 'stable', 'mips': 'dev', ...} + """ + if portdir is None: + portdir = portage_settings().repositories[repo].location + + arch_status = {} + + try: + arch_list = os.path.join(portdir, 'profiles', 'arch.list') + with open(arch_list) as f: + for line in f: + line = line.split('#', 1)[0].strip() + if line: + arch_status[line] = None + except IOError: + pass + + try: + profile_status = { + 'stable': 0, + 'dev': 1, + 'exp': 2, + None: 3, + } + profiles_list = os.path.join(portdir, 'profiles', 'profiles.desc') + with open(profiles_list) as f: + for line in f: + line = line.split('#', 1)[0].split() + if line: + arch, _profile, status = line + arch_status.setdefault(arch, status) + curr_status = profile_status[arch_status[arch]] + new_status = profile_status[status] + if new_status < curr_status: + arch_status[arch] = status + except IOError: + pass + + if arch_status: + arch_status['all'] = None + else: + warning('could not read profile files: %s' % arch_list) + warning('will not be able to verify args are correct') + + return arch_status + + +def arg_to_op(arg): + """Convert a command line |arg| to an Op""" + arch_prefixes = ('-', '~', '^') + + op = None + arch = arg + refarch = None + + if arg and arg[0] in arch_prefixes: + op, arch = arg[0], arg[1:] + + if '=' in arch: + if not op is None: + raise ValueError('Cannot use an op and a refarch') + arch, refarch = arch.split('=', 1) + + return Op(op, arch, refarch) + + +def ignorable_arg(arg, quiet=0): + """Whether it's ok to ignore this argument""" + if os.path.isdir(arg): + if not quiet: + warning('ignoring directory %s' % arg) + return True + + WHITELIST = ( + 'Manifest', + 'metadata.xml', + ) + base = os.path.basename(arg) + if (base.startswith('ChangeLog') or + base in WHITELIST or + base.startswith('.') or + base.endswith('~')): + if not quiet: + warning('ignoring file: %s' % arg) + return True + + return False + + +def args_to_work(args, arch_status=None, _repo='gentoo', quiet=0): + """Process |args| into a list of work itmes (ebuild/arches to update)""" + work = [] + todo_arches = [] + last_todo_arches = None + + for arg in args: + if arg.endswith('.ebuild'): + if not todo_arches: + todo_arches = last_todo_arches + if not todo_arches: + raise ValueError('missing arches to process for %s' % arg) + work.append([arg, todo_arches]) + last_todo_arches = todo_arches + todo_arches = [] + else: + op = arg_to_op(arg) + if not arch_status or op.arch in arch_status: + todo_arches.append(op) + elif not ignorable_arg(arg, quiet=quiet): + raise ValueError('unknown arch/argument: %s' % arg) + + if todo_arches: + raise ValueError('missing ebuilds to process!') + + return work + + +def get_parser(): + """Return an argument parser for ekeyword""" + parser = argparse.ArgumentParser( + description=__doc__, + formatter_class=argparse.RawDescriptionHelpFormatter) + parser.add_argument('-m', '--manifest', default=False, action='store_true', + help='Run `ebuild manifest` on the ebuild after modifying it') + parser.add_argument('-n', '--dry-run', default=False, action='store_true', + help='Show what would be changed, but do not commit') + parser.add_argument('-v', '--verbose', action='count', default=0, + help='Be verbose while processing things') + parser.add_argument('-q', '--quiet', action='count', default=0, + help='Be quiet while processing things (only show errors)') + parser.add_argument('--format', default='auto', dest='style', + choices=('auto', 'color-inline', 'inline', 'short-multi', 'long-multi'), + help='Selet output format for showing differences') + parser.add_argument('-V', '--version', action='version', version=VERSION, + help='Show version information') + return parser + + +def main(argv): + if argv is None: + argv = sys.argv[1:] + + # Extract the args ourselves. This is to allow things like -hppa + # without tripping over the -h/--help flags. We can't use the + # parse_known_args function either. + # This sucks and really wish we didn't need to do this ... + parse_args = [] + work_args = [] + while argv: + arg = argv.pop(0) + if arg.startswith('--'): + if arg == '--': + work_args += argv + break + else: + parse_args.append(arg) + # Handle flags that take arguments. + if arg in ('--format',): + if argv: + parse_args.append(argv.pop(0)) + elif len(arg) == 2 and arg[0] == '-': + parse_args.append(arg) + else: + work_args.append(arg) + + parser = get_parser() + opts = parser.parse_args(parse_args) + if not work_args: + parser.error('need arches/ebuilds to process') + + if opts.style == 'auto': + if not portage_settings().get('NOCOLOR', 'false').lower() in ('no', 'false'): + nocolor() + opts.style = 'short' + else: + opts.style = 'color-inline' + + arch_status = load_profile_data() + try: + work = args_to_work(work_args, arch_status=arch_status, quiet=opts.quiet) + except ValueError as e: + parser.error(e) + + for ebuild, ops in work: + process_ebuild(ebuild, ops, arch_status=arch_status, + verbose=opts.verbose, quiet=opts.quiet, + dry_run=opts.dry_run, style=opts.style, + manifest=opts.manifest) + + return os.EX_OK + + +if __name__ == '__main__': + sys.exit(main(sys.argv[1:])) diff --git a/pym/gentoolkit/ekeyword/ekeyword_unittest.py b/pym/gentoolkit/ekeyword/ekeyword_unittest.py new file mode 100755 index 0000000..de40e7a --- /dev/null +++ b/pym/gentoolkit/ekeyword/ekeyword_unittest.py @@ -0,0 +1,424 @@ +#!/usr/bin/python +# Copyright 2014 Gentoo Foundation +# Distributed under the terms of the GNU General Public License v2 +# Written by Mike Frysinger <vapier@gentoo.org> + +# pylint: disable=no-self-use + +"""Unittests for ekeyword""" + +from __future__ import print_function + +import os +import subprocess +import tempfile +import unittest + +import mock + +import ekeyword + + +TESTDIR = os.path.join(os.path.dirname(__file__), 'tests') + + +class TestSortKeywords(unittest.TestCase): + """Tests for sort_keywords""" + + def _test(self, input_data, exp_data): + """Sort |input_data| and make sure it matches |exp_data|""" + output_data = ekeyword.sort_keywords(input_data.split()) + self.assertEqual(exp_data.split(), output_data) + + def testNull(self): + """Verify whitespace is collapsed""" + self._test('', '') + self._test(' ', '') + + def testGlob(self): + """Verify globs get sorted before all others""" + self._test('* arm', '* arm') + self._test('arm -* x86', '-* arm x86') + self._test('hppa ~* amd64', '~* amd64 hppa') + + def testMixedPlatform(self): + """Verify core arches get sorted before all w/suffix""" + self._test('arm-linux alpha amd64-fbsd hppa', + 'alpha hppa amd64-fbsd arm-linux') + + def testPrefixes(self): + """Verify -/~ and such get ignored for sorting""" + self._test('-hppa arm ~alpha -* ~arm-linux', + '-* ~alpha arm -hppa ~arm-linux') + + def testPlatform(self): + """Verify we sort based on platform first""" + self._test('x86-linux ppc-macos x86-fbsd amd64-linux amd64-fbsd', + 'amd64-fbsd x86-fbsd amd64-linux x86-linux ppc-macos') + + +class TestDiffKeywords(unittest.TestCase): + """Tests for diff_keywords""" + + def testEmpty(self): + """Test when there is no content to diff""" + ret = ekeyword.diff_keywords([], []) + self.assertEqual(ret, '') + + def testSame(self): + """Test when there is no difference""" + ret = ekeyword.diff_keywords(['a b c'], ['a b c']) + self.assertEqual(ret, 'a b c') + + def testInsert(self): + """Test when content is simply added""" + ret = ekeyword.diff_keywords(['a'], ['~a']) + self.assertNotEqual(ret, '') + + def testDelete(self): + """Test when content is simply deleted""" + ret = ekeyword.diff_keywords(['~a'], ['a']) + self.assertNotEqual(ret, '') + + def testReplace(self): + """Test when some content replaces another""" + ret = ekeyword.diff_keywords(['~a'], ['-a']) + self.assertNotEqual(ret, '') + + def _testSmokeStyle(self, style): + return ekeyword.diff_keywords( + ['~a', 'b', '-abcde'], + ['a', '-b', '-abxde'], style=style) + + def testSmokeStyleColor(self): + """Run a full smoke test for color-inline style""" + ret = self._testSmokeStyle('color-inline') + self.assertNotEqual(ret, '') + + def testSmokeStyleNoColor(self): + """Run a full smoke test for non-color-inline style""" + self._testSmokeStyle('nocolor') + + +class TestProcessKeywords(unittest.TestCase): + """Tests for process_keywords""" + + def _test(self, keywords, ops, exp, arch_status=None): + # This func doesn't return sorted results (which is fine), + # so do so ourselves to get stable tests. + ret = ekeyword.process_keywords( + keywords.split(), ops, arch_status=arch_status) + self.assertEqual(sorted(ret), sorted(exp.split())) + + def testAdd(self): + ops = ( + ekeyword.Op(None, 'arm', None), + ekeyword.Op('~', 's390', None), + ekeyword.Op('-', 'sh', None), + ) + self._test('moo', ops, 'arm ~s390 -sh moo') + + def testModify(self): + ops = ( + ekeyword.Op(None, 'arm', None), + ekeyword.Op('~', 's390', None), + ekeyword.Op('-', 'sh', None), + ) + self._test('~arm s390 ~sh moo', ops, 'arm ~s390 -sh moo') + + def testDelete(self): + ops = ( + ekeyword.Op('^', 'arm', None), + ekeyword.Op('^', 's390', None), + ekeyword.Op('^', 'x86', None), + ) + self._test('arm -s390 ~x86 bar', ops, 'bar') + + def testSync(self): + ops = ( + ekeyword.Op('=', 'arm64', 'arm'), + ekeyword.Op('=', 'ppc64', 'ppc'), + ekeyword.Op('=', 'amd64', 'x86'), + ekeyword.Op('=', 'm68k', 'mips'), + ekeyword.Op('=', 'ia64', 'alpha'), + ekeyword.Op('=', 'sh', 'sparc'), + ekeyword.Op('=', 's390', 's390x'), + ekeyword.Op('=', 'boo', 'moo'), + ) + self._test( + 'arm64 arm ' + '~ppc64 ~ppc ' + '~amd64 x86 ' + 'm68k ~mips ' + '-ia64 alpha ' + 'sh -sparc ' + 's390 ' + 'moo ', + ops, + 'arm64 arm ~ppc64 ~ppc amd64 x86 ~m68k ~mips ia64 alpha ' + '-sh -sparc boo moo') + + def testAllNoStatus(self): + ops = ( + ekeyword.Op(None, 'all', None), + ) + self.assertRaises(ValueError, self._test, '', ops, '') + + def testAllStable(self): + ops = ( + ekeyword.Op(None, 'all', None), + ) + arch_status = { + 'alpha': None, + 'arm': 'stable', + 'arm64': 'exp', + 'm68k': 'dev', + } + self._test('* ~alpha ~arm ~arm64 ~m68k ~mips ~arm-linux', ops, + '* ~alpha arm ~arm64 ~m68k ~mips ~arm-linux', arch_status) + + def testAllUnstable(self): + ops = ( + ekeyword.Op('~', 'all', None), + ) + arch_status = { + 'alpha': None, + 'arm': 'stable', + 'arm64': 'exp', + 'm68k': 'dev', + 's390': 'dev', + 'sh': 'dev', + } + self._test('-* ~* * alpha arm arm64 m68k arm-linux', ops, + '-* ~* * ~alpha ~arm ~arm64 ~m68k ~arm-linux', arch_status) + + def testAllMultiUnstableStable(self): + ops = ( + ekeyword.Op('~', 'all', None), + ekeyword.Op(None, 'all', None), + ) + arch_status = { + 'alpha': None, + 'arm': 'stable', + 'arm64': 'exp', + 'm68k': 'dev', + } + self._test('-* ~* * alpha arm arm64 m68k', ops, + '-* ~* * ~alpha arm ~arm64 ~m68k', arch_status) + + def testAllDisabled(self): + """Make sure ~all does not change -arch to ~arch""" + ops = ( + ekeyword.Op('~', 'all', None), + ) + self._test('alpha -sparc ~x86', ops, + '~alpha -sparc ~x86', {}) + + +class TestProcessContent(unittest.TestCase): + """Tests for process_content""" + + def _testKeywords(self, line): + ops = ( + ekeyword.Op(None, 'arm', None), + ekeyword.Op('~', 'sparc', None), + ) + return ekeyword.process_content( + 'file', ['%s\n' % line], ops, quiet=True) + + def testKeywords(self): + """Basic KEYWORDS mod""" + updated, ret = self._testKeywords('KEYWORDS=""') + self.assertTrue(updated) + self.assertEqual(ret, ['KEYWORDS="arm ~sparc"\n']) + + def testKeywordsIndented(self): + """Test KEYWORDS indented by space""" + updated, ret = self._testKeywords(' KEYWORDS=""') + self.assertTrue(updated) + self.assertEqual(ret, [' KEYWORDS="arm ~sparc"\n']) + + def testKeywordsSingleQuote(self): + """Test single quoted KEYWORDS""" + updated, ret = self._testKeywords("KEYWORDS=' '") + self.assertTrue(updated) + self.assertEqual(ret, ['KEYWORDS="arm ~sparc"\n']) + + def testKeywordsComment(self): + """Test commented out KEYWORDS""" + updated, ret = self._testKeywords('# KEYWORDS=""') + self.assertFalse(updated) + self.assertEqual(ret, ['# KEYWORDS=""\n']) + + def testKeywordsCode(self): + """Test code leading KEYWORDS""" + updated, ret = self._testKeywords('[[ ${PV} ]] && KEYWORDS=""') + self.assertTrue(updated) + self.assertEqual(ret, ['[[ ${PV} ]] && KEYWORDS="arm ~sparc"\n']) + + def testKeywordsEmpty(self): + """Test KEYWORDS not set at all""" + updated, ret = self._testKeywords(' KEYWORDS=') + self.assertFalse(updated) + self.assertEqual(ret, [' KEYWORDS=\n']) + + def _testSmoke(self, style='color-inline', verbose=0, quiet=0): + ops = ( + ekeyword.Op(None, 'arm', None), + ekeyword.Op('~', 'sparc', None), + ) + ekeyword.process_content( + 'asdf', ['KEYWORDS="arm"'], ops, verbose=verbose, + quiet=quiet, style=style) + + def testSmokeQuiet(self): + """Smoke test for quiet mode""" + self._testSmoke(quiet=10) + + def testSmokeVerbose(self): + """Smoke test for verbose mode""" + self._testSmoke(verbose=10) + + def testSmokeStyleColor(self): + """Smoke test for color-inline style""" + self._testSmoke('color-inline') + + def testSmokeStyleInline(self): + """Smoke test for inline style""" + self._testSmoke('inline') + + def testSmokeStyleShortMulti(self): + """Smoke test for short-multi style""" + self._testSmoke('short-multi') + + def testSmokeStyleLongMulti(self): + """Smoke test for long-multi style""" + self._testSmoke('long-multi') + + +class TestProcessEbuild(unittest.TestCase): + """Tests for process_ebuild + + This is fairly light as most code is in process_content. + """ + + def _process_ebuild(self, *args, **kwargs): + """Set up a writable copy of an ebuild for process_ebuild()""" + with tempfile.NamedTemporaryFile() as tmp: + with open(tmp.name, 'wb') as fw: + with open(os.path.join(TESTDIR, 'process-1.ebuild'), 'rb') as f: + orig_content = f.read() + fw.write(orig_content) + ekeyword.process_ebuild(tmp.name, *args, **kwargs) + with open(tmp.name, 'rb') as f: + return (orig_content, f.read()) + + def _testSmoke(self, dry_run): + ops = ( + ekeyword.Op(None, 'arm', None), + ekeyword.Op('~', 'sparc', None), + ) + orig_content, new_content = self._process_ebuild(ops, dry_run=dry_run) + if dry_run: + self.assertEqual(orig_content, new_content) + else: + self.assertNotEqual(orig_content, new_content) + + def testSmokeNotDry(self): + self._testSmoke(False) + + def testSmokeDry(self): + self._testSmoke(True) + + def testManifestUpdated(self): + """Verify `ebuild ... manifest` runs on updated files""" + with mock.patch.object(subprocess, 'check_call') as m: + self._process_ebuild((ekeyword.Op('~', 'arm', None),), + manifest=True) + m.assert_called_once_with(['ebuild', mock.ANY, 'manifest']) + + def testManifestNotUpdated(self): + """Verify we don't run `ebuild ... manifest` on unmodified files""" + with mock.patch.object(subprocess, 'check_call') as m: + self._process_ebuild((ekeyword.Op(None, 'arm', None),), + manifest=True) + self.assertEqual(m.call_count, 0) + + +class TestLoadProfileData(unittest.TestCase): + """Tests for load_profile_data""" + + def _test(self, subdir): + portdir = os.path.join(TESTDIR, 'profiles', subdir) + return ekeyword.load_profile_data(portdir=portdir) + + def testLoadBoth(self): + """Test loading both arch.list and profiles.desc""" + ret = self._test('both') + self.assertIn('arm', ret) + self.assertEqual(ret['arm'], 'stable') + self.assertIn('arm64', ret) + self.assertEqual(ret['arm64'], 'exp') + + def testLoadArchOnly(self): + """Test loading only arch.list""" + ret = self._test('arch-only') + self.assertIn('arm', ret) + self.assertEqual(ret['arm'], None) + self.assertIn('x86-solaris', ret) + + def testLoadProfilesOnly(self): + """Test loading only profiles.desc""" + ret = self._test('profiles-only') + self.assertIn('arm', ret) + self.assertEqual(ret['arm'], 'stable') + self.assertIn('arm64', ret) + self.assertEqual(ret['arm64'], 'exp') + + def testLoadNone(self): + """Test running when neither files exists""" + ret = self._test('none') + self.assertEqual(ret, {}) + + +class TestArgToOps(unittest.TestCase): + """Tests for arg_to_op()""" + + def _test(self, arg, op): + self.assertEqual(ekeyword.arg_to_op(arg), ekeyword.Op(*op)) + + def testStable(self): + self._test('arm', (None, 'arm', None)) + + def testUnstable(self): + self._test('~ppc64', ('~', 'ppc64', None)) + + def testDisabled(self): + self._test('-sparc', ('-', 'sparc', None)) + + def testDeleted(self): + self._test('^x86-fbsd', ('^', 'x86-fbsd', None)) + + def testSync(self): + self._test('s390=x86', (None, 's390', 'x86')) + + +class TestMain(unittest.TestCase): + """Tests for the main entry point""" + + def testSmoke(self): + ekeyword.main(['arm', '--dry-run', os.path.join(TESTDIR, 'process-1.ebuild')]) + + def testVersion(self): + with self.assertRaises(SystemExit) as e: + ekeyword.main(['--version', '--dry-run']) + self.assertEqual(e.exception.code, os.EX_OK) + + def testEmptyString(self): + with self.assertRaises(SystemExit) as e: + ekeyword.main(['', os.path.join(TESTDIR, 'process-1.ebuild')]) + self.assertNotEqual(e.exception.code, os.EX_OK) + + +if __name__ == '__main__': + unittest.main() diff --git a/pym/gentoolkit/ekeyword/pylint b/pym/gentoolkit/ekeyword/pylint new file mode 100755 index 0000000..3a9a368 --- /dev/null +++ b/pym/gentoolkit/ekeyword/pylint @@ -0,0 +1,49 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# Copyright 1999-2017 Gentoo Foundation +# Distributed under the terms of the GNU General Public License v2 + +"""Run pylint with the right settings.""" + +from __future__ import print_function + +import os +import sys + + +def find_all_modules(source_root): + """Locate all python modules in the tree for scanning""" + ret = [] + + for root, _dirs, files in os.walk(source_root, topdown=False): + # Add all of the .py modules in the tree. + ret += [os.path.join(root, x) for x in files if x.endswith('.py')] + + # Add the main scripts that don't end in .py. + ret += [os.path.join(source_root, x) for x in ('pylint',)] + + return ret + + +def main(argv): + """The main entry point""" + source_root = os.path.dirname(os.path.realpath(__file__)) + + if not argv: + argv = find_all_modules(source_root) + + pympath = source_root + pythonpath = os.environ.get('PYTHONPATH') + if pythonpath is None: + pythonpath = pympath + else: + pythonpath = pympath + ':' + pythonpath + os.environ['PYTHONPATH'] = pythonpath + + pylintrc = os.path.join(source_root, '.pylintrc') + cmd = ['pylint', '--rcfile', pylintrc] + os.execvp(cmd[0], cmd + argv) + + +if __name__ == '__main__': + sys.exit(main(sys.argv[1:])) diff --git a/pym/gentoolkit/ekeyword/pytest.ini b/pym/gentoolkit/ekeyword/pytest.ini new file mode 100644 index 0000000..622c9d8 --- /dev/null +++ b/pym/gentoolkit/ekeyword/pytest.ini @@ -0,0 +1,3 @@ +[pytest] +addopts = --cov +python_files = *_unittest.py diff --git a/pym/gentoolkit/ekeyword/tests/process-1.ebuild b/pym/gentoolkit/ekeyword/tests/process-1.ebuild new file mode 100644 index 0000000..75168c6 --- /dev/null +++ b/pym/gentoolkit/ekeyword/tests/process-1.ebuild @@ -0,0 +1,5 @@ +# asdf + +KEYWORDS="arm ~hppa x86" + +# blah diff --git a/pym/gentoolkit/ekeyword/tests/profiles/arch-only/profiles/arch.list b/pym/gentoolkit/ekeyword/tests/profiles/arch-only/profiles/arch.list new file mode 120000 index 0000000..361ad76 --- /dev/null +++ b/pym/gentoolkit/ekeyword/tests/profiles/arch-only/profiles/arch.list @@ -0,0 +1 @@ +../../both/profiles/arch.list
\ No newline at end of file diff --git a/pym/gentoolkit/ekeyword/tests/profiles/both/profiles/arch.list b/pym/gentoolkit/ekeyword/tests/profiles/both/profiles/arch.list new file mode 100644 index 0000000..e4787c0 --- /dev/null +++ b/pym/gentoolkit/ekeyword/tests/profiles/both/profiles/arch.list @@ -0,0 +1,45 @@ +alpha +amd64 +amd64-fbsd +arm +arm64 +hppa +ia64 +m68k +mips +ppc +ppc64 +s390 +sh +sparc +sparc-fbsd +x86 +x86-fbsd + +# Prefix keywords +ppc-aix +x86-freebsd +x64-freebsd +sparc64-freebsd +hppa-hpux +ia64-hpux +x86-interix +amd64-linux +arm-linux +ia64-linux +ppc64-linux +x86-linux +ppc-macos +x86-macos +x64-macos +m68k-mint +x86-netbsd +ppc-openbsd +x86-openbsd +x64-openbsd +sparc-solaris +sparc64-solaris +x64-solaris +x86-solaris +x86-winnt +x86-cygwin diff --git a/pym/gentoolkit/ekeyword/tests/profiles/both/profiles/profiles.desc b/pym/gentoolkit/ekeyword/tests/profiles/both/profiles/profiles.desc new file mode 100644 index 0000000..be751a8 --- /dev/null +++ b/pym/gentoolkit/ekeyword/tests/profiles/both/profiles/profiles.desc @@ -0,0 +1,295 @@ +############################################# +# This is a list of valid profiles for each architecture. This file is used by +# repoman when doing a repoman scan or repoman full. +# DO NOT ADD PROFILES WITH A "die" or "exit" IN THEM OR IT KILLS REPOMAN +# +#layout: +#arch profile_directory status + +# Alpha Profiles +alpha default/linux/alpha/13.0 stable +alpha default/linux/alpha/13.0/desktop stable +alpha default/linux/alpha/13.0/desktop/gnome stable +alpha default/linux/alpha/13.0/desktop/gnome/systemd stable +alpha default/linux/alpha/13.0/desktop/kde stable +alpha default/linux/alpha/13.0/desktop/kde/systemd stable +alpha default/linux/alpha/13.0/developer stable + +# AMD64 Profiles +amd64 default/linux/amd64/13.0 stable +amd64 default/linux/amd64/13.0/selinux dev +amd64 default/linux/amd64/13.0/desktop stable +amd64 default/linux/amd64/13.0/desktop/gnome stable +amd64 default/linux/amd64/13.0/desktop/gnome/systemd stable +amd64 default/linux/amd64/13.0/desktop/kde stable +amd64 default/linux/amd64/13.0/desktop/kde/systemd stable +amd64 default/linux/amd64/13.0/developer stable +amd64 default/linux/amd64/13.0/no-multilib dev +amd64 default/linux/amd64/13.0/x32 dev + +# ARM Profiles +arm default/linux/arm/13.0 stable +arm default/linux/arm/13.0/desktop dev +arm default/linux/arm/13.0/desktop/gnome dev +arm default/linux/arm/13.0/desktop/gnome/systemd dev +arm default/linux/arm/13.0/desktop/kde dev +arm default/linux/arm/13.0/desktop/kde/systemd dev +arm default/linux/arm/13.0/developer dev +arm default/linux/arm/13.0/armv4 dev +arm default/linux/arm/13.0/armv4/desktop dev +arm default/linux/arm/13.0/armv4/desktop/gnome dev +arm default/linux/arm/13.0/armv4/desktop/kde dev +arm default/linux/arm/13.0/armv4/developer dev +arm default/linux/arm/13.0/armv4t dev +arm default/linux/arm/13.0/armv4t/desktop dev +arm default/linux/arm/13.0/armv4t/desktop/gnome dev +arm default/linux/arm/13.0/armv4t/desktop/kde dev +arm default/linux/arm/13.0/armv4t/developer dev +arm default/linux/arm/13.0/armv5te dev +arm default/linux/arm/13.0/armv5te/desktop dev +arm default/linux/arm/13.0/armv5te/desktop/gnome dev +arm default/linux/arm/13.0/armv5te/desktop/kde dev +arm default/linux/arm/13.0/armv5te/developer dev +arm default/linux/arm/13.0/armv6j dev +arm default/linux/arm/13.0/armv6j/desktop dev +arm default/linux/arm/13.0/armv6j/desktop/gnome dev +arm default/linux/arm/13.0/armv6j/desktop/kde dev +arm default/linux/arm/13.0/armv6j/developer dev +arm default/linux/arm/13.0/armv7a dev +arm default/linux/arm/13.0/armv7a/desktop dev +arm default/linux/arm/13.0/armv7a/desktop/gnome dev +arm default/linux/arm/13.0/armv7a/desktop/kde dev +arm default/linux/arm/13.0/armv7a/developer dev + +# ARM64 Profiles +arm64 default/linux/arm64/13.0 exp +arm64 default/linux/arm64/13.0/desktop exp +arm64 default/linux/arm64/13.0/developer exp + +# HPPA Profiles +hppa default/linux/hppa/13.0 stable +hppa default/linux/hppa/13.0/desktop dev +hppa default/linux/hppa/13.0/developer dev + +# IA64 Profiles +ia64 default/linux/ia64/13.0 stable +ia64 default/linux/ia64/13.0/desktop stable +ia64 default/linux/ia64/13.0/desktop/gnome stable +ia64 default/linux/ia64/13.0/desktop/gnome/systemd stable +ia64 default/linux/ia64/13.0/desktop/kde stable +ia64 default/linux/ia64/13.0/desktop/kde/systemd stable +ia64 default/linux/ia64/13.0/developer stable + +# M68K Profiles +m68k default/linux/m68k/13.0 dev +m68k default/linux/m68k/13.0/desktop dev +m68k default/linux/m68k/13.0/desktop/gnome dev +m68k default/linux/m68k/13.0/desktop/kde dev +m68k default/linux/m68k/13.0/developer dev + +# MIPS Profiles +mips default/linux/mips/13.0 dev +mips default/linux/mips/13.0/n32 dev +mips default/linux/mips/13.0/n64 exp +mips default/linux/mips/13.0/multilib dev +mips default/linux/mips/13.0/multilib/n32 dev +mips default/linux/mips/13.0/multilib/n64 exp +mips default/linux/mips/13.0/mipsel dev +mips default/linux/mips/13.0/mipsel/n32 dev +mips default/linux/mips/13.0/mipsel/n64 exp +mips default/linux/mips/13.0/mipsel/multilib dev +mips default/linux/mips/13.0/mipsel/multilib/n32 dev +mips default/linux/mips/13.0/mipsel/multilib/n64 exp + +# PPC32 Profiles +ppc default/linux/powerpc/ppc32/13.0 stable +ppc default/linux/powerpc/ppc32/13.0/desktop stable +ppc default/linux/powerpc/ppc32/13.0/desktop/gnome stable +ppc default/linux/powerpc/ppc32/13.0/desktop/gnome/systemd stable +ppc default/linux/powerpc/ppc32/13.0/desktop/kde stable +ppc default/linux/powerpc/ppc32/13.0/desktop/kde/systemd stable +ppc default/linux/powerpc/ppc32/13.0/developer stable + +# PPC64 Profiles +ppc default/linux/powerpc/ppc64/13.0/32bit-userland stable +ppc default/linux/powerpc/ppc64/13.0/32bit-userland/desktop stable +ppc default/linux/powerpc/ppc64/13.0/32bit-userland/desktop/gnome stable +ppc default/linux/powerpc/ppc64/13.0/32bit-userland/desktop/gnome/systemd stable +ppc default/linux/powerpc/ppc64/13.0/32bit-userland/desktop/kde stable +ppc default/linux/powerpc/ppc64/13.0/32bit-userland/desktop/kde/systemd stable +ppc default/linux/powerpc/ppc64/13.0/32bit-userland/developer stable +ppc64 default/linux/powerpc/ppc64/13.0/64bit-userland stable +ppc64 default/linux/powerpc/ppc64/13.0/64bit-userland/desktop stable +ppc64 default/linux/powerpc/ppc64/13.0/64bit-userland/desktop/gnome stable +ppc64 default/linux/powerpc/ppc64/13.0/64bit-userland/desktop/gnome/systemd stable +ppc64 default/linux/powerpc/ppc64/13.0/64bit-userland/desktop/kde stable +ppc64 default/linux/powerpc/ppc64/13.0/64bit-userland/desktop/kde/systemd stable +ppc64 default/linux/powerpc/ppc64/13.0/64bit-userland/developer stable + +# S390 Profiles +s390 default/linux/s390/13.0 dev +s390 default/linux/s390/13.0/s390x dev + +# SH Profiles +sh default/linux/sh/13.0 dev +sh default/linux/sh/13.0/desktop dev +sh default/linux/sh/13.0/desktop/gnome dev +sh default/linux/sh/13.0/desktop/kde dev +sh default/linux/sh/13.0/developer dev + +# SPARC Profiles +sparc default/linux/sparc/13.0 stable +sparc default/linux/sparc/13.0/desktop stable +sparc default/linux/sparc/13.0/desktop/gnome stable +sparc default/linux/sparc/13.0/desktop/kde stable +sparc default/linux/sparc/13.0/developer stable + +# x86 Profiles +x86 default/linux/x86/13.0 stable +x86 default/linux/x86/13.0/selinux dev +x86 default/linux/x86/13.0/desktop stable +x86 default/linux/x86/13.0/desktop/gnome stable +x86 default/linux/x86/13.0/desktop/gnome/systemd stable +x86 default/linux/x86/13.0/desktop/kde stable +x86 default/linux/x86/13.0/desktop/kde/systemd stable +x86 default/linux/x86/13.0/developer stable + +# Gentoo/FreeBSD Profiles +amd64-fbsd default/bsd/fbsd/amd64/9.1 stable +amd64-fbsd default/bsd/fbsd/amd64/9.2 dev +amd64-fbsd default/bsd/fbsd/amd64/9.1/clang exp +amd64-fbsd default/bsd/fbsd/amd64/9.2/clang exp +sparc-fbsd default/bsd/fbsd/sparc/8.2 exp +x86-fbsd default/bsd/fbsd/x86/9.1 dev +x86-fbsd default/bsd/fbsd/x86/9.2 dev + +# Hardened Profiles +amd64 hardened/linux/amd64 stable +amd64 hardened/linux/amd64/selinux stable +amd64 hardened/linux/amd64/no-multilib stable +amd64 hardened/linux/amd64/no-multilib/selinux stable +amd64 hardened/linux/amd64/x32 dev +amd64 hardened/linux/uclibc/amd64 dev +arm hardened/linux/arm/armv7a dev +arm hardened/linux/arm/armv6j dev +arm hardened/linux/uclibc/arm/armv7a dev +ia64 hardened/linux/ia64 dev +mips hardened/linux/uclibc/mips exp +mips hardened/linux/uclibc/mips/mipsel exp +ppc hardened/linux/powerpc/ppc32 dev +ppc hardened/linux/powerpc/ppc64/32bit-userland dev +ppc64 hardened/linux/powerpc/ppc64/64bit-userland dev +x86 hardened/linux/x86 stable +x86 hardened/linux/x86/selinux stable +x86 hardened/linux/uclibc/x86 dev + +# uclibc/embedded multiarch profiles +#amd64 uclibc/amd64 dev +#arm uclibc/arm dev +#arm uclibc/arm/2.4 dev +#mips uclibc/mips dev +#mips uclibc/mips/hardened dev +#ppc uclibc/ppc dev +#ppc uclibc/ppc/2.4 dev +#ppc uclibc/ppc/hardened dev +#ppc uclibc/ppc/hardened/2.4 dev +#sh uclibc/sh dev +#sh uclibc/sh/2.4 dev +#x86 uclibc/x86 dev +#x86 uclibc/x86/2.4 dev +#x86 uclibc/x86/2005.1 dev +#x86 uclibc/x86/2005.1/2.4 dev +#x86 uclibc/x86/hardened dev +#x86 uclibc/x86/hardened/2.4 dev + + +# These are Gentoo Prefix profiles, maintained by the Prefix team + +# Linux Profiles +amd64-linux prefix/linux/amd64 exp +arm-linux prefix/linux/arm exp +ia64-linux prefix/linux/ia64 exp +ppc64-linux prefix/linux/ppc64 exp +x86-linux prefix/linux/x86 exp + +# Mac OS X Profiles +ppc-macos prefix/darwin/macos/10.4/ppc exp +x86-macos prefix/darwin/macos/10.4/x86 exp +ppc-macos prefix/darwin/macos/10.5/ppc exp +x86-macos prefix/darwin/macos/10.5/x86 exp +x64-macos prefix/darwin/macos/10.5/x64 exp +x86-macos prefix/darwin/macos/10.6/x86 exp +x64-macos prefix/darwin/macos/10.6/x64 exp +x86-macos prefix/darwin/macos/10.7/x86 exp +x64-macos prefix/darwin/macos/10.7/x64 exp +x86-macos prefix/darwin/macos/10.8/x86 exp +x64-macos prefix/darwin/macos/10.8/x64 exp +x86-macos prefix/darwin/macos/10.9/x86 exp +x64-macos prefix/darwin/macos/10.9/x64 exp + +# Solaris Profiles +sparc-solaris prefix/sunos/solaris/5.9/sparc exp +sparc-solaris prefix/sunos/solaris/5.10/sparc exp +sparc64-solaris prefix/sunos/solaris/5.10/sparc64 exp +x86-solaris prefix/sunos/solaris/5.10/x86 exp +x64-solaris prefix/sunos/solaris/5.10/x64 exp +sparc-solaris prefix/sunos/solaris/5.11/sparc exp +sparc64-solaris prefix/sunos/solaris/5.11/sparc64 exp +x86-solaris prefix/sunos/solaris/5.11/x86 exp +x64-solaris prefix/sunos/solaris/5.11/x64 exp + +# AIX Profiles +ppc-aix prefix/aix/5.2.0.0/ppc exp +ppc-aix prefix/aix/5.3.0.0/ppc exp +ppc-aix prefix/aix/6.1.0.0/ppc exp + +# Interix Profiles +x86-interix prefix/windows/interix/3.5/x86 exp +x86-interix prefix/windows/interix/5.2/x86 exp +x86-interix prefix/windows/interix/6.0/x86 exp +x86-interix prefix/windows/interix/6.1/x86 exp + +# Windows Profiles +x86-winnt prefix/windows/winnt/3.5/x86 exp +x86-winnt prefix/windows/winnt/5.2/x86 exp +x86-winnt prefix/windows/winnt/6.0/x86 exp +x86-winnt prefix/windows/winnt/6.1/x86 exp + +# Cygwin Profiles +x86-cygwin prefix/windows/cygwin/1.7/x86 exp + +# HP-UX Profiles +ia64-hpux prefix/hpux/B.11.23/ia64 exp +hppa-hpux prefix/hpux/B.11.31/hppa2.0 exp +ia64-hpux prefix/hpux/B.11.31/ia64 exp + +# FreeBSD Profiles +x86-freebsd prefix/bsd/freebsd/7.1/x86 exp +x64-freebsd prefix/bsd/freebsd/7.1/x64 exp +x86-freebsd prefix/bsd/freebsd/7.2/x86 exp +x64-freebsd prefix/bsd/freebsd/7.2/x64 exp +x86-freebsd prefix/bsd/freebsd/8.0/x86 exp +x64-freebsd prefix/bsd/freebsd/8.0/x64 exp +x86-freebsd prefix/bsd/freebsd/8.1/x86 exp +x64-freebsd prefix/bsd/freebsd/8.1/x64 exp +sparc64-freebsd prefix/bsd/freebsd/8.1/sparc64 exp +x86-freebsd prefix/bsd/freebsd/8.2/x86 exp +x64-freebsd prefix/bsd/freebsd/8.2/x64 exp +x86-freebsd prefix/bsd/freebsd/9.0/x86 exp +x64-freebsd prefix/bsd/freebsd/9.0/x64 exp +x86-freebsd prefix/bsd/freebsd/9.1/x86 exp +x64-freebsd prefix/bsd/freebsd/9.1/x64 exp + + +# OpenBSD Profiles +ppc-openbsd prefix/bsd/openbsd/4.2/ppc exp +x86-openbsd prefix/bsd/openbsd/4.2/x86 exp +x64-openbsd prefix/bsd/openbsd/4.2/x64 exp + +# NetBSD Profiles +x86-netbsd prefix/bsd/netbsd/4.0/x86 exp + +# FreeMiNT +m68k-mint prefix/mint/m68k exp + +# vim: set ts=8: diff --git a/pym/gentoolkit/ekeyword/tests/profiles/none/profiles/.keep b/pym/gentoolkit/ekeyword/tests/profiles/none/profiles/.keep new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/pym/gentoolkit/ekeyword/tests/profiles/none/profiles/.keep diff --git a/pym/gentoolkit/ekeyword/tests/profiles/profiles-only/profiles/profiles.desc b/pym/gentoolkit/ekeyword/tests/profiles/profiles-only/profiles/profiles.desc new file mode 120000 index 0000000..04f8005 --- /dev/null +++ b/pym/gentoolkit/ekeyword/tests/profiles/profiles-only/profiles/profiles.desc @@ -0,0 +1 @@ +../../both/profiles/profiles.desc
\ No newline at end of file diff --git a/pym/gentoolkit/imlate/Makefile b/pym/gentoolkit/imlate/Makefile new file mode 100644 index 0000000..6735696 --- /dev/null +++ b/pym/gentoolkit/imlate/Makefile @@ -0,0 +1,18 @@ +# Copyright 1999-2009 Gentoo Foundation +# Distributed under the terms of the GNU General Public License v2 +# $Header: $ + +include ../../makedefs.mak + +.PHONY: all + +all: + +dist: + mkdir -p ../../$(DISTDIR)/src/imlate/ + cp Makefile imlate imlate.1 ../../$(DISTDIR)/src/imlate/ + +install: all + install -m 0755 imlate $(BINDIR)/ + install -m 0644 imlate.1 $(MAN1DIR)/ + diff --git a/pym/gentoolkit/imlate/imlate b/pym/gentoolkit/imlate/imlate new file mode 100755 index 0000000..0be72e4 --- /dev/null +++ b/pym/gentoolkit/imlate/imlate @@ -0,0 +1,480 @@ +#!/usr/bin/python +# Copyright 1999-2010 Gentoo Foundation +# Distributed under the terms of the GNU General Public License v2 +# $Id$ +# Author: Christian Ruppert <idl0r@gentoo.org> + +# Python 2.6 compatibility +from __future__ import print_function + +VERSION = "1.0.1" + +# works just with stable keywords! +MAIN_ARCH = "auto" # can be overridden by -m ARCH +TARGET_ARCH = "auto" # can be overridden by -t ARCH +# auto means e.g.: +# MAIN_ARCH = amd64 +# TARGET_ARCH = ~amd64 +# That will show you general stable candidates for amd64. +# The arch will be taken from your portage settings (e.g. make.conf). + +################################ +# do not change anything below # +################################ + +from os.path import join, basename +from sys import stderr, stdout +from os import stat +from time import time +from xml.dom import minidom, NotFoundErr +from xml.parsers.expat import ExpatError +# TODO: just import needed stuff to safe memory/time and maybe use "as foo" +import portage +import portage.versions + +if __name__ == "__main__": + from optparse import OptionParser + from time import gmtime, strftime + +# override/change portage module settings +def _portage_settings( var, value, settings = None ): + if not settings: + settings = portage.settings + + settings.unlock() + settings[var] = value + # backup_changes is very important since it can cause trouble, + # if we do not backup our changes! + settings.backup_changes( var ) + settings.lock() + +# add stuff to our imlate dict +def _add_ent( imlate, cat, pkg, ver, our_ver ): + if not cat in list(imlate.keys()): + imlate[cat] = {} + if not pkg in list(imlate[cat].keys()): + imlate[cat][pkg] = [] + + imlate[cat][pkg].append( ver ) + imlate[cat][pkg].append( our_ver ) + + return imlate + +def _fill( width, line, fill = " " ): + while len( line ) < width: + line = "%s%s" % ( str( line ), str( fill ) ) + return line + +# create a hopefully pretty result +def show_result( conf, pkgs ): + # X - len(colX) = space to fill + col1 = 40 + col2 = 20 + + _header = "%s candidates for 'gentoo' on '%s'" + _helper = "category/package[:SLOT] our version best version" + _cand = "" + header = "" + + if conf["FILE"] == "stdout": + out = stdout + elif conf["FILE"] == "stderr": + out = stderr + else: + out = open( conf["FILE"], "w" ) + + if conf["STABLE"] and conf["KEYWORD"]: + _cand = "%i Stable and %i Keyword(~)" % ( conf["STABLE_SUM"], + conf["KEYWORD_SUM"] ) + elif conf["STABLE"]: + _cand = "%i Stable" % conf["STABLE_SUM"] + elif conf["KEYWORD"]: + _cand = "%i Keyword(~)" % conf["KEYWORD_SUM"] + + header = _header % ( _cand, conf["MAIN_ARCH"] ) + + print("Generated on: %s" % conf["TIME"], file=out) + print(_fill( len( header ), "", "=" ), file=out) + print(header, file=out) + print(_fill( len( header ), "", "=" ), file=out) + print(file=out) + + print(_helper, file=out) + print(_fill( len( _helper ), "", "-" ), file=out) + + for cat in sorted( pkgs.keys() ): + print("%s/" % cat, file=out) + for pkg in sorted( pkgs[cat].keys() ): + print("%s%s%s" % ( _fill( col1, ( " %s" % pkg ) ), + _fill( col2, pkgs[cat][pkg][1] ), + pkgs[cat][pkg][0] ), file=out) + + if conf["FILE"] != "stdout": + out.close() + +def _get_metadata(metadata, element, tag): + values = [] + + try: + metadatadom = minidom.parse(metadata) + except ExpatError as e: + raise ExpatError("%s: %s" % (metadata, e,)) + + try: + elements = metadatadom.getElementsByTagName(element) + if not elements: + return values + except NotFoundErr: + return values + + try: + for _element in elements: + node = _element.getElementsByTagName(tag) + + if tag == "herd" and (not node or not node[0].childNodes): +# print >> stderr, "'%s' is missing a <herd> tag or it is empty," % metadata +# print >> stderr, "please file a bug at https://bugs.gentoo.org and refer to http://www.gentoo.org/proj/en/devrel/handbook/handbook.xml?part=2&chap=4" + values.append("no-herd") + continue + + try: + values.append(node[0].childNodes[0].data) + except IndexError: + pass + except NotFoundErr: + raise NotFoundErr("%s: Malformed input: missing 'flag' tag(s)" % (metadata)) + + metadatadom.unlink() + return values + +def is_maintainer(maintainer, metadata): + data = [] + + if maintainer == None: + return True + + mtainer = maintainer.split(",") + + data = _get_metadata(metadata, "maintainer", "email") + + if not data and len(maintainer) == 0: + return True + elif not data and len(maintainer) > 0: + return False + else: + for addy in data: + for contact in mtainer: + if addy == contact: + return True + if addy.startswith(contact): + return True + return False + +def is_herd(herd, metadata): + data = [] + + if herd == None: + return True + + hrd = herd.split(",") + data = _get_metadata(metadata, "pkgmetadata", "herd") + + if not data and len(herd) == 0: + return True + elif not data and len(herd) > 0: + return False + else: + for hd in data: + for hd2 in hrd: + if hd == hd2: + return True + if hd.startswith(hd2): + return True + + return False + + +# fetch a list of arch (just stable) packages +# -* is important to be sure that just arch is used +def get_packages( conf ): + _pkgs = {} + + _portage_settings( "ACCEPT_KEYWORDS", ( "-* %s" % str( conf["TARGET_ARCH"] ) ), + conf["portdb"].settings ) + + for cp in conf["portdb"].dbapi.cp_all(): + cpvrs = [] + slots = {} + + if conf["USER_PKGS"]: + if not cp in conf["USER_PKGS"] and not basename(cp) in conf["USER_PKGS"]: + continue + + # None is important to match also on empty string + if conf["MAINTAINER"] != None: + if not is_maintainer(conf["MAINTAINER"], join(conf["PORTDIR"], cp, "metadata.xml")): + continue + if conf["HERD"] != None: + if not is_herd(conf["HERD"], join(conf["PORTDIR"], cp, "metadata.xml")): + continue + + cpvrs = conf["portdb"].dbapi.match( cp ) + + for cpvr in cpvrs: + slot = conf["portdb"].dbapi.aux_get( cpvr, ["SLOT"] )[0] + if not slot in slots: + slots[slot] = [] + slots[slot].append(cpvr) + + for slot in sorted(slots): + cpvr = portage.versions.best( slots[slot] ) + + if cpvr: + ( cat, pkg, ver, rev ) = portage.versions.catpkgsplit( cpvr ) + + if not cat in list(_pkgs.keys()): + _pkgs[cat] = {} + if not pkg in list(_pkgs[cat].keys()): + _pkgs[cat][pkg] = [] + + if rev != "r0": + ver = "%s-%s" % ( ver, rev ) + + _pkgs[cat][pkg].append( ver ) + + return _pkgs + +# compare get_packages() against MAIN_ARCH +def get_imlate( conf, pkgs ): + _portage_settings( "ACCEPT_KEYWORDS", ( "-* %s" % str( conf["MAIN_ARCH"] ) ), + conf["portdb"].settings ) + + stable = str( conf["MAIN_ARCH"].lstrip("~") ) + testing = "~%s" % stable + exclude = "-%s" % stable + exclude_all = "-*" + + imlate = {} + + for cat in sorted( pkgs.keys() ): + for pkg in sorted( pkgs[cat].keys() ): + for vr in pkgs[cat][pkg]: + cpvr = "" + abs_pkg = "" + kwds = "" + our = "" + our_ver = "" + mtime = 0 + slot = 0 + + # 0 = none(default), 1 = testing(~arch), 2 = stable(arch), + # 3 = exclude(-arch), 4 = exclude_all(-*) + # -* would be overridden by ~arch or arch + kwd_type = 0 + + cpvr = "%s/%s-%s" % ( cat, pkg, vr ) + + # absolute ebuild path for mtime check + abs_pkg = join( conf["PORTDIR"], cat, pkg, basename( cpvr ) ) + abs_pkg = "%s.ebuild" % str( abs_pkg ) + + kwds = conf["portdb"].dbapi.aux_get( cpvr, ["KEYWORDS"] )[0] + + # FIXME: %s is bad.. maybe even cast it, else there are issues because its unicode + slot = ":%s" % conf["portdb"].dbapi.aux_get( cpvr, ["SLOT"] )[0] + if slot == ":0": + slot = "" + + # sorted() to keep the right order + # e.g. -* first, -arch second, arch third and ~arch fourth + # -* -foo ~arch + # example: -* would be overridden by ~arch + for kwd in sorted( kwds.split() ): + if kwd == stable: + kwd_type = 2 + break + elif kwd == exclude: + kwd_type = 3 + break + elif kwd == exclude_all: + kwd_type = 4 + elif kwd == testing: + kwd_type = 1 + break + + # ignore -arch and already stabilized packages + if kwd_type == 3 or kwd_type == 2: + continue + # drop packages with -* and without ~arch or arch + # even if there is another version which includes arch or ~arch + if kwd_type == 4: + continue + # drop "stable candidates" with mtime < 30 days + # Shall we use gmtime/UTC here? + if kwd_type == 1: + mtime = int( ( time() - stat( abs_pkg ).st_mtime ) / 60 / 60 / 24 ) + if mtime < conf["MTIME"]: + continue + + # look for an existing stable version + our = portage.versions.best( conf["portdb"].dbapi.match( "%s/%s%s" % ( cat, pkg, slot ) ) ) + if our: + _foo = portage.versions.pkgsplit( our ) + our_ver = _foo[1] + if _foo[2] != "r0": + our_ver = "%s-%s" % ( our_ver, _foo[2] ) + else: + our_ver = "" + + # we just need the version if > our_ver + if our_ver: + if portage.versions.vercmp( our_ver, vr ) >= 0: + continue + + if kwd_type == 1 and conf["STABLE"]: + imlate = _add_ent( imlate, cat, ("%s%s" % (pkg, slot)), vr, our_ver ) + conf["STABLE_SUM"] += 1 + elif kwd_type == 0 and conf["KEYWORD"]: + conf["KEYWORD_SUM"] += 1 + imlate = _add_ent( imlate, cat, ( "~%s%s" % (pkg, slot) ), + vr, our_ver ) + + return imlate + +# fetch portage related settings +def get_settings( conf = None ): + if not isinstance( conf, dict ) and conf: + raise TypeError("conf must be dict() or None") + if not conf: + conf = {} + + # TODO: maybe we should improve it a bit ;) + mysettings = portage.config( config_incrementals = portage.const.INCREMENTALS, local_config = False ) + + if conf["MAIN_ARCH"] == "auto": + conf["MAIN_ARCH"] = "%s" % mysettings["ACCEPT_KEYWORDS"].split(" ")[0].lstrip("~") + if conf["TARGET_ARCH"] == "auto": + conf["TARGET_ARCH"] = "~%s" % mysettings["ACCEPT_KEYWORDS"].split(" ")[0].lstrip("~") + + # TODO: exclude overlay categories from check + if conf["CATEGORIES"]: + _mycats = [] + for _cat in conf["CATEGORIES"].split(","): + _cat = _cat.strip() + _mycats.append(_cat ) + if _cat not in mysettings.categories: + raise ValueError("invalid category for -C switch '%s'" % _cat) + mysettings.categories = _mycats + + # maybe thats not necessary because we override porttrees below.. + _portage_settings( "PORTDIR_OVERLAY", "", mysettings ) + trees = portage.create_trees() + trees["/"]["porttree"].settings = mysettings + portdb = trees["/"]["porttree"] + portdb.dbapi.settings = mysettings + portdb.dbapi.porttrees = [portage.portdb.porttree_root] + # does it make sense to remove _all_ useless stuff or just leave it as it is? + #portdb.dbapi._aux_cache_keys.clear() + #portdb.dbapi._aux_cache_keys.update(["EAPI", "KEYWORDS", "SLOT"]) + + conf["PORTDIR"] = portage.settings["PORTDIR"] + conf["portdb"] = portdb + + return conf + + +# just for standalone +def main(): + conf = {} + pkgs = {} + + parser = OptionParser( version = "%prog " + VERSION ) + parser.usage = "%prog [options] [category/package] ..." + parser.disable_interspersed_args() + + parser.add_option( "-f", "--file", dest = "filename", action = "store", type = "string", + help = "write result into FILE [default: %default]", metavar = "FILE", default = "stdout" ) + parser.add_option( "-m", "--main", dest = "main_arch", action = "store", type = "string", + help = "set main ARCH (e.g. your arch) [default: %default]", metavar = "ARCH", default = MAIN_ARCH ) + parser.add_option( "-t", "--target", dest = "target_arch", action = "store", type = "string", + help = "set target ARCH (e.g. x86) [default: %default]", metavar = "ARCH", default = TARGET_ARCH ) + parser.add_option( "--mtime", dest = "mtime", action = "store", type = "int", + help = "set minimum MTIME in days [default: %default]", metavar = "MTIME", default = 30 ) + + # TODO: leave a good comment here (about True/False) :) + parser.add_option( "-s", "--stable", dest = "stable", action = "store_true", default = False, + help = "just show stable candidates (e.g. -s and -k is the default result) [default: True]" ) + parser.add_option( "-k", "--keyword", dest = "keyword", action = "store_true", default = False, + help = "just show keyword candidates (e.g. -s and -k is the default result) [default: True]" ) + + parser.add_option( "-M", "--maintainer", dest = "maintainer", action = "store", type = "string", + help = "Show only packages from the specified maintainer", metavar = "MAINTAINER", default = None) + + parser.add_option( "-H", "--herd", dest = "herd", action = "store", type = "string", + help = "Show only packages from the specified herd", metavar = "HERD", default = None) + +# # EXPERIMENTAL +# parser.add_option( "-e", "--experimental", dest = "experimental", action = "store_true", default = False, +# help = "enables experimental functions/features (have a look for # EXPERIMENTAL comments in the source) [default: %default]" ) + + parser.add_option( "-C", "--category", "--categories", dest = "categories", action = "store", default = None, + metavar = "CATEGORIES", + help = "just check in the specified category/categories (comma separated) [default: %default]") + + ( options, args ) = parser.parse_args() + + if len( args ) > 0: + conf["USER_PKGS"] = args + else: + conf["USER_PKGS"] = [] + + # cleanup optparse + try: + parser.destroy() + except AttributeError: + # to be at least python 2.4 compatible + del parser._short_opt + del parser._long_opt + del parser.defaults + + # generated timestamp (UTC) + conf["TIME"] = strftime( "%a %b %d %H:%M:%S %Z %Y", gmtime() ) + + # package counter + conf["KEYWORD_SUM"] = 0 + conf["STABLE_SUM"] = 0 + + if not options.main_arch in portage.archlist and options.main_arch != "auto": + raise ValueError("invalid MAIN ARCH defined!") + if not options.target_arch in portage.archlist and options.target_arch != "auto": + raise ValueError("invalid TARGET ARCH defined!") + + conf["MAIN_ARCH"] = options.main_arch + conf["TARGET_ARCH"] = options.target_arch + + conf["FILE"] = options.filename + conf["MTIME"] = options.mtime + + if not options.stable and not options.keyword: + conf["STABLE"] = True + conf["KEYWORD"] = True + else: + conf["STABLE"] = options.stable + conf["KEYWORD"] = options.keyword + +# conf["EXPERIMENTAL"] = options.experimental + conf["CATEGORIES"] = options.categories + + conf["MAINTAINER"] = options.maintainer + conf["HERD"] = options.herd + + # append to our existing + conf = get_settings( conf ) + pkgs = get_packages( conf ) + pkgs = get_imlate( conf, pkgs ) + + show_result( conf, pkgs ) + +if __name__ == "__main__": + main() + diff --git a/pym/gentoolkit/imlate/imlate.1 b/pym/gentoolkit/imlate/imlate.1 new file mode 100644 index 0000000..b9163a4 --- /dev/null +++ b/pym/gentoolkit/imlate/imlate.1 @@ -0,0 +1,48 @@ +.TH "imlate" "1" "1.0.0" "Christian Ruppert" "gentoolkit-dev" +.SH "NAME" +.LP +imlate \- Displays candidates for keywords for an architecture based upon a target architecture. +.SH "SYNTAX" +.LP +imlate [options] + + +.SH "OPTIONS" +.TP +.B \-\-version +show program's version number and exit +.TP +.B \-h, \-\-help +show this help message and exit +.TP +.B \-f FILE, \-\-file=FILE +write result into FILE [default: stdout] +.TP +.B \-m ARCH, \-\-main=ARCH +set main ARCH (e.g. your arch) [default: amd64] +.TP +.B \-t ARCH, \-\-target=ARCH +set target ARCH (e.g. x86) [default: x86] +.TP +.B \-\-mtime=MTIME +set minimum MTIME in days [default: 30] +.TP +.B \-s, \-\-stable +just show stable candidates (e.g. \-s and \-k is the default result) [default: True] +.TP +.B \-k, \-\-keyword +just show keyword candidates (e.g. \-s and \-k is the default result) [default: True] +.TP +.B \-M MAINTAINER, \-\-maintainer=MAINTAINER +Show only packages from the specified maintainer +.TP +.B \-H HERD, \-\-herd=HERD +Show only packages from the specified herd +.TP +.B \-C CATEGORIES, \-\-category=CATEGORIES, \-\-categories=CATEGORIES +just check in the specified category/categories (comma separated) [default: none] +.SH "AUTHORS" +.LP +Christian Ruppert <idl0r@gentoo.org> +.SH "BUGS" +Please report any bugs to http://bugs.gentoo.org |