From: karltk Date: Fri, 27 Feb 2004 10:17:14 +0000 (-0000) Subject: Late commit... X-Git-Tag: gentoolkit-0.2.4.3~414 X-Git-Url: http://git.tremily.us/?a=commitdiff_plain;h=95490bd11fabf4a616c72c41c14a93a4bbb60181;p=gentoolkit.git Late commit... svn path=/; revision=88 --- diff --git a/trunk/ChangeLog b/trunk/ChangeLog index c7bf972..464e4b3 100644 --- a/trunk/ChangeLog +++ b/trunk/ChangeLog @@ -1,3 +1,8 @@ +2004-02-08 Karl Trygve Kalleberg + * Added ebump + * Added gensync + * Added epkgmove, closes #36663. + 2004-02-06 Marius Mauch * fixed bugs #40159, #39798, #39652, #39596, #39293 * changed etcat and equery behavior for ambigous package names, diff --git a/trunk/Makefile b/trunk/Makefile index 480c23f..27bc559 100644 --- a/trunk/Makefile +++ b/trunk/Makefile @@ -22,7 +22,7 @@ dist: dist-gentoolkit-dev: mkdir -p release/gentoolkit-dev-$(VERSION)$(RELEASE_TAG) - for x in ekeyword echangelog ego ; do \ + for x in ekeyword echangelog ego ebump gensync epkgmove; do \ ( cd src/$$x ; make distdir=release/gentoolkit-dev-$(VERSION)$(RELEASE_TAG) dist ) \ done cp {Makefile,AUTHORS,README,README.Developer,TODO,COPYING,NEWS,ChangeLog} release/gentoolkit-dev-$(VERSION)$(RELEASE_TAG)/ @@ -73,6 +73,6 @@ install-gentoolkit-dev: install -m 0644 {AUTHORS,ChangeLog,COPYING,NEWS,README,README.Developer,TODO} $(docdir)/ - for x in ekeyword echangelog ego ; do \ + for x in ekeyword echangelog ego ebump gensync epkgmove ; do \ ( cd src/$$x ; make DESTDIR=$(DESTDIR) install ) \ done diff --git a/trunk/TODO b/trunk/TODO index 6078bc1..d39ea85 100644 --- a/trunk/TODO +++ b/trunk/TODO @@ -25,6 +25,11 @@ - write a Gentoolkit Guide - write efeatures for turning on/off FEATURES in make.conf - look at ekeys, ewatch +- revision bump tool + - bump versioned files in filesdir + - -m for changelog entry + - use ~/.gentoo/gentoolkit/ebump.conf + - use /etc/gentoolkit/ebump.conf - should be removed: - etc-update - epm @@ -32,3 +37,4 @@ - add 'clean' target + check esearch, eupdatedb: separate package for now + diff --git a/trunk/src/ebump/AUTHORS b/trunk/src/ebump/AUTHORS new file mode 100644 index 0000000..2432e06 --- /dev/null +++ b/trunk/src/ebump/AUTHORS @@ -0,0 +1,5 @@ +Maintainer: +Karl Trygve Kalleberg + +Original author: +Karl Trygve Kalleberg diff --git a/trunk/src/ebump/ChangeLog b/trunk/src/ebump/ChangeLog new file mode 100644 index 0000000..8343792 --- /dev/null +++ b/trunk/src/ebump/ChangeLog @@ -0,0 +1,2 @@ +2004-02-08 Karl Trygve Kalleberg + * Initial import diff --git a/trunk/src/ebump/Makefile b/trunk/src/ebump/Makefile new file mode 100644 index 0000000..ec91deb --- /dev/null +++ b/trunk/src/ebump/Makefile @@ -0,0 +1,20 @@ +# Copyright 2004 Karl Trygve Kalleberg +# 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,TODO,ChangeLog,ebump,ebump.1} ../../$(distdir)/src/ebump/ + +install: all + install -m 0755 ebump $(bindir)/ + install -d $(docdir)/ebump + install -m 0644 {AUTHORS,README,TODO,ChangeLog} $(docdir)/ebump/ + install -m 0644 ebump.1 $(mandir)/ diff --git a/trunk/src/ebump/README b/trunk/src/ebump/README new file mode 100644 index 0000000..c81835c --- /dev/null +++ b/trunk/src/ebump/README @@ -0,0 +1,18 @@ + +Package : ebump +Version : 0.1.0 +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/trunk/src/ebump/TODO b/trunk/src/ebump/TODO new file mode 100644 index 0000000..e69de29 diff --git a/trunk/src/ebump/ebump b/trunk/src/ebump/ebump new file mode 100755 index 0000000..4daf33e --- /dev/null +++ b/trunk/src/ebump/ebump @@ -0,0 +1,352 @@ +#! /bin/sh +# +# Copyright (c) 2004 Karl Trygve Kalleberg +# Copyright (c) Gentoo Technologies, Inc. +# Licensed under the GNU General Public License, version 2 +# +# Maintainer: Karl Trygve Kalleberg + +__version__="0.1.0" +__author__="Karl Trygve Kalleberg" +__email__="" +__description__="Ebuild version bumping tool" + + + +die() { + echo $1 > /dev/stderr + exit -1 +} + +einfo() { + if [ ${opt_verbosity} -gt 2 ] ; then + echo $* + fi +} + +print_version() { + echo "${__description__}, v${__version__}" + echo "Copyright (c) 2004 ${__author__} ${__email__}" + echo "Copyright (c) 2004 Gentoo Technologies, Inc." + echo "Licensed under the GNU General Public License, version 2" +} + +print_usage() { + echo "Usage: ebump 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 " -C|--no-cvs do not add to CVS" + echo " -m|--message append message to ChangeLog" + echo " -d|--delete-old delete previous revision from CVS (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 +} + +# +# Find closes ebuild to ${1}, if any +# +find_ebuild() { + f=${1} + + if [ -f ${1} ] ; then + echo ${1} + fi + + if [ -f ${1}.ebuild ] ; then + echo ${1} + 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() { + ebuild_arg=${1} + + # Files to add to CVS + addfiles="" + # Files to remove from CVS + delfiles="" + + if [ -z ${ebuild_arg} ] ; then + print_usage + exit + fi + + # + # Try to find a matching ebuild + # + + ebuild_name=$(find_ebuild ${ebuild_arg}) + if [ -z ${ebuild_name} ] ; then + die "Could not find ${ebuild_arg}" + fi + einfo "Processing ebuild ${ebuild_name}" + + # + # Bump revision suffix (or add one) + # + + PF=$(basename ${ebuild_name} .ebuild) + PN=$(splitname name ${PF}) + PV=$(splitname version ${PF}) + rev=$(splitname revision ${PF}) + PV_norev=$(splitname vernorev ${PF}) + newPF=${PN}-${PV_norev}-r$[rev+1] + +# echo $PF / $PN / $PV / $rev / $PV_norev / $newPF + + einfo "Bumped ${PF}.ebuild to ${newPF}.ebuild" + + cp ${PF}.ebuild ${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. + + 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 + + bn=$(basename ${x}) + dn=$(dirname ${x}) + + 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" > /dev/stderr + else + cp -a ${dn}/${bn} ${dn}/${newbn} + 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 + +# echo "addfiles ${addfiles}" +# echo "delfiles ${delfiles}" + + # + # (Optional) Add ChangeLog entry + # + + if [ "${opt_add_changelog}" == "y" ] && + [ -f ChangeLog ] ; then + + # Add ChangeLog entry + + curdate=$(LC_TIME="us" date +"%02d %b %Y") + cp ChangeLog ChangeLog.old + ( + # Use header (four first lines) of the old ChangeLog + head -n 4 ChangeLog.old + + # Write new entry + + echo "*${newPF} ${curdate}" + echo + echo " ${curdate}; ${AUTHORNAME} <${AUTHOREMAIL}> ${filelist}" + + # If we don't have a commit message, add comment + if [ -z "${opt_commitmessage}" ] ; then + echo " # INSERT ENTRY HERE" + echo + else + echo " ${opt_commitmessage}" + echo + fi + + # Write tail of old ChangeLog + nl=$(wc -l ChangeLog.old | cut -c 0-8) + tail -n $[nl - 4] ChangeLog.old + ) > ChangeLog + rm ChangeLog.old + + einfo "Added ChangeLog entry" + fi + + # + # (Optional) Add CVS entry for all new files + # + + if [ "${opt_add_cvs}" == "y" ] ; then + + # Add all new files to CVS + for x in ${addfiles} ; do + if [ -d ${x} ] ; then + find ${x} | xargs echo cvs add + else + cvs add ${x} + fi + done + einfo "Added ${addfiles} to CVS" + fi + + + # + # (Optional) Delete previous entry + # + + if [ "${opt_delete_old}" == "y" ] ; then + + for x in ${delfiles} ; do + cvs remove -f ${x} + done + einfo "Removed ${delfiles} from CVS" + fi + +} + +original_params=${#} + +# +# Global options +# +opt_verbosity=1 +opt_warn_on_delete=y +opt_add_changelog=y +opt_add_cvs=y +opt_bump_auxfiles=y +opt_delete_old=n +opt_commitmessage="" + +load_options + +skip=0 +while [ ${#} -gt 0 ] ; do + arg=${1} + shift + if [ ${skip} -gt 0 ] ; then + skip=$[skip-1] + else + case ${arg} in + -h|--help) + print_usage + exit 0 + ;; + -m|--message) + opt_commitmessage="${1}" + skip=1 + ;; + -C|--no-cvs) + opt_add_cvs=n + ;; + -V|--version) + print_version + exit + ;; + -v|--verbose) + opt_verbosity=$[opt_verbosity + 1] + ;; + -q|--quiet) + opt_verbosity=0 + ;; + -d|--delete-old) + opt_delete_old=y + ;; + *) + ebuild_arg=${arg} + ;; + esac + fi +done + +process_ebuild ${ebuild_arg} + +# TODO: +# - put cli parser into separate functions diff --git a/trunk/src/ebump/ebump.1 b/trunk/src/ebump/ebump.1 new file mode 100644 index 0000000..6a64a0a --- /dev/null +++ b/trunk/src/ebump/ebump.1 @@ -0,0 +1,110 @@ +.TH "ebump" "1" "0.1.0" "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 CVS, and a +dummy ChangeLog entry will be made. + +.LP +You must stand in the directory of the ebuild to be bumped. + +.SH "OPTIONS" +.LP +\fB\-C\fR +.br +\fB--no-cvs\fB +.IP +Do not add new files to CVS. + +.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\-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 CVS. 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_cvs\fR (default \fIy\fR) - add new files to CVS +.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~/.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\fR and \fIego\fR. + +.SH "AUTHORS" +.LP +Karl Trygve Kalleberg + diff --git a/trunk/src/ego/AUTHORS b/trunk/src/ego/AUTHORS new file mode 100644 index 0000000..36d5bfd --- /dev/null +++ b/trunk/src/ego/AUTHORS @@ -0,0 +1 @@ +Aron Griffis diff --git a/trunk/src/ego/Makefile b/trunk/src/ego/Makefile new file mode 100644 index 0000000..53e57ac --- /dev/null +++ b/trunk/src/ego/Makefile @@ -0,0 +1,18 @@ +# Copyright 2004 Karl Trygve Kalleberg +# Copyright 2004 Gentoo Technologies, Inc. +# Distributed under the terms of the GNU General Public License v2 +# +# $Header$ + +include ../../makedefs.mak + +all: + +dist: + mkdir -p ../../$(distdir)/src/ego/ + cp {Makefile,AUTHORS,README,TODO,ChangeLog,ego} ../../$(distdir)/src/ego/ + +install: all + install -d $(docdir)/ego + install -m 0644 {AUTHORS,README,ego} $(docdir)/ego/ + diff --git a/trunk/src/epkgmove/AUTHORS b/trunk/src/epkgmove/AUTHORS new file mode 100644 index 0000000..38fdca1 --- /dev/null +++ b/trunk/src/epkgmove/AUTHORS @@ -0,0 +1,2 @@ +Maintainer: +Ian Leitch diff --git a/trunk/src/epkgmove/ChangeLog b/trunk/src/epkgmove/ChangeLog new file mode 100644 index 0000000..e69de29 diff --git a/trunk/src/epkgmove/Makefile b/trunk/src/epkgmove/Makefile new file mode 100644 index 0000000..e1c8619 --- /dev/null +++ b/trunk/src/epkgmove/Makefile @@ -0,0 +1,20 @@ +# Copyright 2004 Karl Trygve Kalleberg +# 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/epkgmove/ + cp {Makefile,AUTHORS,README,TODO,ChangeLog,epkgmove} ../../$(distdir)/src/epkgmove/ + +install: all + install -m 0755 epkgmove $(bindir)/ + install -d $(docdir)/epkgmove + install -m 0644 {AUTHORS,README,TODO,ChangeLog} $(docdir)/epkgmove/ +# install -m 0644 epkgmove.1 $(mandir)/ diff --git a/trunk/src/epkgmove/README b/trunk/src/epkgmove/README new file mode 100644 index 0000000..e58b92a --- /dev/null +++ b/trunk/src/epkgmove/README @@ -0,0 +1,16 @@ + +Package : epkgmove +Version : 0.3 +Author : See AUTHORS + +MOTIVATION + +Eases moving around ebuilds in the Portage CVS tree. + +MECHANICS + +N/A + +IMPROVEMENTS + +N/A diff --git a/trunk/src/epkgmove/TODO b/trunk/src/epkgmove/TODO new file mode 100644 index 0000000..e69de29 diff --git a/trunk/src/epkgmove/epkgmove b/trunk/src/epkgmove/epkgmove new file mode 100644 index 0000000..e06d66e --- /dev/null +++ b/trunk/src/epkgmove/epkgmove @@ -0,0 +1,266 @@ +#!/usr/bin/env python2 +# Copyright 1999-2003 Gentoo Technologies, Inc. +# Distributed under the terms of the GNU General Public License v2 +# $Header$ +# +# Author: +# Ian Leitch +# +# ChangeLog: +# +# 02 Jan 2004 - epkgmove-0.3, +# - Check exit status of commands, die if non zero +# - Check if PORTDIR is a CVS repository +# - Name changed to epkgmove +# +# 02 Jan 2004 - pkgmove-0.2, +# - Code cleanups +# - Take only a catgegory name as destination argument +# - Wrote the AddUpdate() function - thanks to lanius +# - Catch SIGINT +# - err() now prints to stderr +# - strip trailing /'s from input +# - use -Qf flags on cvs commands +# - Recursively find all files to remove/add +# - Tested on local repository - command changes accordingly +# - Update categories before working in them +# +# 28 Dec 2003 - pkgmove-0.1, +# - 0.1 Beta Release +# + +import os, re, portage, signal +from output import * + +__author__ = "Ian Leitch" +__email__ = "port001@gentoo.org" +__productname__ = "epkgmove" +__version__ = "0.3" +__description__ = "A simple tool for moving packages in CVS" + +def main(): + + signal.signal(signal.SIGINT, sighandler) + + global portdir, ignore, cvsrmcmd, cvscommitcmd, cvsupcmd, cvsaddcmd, devnull + + portdir = portage.settings["PORTDIR"] + ignore = ("CVS") + cvsrmcmd = "cvs -Qf rm" + cvscommitcmd = "cvs -Qf commit -m" + cvsupcmd = "cvs -Qf up -dPC ." + cvsaddcmd = "cvs -Qf add" + devnull = "> /dev/null" + + if len(sys.argv) != 3: + print + err("Expected two arguments as input.") + help() + sys.exit(1) + + Checkcwd() + CheckArgs() + print "%s Moving %s to %s..." % (green(" *"), turquoise(location), yellow(destination)) + BackupPkg() + RemovePackage() + AddPackage() + AddUpdate() + CleanUp() + print "%s %s successfully moved to %s." % (green(" *"), turquoise(location), yellow(destination)) + +def help(): + + print + print "%s %s [ %s ] [ %s ]" % (white("Usage:"), turquoise(__productname__), green("location"), green("destination")) + print " %s: Expects a package name in the format 'category/pkg' e.g net-im/gaim" % (green("location")) + print " %s: Expects a category name" % (green("destination")) + +def about(): + + print + print "%s %s %s" % (white("About:"), turquoise(__productname__), green(__version__)) + print " \"%s\"" % (__description__) + print " By %s <%s>" % (__author__, __email__) + +def docmd(cmd): + + retval = os.system(cmd) + if retval != 0: + err("Command '%s' failed with exit status %d." % (cmd, retval)) + sys.exit(1) + +def err(msg): + + sys.stderr.write("%s ERROR: %s\n" % (red("!!!"), msg)) + +def sighandler(signal_number=None, stack_frame=None): + + err("Caught SIGINT; exiting...\n") + sys.exit(1) + os.kill(0,signal.SIGKILL) + +def Checkcwd(): + + if os.getcwd() != portdir: + err("I'm not in PORTDIR, take me there!") + sys.exit(1) + + files = os.listdir(portdir) + + for file in files: + if not os.path.isdir(file): + files.remove(file) + + if "CVS" not in files: + err("No CVS directory, this doesn't look like a repository!") + sys.exit(1) + +def UpdateCats(): + + print "%s Updating %s & %s..." % (green(" *"), locategory, decategory) + + os.chdir(os.path.join(portdir, locategory)) + docmd(cvsupcmd) + os.chdir(os.path.join(portdir, decategory)) + docmd(cvsupcmd) + +def CheckArgs(): + + global fulllocation, location, fulldestination, destination, locategory, lopkg, decategory + + (locategory, lopkg) = sys.argv[1].strip('/').split('/') + location = os.path.join(locategory, lopkg) + decategory = sys.argv[2].strip('/') + fulllocation = os.path.join(portdir, location) + fulldestination = os.path.join(portdir, decategory, lopkg) + destination = os.path.join(decategory, lopkg) + + if re.search('/', decategory): + err("Expected category name as destination argument.") + sys.exit(1) + + if destination == location: + err("Don't waste my time...") + sys.exit(1) + + if not os.path.exists(os.path.join(portdir, decategory)): + err("No such category '%s'" % (decategory)) + sys.exit(1) + + UpdateCats() + + if not os.path.exists(fulllocation): + err("'%s' Invalid 'category/pkg'." % (location)) + sys.exit(1) + + if os.path.exists(fulldestination): + err("Destination location '%s' already exists." % (destination)) + sys.exit(1) + + if not os.path.exists(os.path.join(portdir, "profiles/updates")): + err("There doesn't seem to be a 'profiles/updates' directory here, I need it for later.") + sys.exit(1) + +def BackupPkg(): + + print "%s Backing-up %s to /tmp..." % (green(" *"), turquoise(location)) + docmd("cp -R %s /tmp" % (fulllocation)) + +def RemovePackage(): + + def RemoveFiles(arg, dir, files): + if os.path.basename(dir) not in ignore: + for file in files: + if not os.path.isdir(os.path.join(dir, file)): + print " <<< %s" % (os.path.join(dir.strip('./'), file)) + os.unlink(os.path.join(dir, file)) + docmd("%s %s" % (cvsrmcmd, os.path.join(dir, file))) + + print "%s Removing %s from CVS:" % (green(" *"), turquoise(location)) + os.chdir(fulllocation) + os.path.walk('.', RemoveFiles , None) + os.chdir("..") + + print "%s Running '%s 'Moving to %s''..." % (green(" *"), cvscommitcmd, destination) + docmd("%s 'Moving to %s' %s" % (cvscommitcmd, destination, devnull)) + docmd("rm -rf " + fulllocation) + + print "%s Checking if package still remains in CVS..." % (green(" *")) + docmd(cvsupcmd) + + if not os.path.exists(fulllocation): + print "%s %s successfully removed from CVS." % (green(" *"), turquoise(location)) + else: + err("Remnants of %s still remain in CVS." % (turquoise(location))) + +def AddPackage(): + + def AddFiles(arg, dir, files): + global dirhi + dirhi = "" + if os.path.basename(dir) not in ignore: + if os.path.basename(dir) != lopkg: + (rubbish, dirhi) = dir.split("tmp/%s/" % (lopkg)) + print " >>> %s/" % (dirhi) + os.mkdir(os.path.join(fulldestination, dirhi)) + docmd("%s %s" % (cvsaddcmd, os.path.basename(dir))) + os.chdir(os.path.join(fulldestination, dirhi)) + for file in files: + if not os.path.isdir(os.path.join(dir, file)): + print " >>> %s" % (os.path.join(dirhi, os.path.basename(file))) + docmd("cp %s ." % (os.path.join(dir, file))) + docmd("%s %s" % (cvsaddcmd, file)) + os.chdir(fulldestination) + + print "%s Adding %s to CVS:" % (green(" *"), turquoise(destination)) + + os.chdir(os.path.join(portdir, decategory)) + print " >>> %s/" % (lopkg) + os.mkdir(lopkg) + docmd("%s %s" % (cvsaddcmd,lopkg)) + os.chdir(lopkg) + os.path.walk("/tmp/%s" % (lopkg), AddFiles , None) + os.chdir(fulldestination) + print "%s Running 'echangelog 'Moved from %s to %s.''..." % (green(" *"), location, destination) + docmd("echangelog 'Moved from %s to %s.' %s" % (location, destination, devnull)) + + print "%s Running '%s'..." % (green(" *"), cvscommitcmd) + docmd("%s 'Moved from %s to %s.' %s" % (cvscommitcmd, location, destination, devnull)) + + print "%s %s successfully added to CVS." % (green(" *"), turquoise(destination)) + +def AddUpdate(): + + updatefiles = [] + + print "%s Logging move in 'profiles/updates'..." % (green(" *")) + os.chdir(os.path.join(portdir, "profiles/updates")) + docmd(cvsupcmd) + + for file in os.listdir("."): + if file not in ignore: + (q, y) = file.split("-") + updatefiles.append(y + "-" + q) + + updatefiles.sort() + (y, q) = updatefiles[-1].split("-") + upfile = q + "-" + y + + print " >>> %s" % (upfile) + + f = open(upfile, 'a') + f.write("move %s %s\n" % (location, destination)) + f.close() + + docmd("%s 'Moved from %s to %s' %s" % (cvscommitcmd, location, destination, devnull)) + + os.chdir(portdir) + +def CleanUp(): + + print "%s Removing back-up..." % (green(" *")) + docmd("rm -rf /tmp/%s" % (lopkg)) + +if __name__ == "__main__": + main() + diff --git a/trunk/src/gensync/AUTHORS b/trunk/src/gensync/AUTHORS new file mode 100644 index 0000000..389c51b --- /dev/null +++ b/trunk/src/gensync/AUTHORS @@ -0,0 +1,5 @@ +Maintainer: +Karl Trygve Kalleberg + +Original Author: +Matthew Schick diff --git a/trunk/src/gensync/ChangeLog b/trunk/src/gensync/ChangeLog new file mode 100644 index 0000000..8343792 --- /dev/null +++ b/trunk/src/gensync/ChangeLog @@ -0,0 +1,2 @@ +2004-02-08 Karl Trygve Kalleberg + * Initial import diff --git a/trunk/src/gensync/Makefile b/trunk/src/gensync/Makefile new file mode 100644 index 0000000..6462b1e --- /dev/null +++ b/trunk/src/gensync/Makefile @@ -0,0 +1,25 @@ +# Copyright 2004 Karl Trygve Kalleberg +# 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/gensync/ + cp Makefile AUTHORS README TODO ChangeLog \ + gensync gensync.1 gensync.conf \ + bmg-main.syncsource bmg-gnome-current.syncsource \ + ../../$(distdir)/src/gensync/ + +install: all + install -m 0755 gensync $(bindir)/ + install -d $(docdir)/gensync + install -m 0644 {AUTHORS,README,TODO,ChangeLog} $(docdir)/gensync/ + install -m 0644 gensync.1 $(mandir)/ + install -d $(DESTDIR)/etc/gensync + install -m 0644 gensync.conf {bmg-main,bmg-gnome-current}.syncsource $(DESTDIR)/etc/gensync/ diff --git a/trunk/src/gensync/README b/trunk/src/gensync/README new file mode 100644 index 0000000..cf54dec --- /dev/null +++ b/trunk/src/gensync/README @@ -0,0 +1,16 @@ + +Package : gensync +Version : 0.1.0 +Author : See AUTHORS + +MOTIVATION + +Gensync handles rsyncing to multiple sync sources. + +MECHANICS + +N/A + +IMPROVEMENTS + +N/A diff --git a/trunk/src/gensync/TODO b/trunk/src/gensync/TODO new file mode 100644 index 0000000..733a959 --- /dev/null +++ b/trunk/src/gensync/TODO @@ -0,0 +1,4 @@ +- Add using cvs to sync +- Update portage cache after sync +- Add overlay dirs defined in /etc/conf.d/gensync to /etc/make.conf +- Sync up with Marius on the multiple overlay code diff --git a/trunk/src/gensync/bmg-gnome-current.syncsource b/trunk/src/gensync/bmg-gnome-current.syncsource new file mode 100644 index 0000000..51dd908 --- /dev/null +++ b/trunk/src/gensync/bmg-gnome-current.syncsource @@ -0,0 +1,18 @@ + +# This id must be unique among all the ids in /etc/gensync/*.syncsource +id="bmg-gnome-current" + +# This is a human-readable description of the source +description="BreakMyGentoo GNOME current" + +# The rsync url +rsync="rsync://rsync.breakmygentoo.net/gnome-current" + +# By default, the overlay directory is set to ${base_overlay}/${id}, +# where base_overlay is picked from /etc/gensync/gensync.conf +# +# You may specify a different relative path, such as +overlay="bmg-gnome-current.alternative" +# +# Or an entirely new absolute path (remeber to create the path first) +#overlay="/my/absolute/path" diff --git a/trunk/src/gensync/bmg-main.syncsource b/trunk/src/gensync/bmg-main.syncsource new file mode 100644 index 0000000..8d74ca2 --- /dev/null +++ b/trunk/src/gensync/bmg-main.syncsource @@ -0,0 +1,18 @@ + +# This id must be unique among all the ids in /etc/gensync/*.syncsource +id="bmg-main" + +# This is a human-readable description of the source +description="BreakMyGentoo main tree" + +# The rsync url +rsync="rsync://rsync.breakmygentoo.net/bmg-overlay" + +# By default, the overlay directory is set to ${base_overlay}/${id}, +# where base_overlay is picked from /etc/gensync/gensync.conf +# +# You may specify a different relative path, such as +overlay="bmg-main.alternative" +# +# Or an entirely new absolute path (remeber to create the path first) +#overlay="/my/absolute/path" diff --git a/trunk/src/gensync/gensync b/trunk/src/gensync/gensync new file mode 100755 index 0000000..99c696b --- /dev/null +++ b/trunk/src/gensync/gensync @@ -0,0 +1,207 @@ +#!/usr/bin/python +# +# Simple program to rsync from various 3rd party ebuild servers. +# +# +# Copyright (c) 2004, Matthew Schick (original author) +# Copyright (c) 2004, Gentoo Technologies, Inc +# Copyright (c) 2004, Karl Trygve Kalleberg +# +# Distributed under the terms of the GNU General Public License v2 + +__author__ = "Matt Schick , Karl Trygve Kalleberg " +__email__ = "karltk@gentoo.org" +__version__ = "0.1.0" +__productname__ = "gensync" +__description__ = "Gentoo Overlay Sync Tool" + + +import fileinput +import portage +import sys +import os +from output import * + +class ConfigDefaults: + def __init__(self): + self.rsync_timeout = 0 + self.base_overlay = "/usr/local/overlays" + self.confdir = '/etc/gensync' + self._init_from_file() + self._setup_rsync() + def _init_from_file(self): + try: + ins = open("/etc/gensync/gensync.conf") + for x in ins.readlines(): + # Skip line if it's a comment or not well-formed + if x.find("=") == -1 or \ + (len(x) and x[0] == "#"): + continue + (attrib, value) = x.split() + value = value.strip().strip('"') + if attrib == "rsync_timeout": + self.rsync_timeout = value + elif attrib == "base_overlay": + self.base_overlay = value + except: + pass + + def _setup_rsync(self): + if self.rsync_timeout == 0: + try: + self.rsync_timeout = portage.settings["RSYNC_TIMEOUT"] + except: + self.rsync_timeout = 180 + self.rsync_command = "/usr/bin/rsync " + \ + "-rlptDvz --progress " + \ + "--delete --delete-after " + \ + "--timeout=" + str(self.rsync_timeout) + " " \ + "--exclude='distfiles/*' " + \ + "--exclude='local/*' " + \ + "--exclude='packages/*' " + +Config = ConfigDefaults() + +class SyncSource: + """Contains all details about an upstream rsync source.""" + def __init__(self,filename): + self.id = "#unset#" + self.name = "#unset#" + self.url = "#unset#" + self.overlay = "#unset#" + self._init_from_file(filename) + + if self.overlay == "#unset#": + self.overlay = Config.base_overlay + "/" + self.id + elif len(self.overlay) and self.overlay[0] != "/": + self.overlay = Config.base_overlay + "/" + self.overlay + elif len(self.overlay) and self.overlay[0] == "/": + pass + else: + print "Malformed overlay, '" + self.overlay + "'" + sys.exit(-2) + + def _init_from_file(self, filename): + """Loads configuration settings from a .syncsource file.""" + ins = open(filename) + for x in ins.readlines(): + # Skip line if it's a comment or not well-formed + if x.find("=") == -1 or \ + (len(x) and x[0] == "#"): + continue + attrib,value = x.split("=") + value = value.strip().strip('"') + if attrib == "id": + self.id = value + elif attrib == "description": + self.name = value + elif attrib == "rsync": + self.url = value + elif attrib == "overlay": + self.overlay = value + def perform_sync(self): + """Syncs with upstream source.""" + cmd = Config.rsync_command + self.url +"/* " + self.overlay + exitcode = portage.spawn(cmd, portage.settings, free=1) + + def dump(self,ous=sys.stdout): + ous.write("id =\"%s\"\n" % (self.id) + \ + "name =\"%s\"\n" % (self.name) + \ + "rsync =\"%s\"\n" % (self.url) + \ + "overlay=\"%s\"\n\n" % (self.overlay)) + +class SyncSourceManager: + """Holds information on all known upstream rsync sources.""" + def __init__(self): + self.sources = [] + self._load_source_info() + def _load_source_info(self): + for x in os.listdir(Config.confdir): + if x.rfind(".syncsource") > 0: + ss = SyncSource(Config.confdir + "/" + x) + self.sources.append(ss) + def list_sources(self): + for x in self.sources: + x.dump() + def get_sync_source(self, source_id): + for x in self.sources: + if x.id == source_id: + return x + return None + +def print_version(): + print __productname__ + "(" + __version__ + ") - " + \ + __description__ + print "Author(s): " + __author__ + +def print_usage(): + print white("Usage: ") + turquoise(__productname__) + \ + yellow(" ") + green("repo-id-1 repo-id-2 ...") + print "where " + yellow("") + " is one of" + print yellow(" -l, --list-sources") + " - list known rsync sources" + print yellow(" -C, --no-color") + " - turn off colours" + print yellow(" -h, --help") + " - this help screen" + print yellow(" -V, --version") + " - display version info" + + +def parse_args(cliargs): + + cmd = "sync-sources" + args = [] + options = [] + + for x in cliargs: + if x in ["-V", "--version"]: + cmd = "print-version" + break + elif x in ["-h", "--help"]: + cmd = "print-usage" + break + elif x in ["-C", "--no-color"]: + options.append("nocolor") + break + elif x in ["-l", "--list-sources"]: + cmd = "list-sources" + else: + args.append(x) + return (cmd, args, options) + +def main(): + + # Setup colors, as per system defaults + if (not sys.stdout.isatty()) or \ + (portage.settings["NOCOLOR"] in ["yes","true"]): + nocolor() + + # Parse arguments + (cmd, args, options) = parse_args(sys.argv[1:]) + + # Turn off color, if specified by cmdline switch + if "nocolor" in options: + nocolor() + + # Initialise sync source manager + ssmgr = SyncSourceManager() + + # Perform selected command + if cmd == "list-sources": + ssmgr.list_sources() + elif cmd == "print-version": + print_version() + elif cmd == "print-usage": + print_usage() + elif cmd == "sync-sources": + for x in args: + repo = ssmgr.get_sync_source(x) + if repo: + print "Syncing '%s' into '%s'" % \ + (repo.id, repo.overlay) + repo.perform_sync() + else: + print red("Unknown command '" + cmd + "'") + +if __name__ == "__main__": + try: + main() + except KeyboardInterrupt: + print "Operation aborted by user keypress!" diff --git a/trunk/src/gensync/gensync.1 b/trunk/src/gensync/gensync.1 new file mode 100644 index 0000000..17d85fc --- /dev/null +++ b/trunk/src/gensync/gensync.1 @@ -0,0 +1,75 @@ +.TH gensync 1 "0.1.0" "Gentoolkit" "Gentoo Administration" +.SH "NAME" +.LP +gensync \- Gentoo: Overlay Sync Tool +.SH "SYNTAX" +.LP +gensync [\fIoption\fP] <\fIrepo-id-1 repo-id-2 ...\fP> + +.SH "DESCRIPTION" + +.LP + +\fIgensync\fR synchronises your machine against an upstream repository +of ebuild files, called a sync source. Normally, Portage will only +synchronise against the main Portage tree maintained by the Gentoo +project. However, there exist development trees with auxiliary ebuild +files that may occasionally be of interest to the adventurous power +user, such as the \fIbreakmygentoo.net\fR sync source. + +This tool can be used to synchronise against these alternative sync +sources. + +.SH "OPTIONS" +.LP +\fB\-l\fR +.br +\fB--list-sources\fB +.IP +List available sync sources known to \fIgensync\fR. + +.LP +\fB\-V\fR +.br +\fB--version\fB +.IP +Display version information and exit. + +.LP +\fB\-C\fR +.br +\fB--no-color\fB +.IP +Turn off colors. + +.LP +\fB\-h\fR +.br +\fB\--help\fR +.IP +Display help. + +.SH "CONFIGURATION FILES" +.LP +\fB/etc/gensync/gensync.conf\fR +.IP +The main configuration file, commented and self-explanatory + +.LP +\fB/etc/gensync/*.syncsource\fR +.IP +Per-sync source configuration files, describing the sync source to +\fIgensync\fR. + + +.SH "SEE ALSO" +.LP +The rest of the utilities in \fIapp-portage/gentoolkit-dev\fR, such as +\fIechangelog\fR, \fIebump\fR and \fIego\fR. + +.SH "AUTHORS" +.LP +Matthew Schick (original author) +.br +Karl Trygve Kalleberg + diff --git a/trunk/src/gensync/gensync.conf b/trunk/src/gensync/gensync.conf new file mode 100644 index 0000000..389e020 --- /dev/null +++ b/trunk/src/gensync/gensync.conf @@ -0,0 +1,8 @@ + +# By default, we ask Portage about the preferred timeout +#rsync_timeout = 180 + +# Normally, each sync source will be placed under +# ${base_overlay}/${syncsource-id}, where the 'syncsource-id' is a unique +# identifier, specified inside the individual .syncsource file +base_overlay = /usr/local/overlays diff --git a/trunk/src/lintool/AUTHORS b/trunk/src/lintool/AUTHORS new file mode 100644 index 0000000..fe436cb --- /dev/null +++ b/trunk/src/lintool/AUTHORS @@ -0,0 +1 @@ +Karl Trygve Kalleberg diff --git a/trunk/src/lintool/COPYING b/trunk/src/lintool/COPYING new file mode 100644 index 0000000..60549be --- /dev/null +++ b/trunk/src/lintool/COPYING @@ -0,0 +1,340 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc. + 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Library General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) 19yy + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) 19yy name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Library General +Public License instead of this License. diff --git a/trunk/src/lintool/ChangeLog b/trunk/src/lintool/ChangeLog new file mode 100644 index 0000000..0e8c1e9 --- /dev/null +++ b/trunk/src/lintool/ChangeLog @@ -0,0 +1,71 @@ +2004-02-18 Karl Trygve Kalleberg + * Moved from separate CVS module to be part of gentoolkit-dev + +2002-10-31 Karl Trygve Kalleberg + * Fixed \$Header$ comment + * Fixed off-by-one in line numbering + +2002-10-30 Karl Trygve Kalleberg + * Now defaults to showing all details + * Added --no-details + * Better LICENSE parsing + * Better references to policy. + * Line numbers for whitespace testing. + * Released 0.2.4 + +2002-08-12 Karl Trygve Kalleberg + * Added more header tests + * Added initial tests for SLOT, KEYWORD and LICENSE + +2002-08-10 Karl Trygve Kalleberg + * Spurious space checker works again. + * Added Makefile, COPYING, README, AUTHORS, NEWS + +2002-07-22 Karl Trygve Kalleberg + * Changed A=${P}.tar.gz check to ^A=.*, as per Seemant's suggestion. + +2002-07-17 Karl Trygve Kalleberg + * Added TestLicense to ebuild.py + * Added --aux-license-dir= + * Added MunchieFormatter + * Added --formatter= + +2002-07-10 Karl Trygve Kalleberg + * Refactored; split tests into changelog, digest and ebuild-specific + * Added TestSyntax test to digest.py + * Added TestHeaders, TestConstructPresence, TestIndentation to + changelog.py + +2002-05-15 Karl Trygve Kalleberg + + * Fixed test for Author: and Maintainer: to warn about their + deprecation and recommend the use of ChangeLog instead. + * Added (E) and (W) prefixes to messages printed by --show-details + * Added --tests=- to allow turning off a + particular test easily. + * Added statistics output at the bottom. + * Updated man page to reflect new option feature. + * Fixed the --show-details, --show-separate annoyance. + * Fixed use flags parsing to disregard some false positives. + +2002-05-13 Karl Trygve Kalleberg + * Fixed --list-tests which was incorrectly --listTests + +2002-04-30 Karl Trygve Kalleberg + * Added 's cleanups + * Improved error reporting; now separates into errors and + warnings. + * Improved TestSpaces to check for non-conformant + line-continuations. + * Added TestUseFlags that checks that an ebuild only uses + sanctioned use flags (read from /usr/portage/profiles/use.desc) + * Added SLOT= to list desired env vars (warns if missing) + * Added LICENSE= to list of required env vars (errors if missing) + * Improved error messages + * Added --tests= option to allow the user to specify which tests + to run + * Added --list-tests option that lists available tests + * Updated man page. + +2002-03-22 Karl Trygve Kalleberg + * Added this ChangeLog diff --git a/trunk/src/lintool/NEWS b/trunk/src/lintool/NEWS new file mode 100644 index 0000000..e69de29 diff --git a/trunk/src/lintool/README b/trunk/src/lintool/README new file mode 100644 index 0000000..1494aad --- /dev/null +++ b/trunk/src/lintool/README @@ -0,0 +1,23 @@ + +Package: Lintool +Version: 0.1.3 +Author : Karl Trygve Kalleberg + + +MOTIVATION + +Lintool is a "lint" utility for the Gentoo Linux distribution. It is a +tool that tests ebuilds, changelogs and package digests for +well-formedness. It's intended to aid Gentoo Linux developers avoid most +common mistakes when packaging software for Gentoo. + +MECHANICS + +To check an ebuild, do lintool --ebuild +To check a ChangeLog, do lintool --changelog +To check a package digest, do lintoo --digest + +IMPROVEMENTS + +Any suggestions for improvements should be sent to karltk@gentoo.org, or +added as a bug assigned to me. diff --git a/trunk/src/lintool/lintool.1 b/trunk/src/lintool/lintool.1 new file mode 100644 index 0000000..11883e8 --- /dev/null +++ b/trunk/src/lintool/lintool.1 @@ -0,0 +1,41 @@ +.TH lintool "1" "March 2002" "gentoolkit 0.1.3" +.SH NAME +lintool \- manual page for the lintool program, a program that checks the +santity of ebuild scripts. +.SH SYNOPSIS +.B lintool +\fI\fR \fIebuilds...\fR +.SH DESCRIPTION +Lintool checks if a set of +.I ebuilds +conforms to the ebuild style guide. This is not +(yet) an exhaustive test, so your ebuild might be broken even if lintool +thinks it's okay. +.PP +.SH OPTIONS +.TP +\fB--no-summary\fI +Turn off total summary for all tests run on all ebuilds. +.TP +\fB--show-separate\fI +Show short summary of tests for each ebuild checked. +.TP +\fB--show-details\fI +Show full details of tests for each ebuild checked +.TP +\fB--tests=test1,test2,..\fI +Run only the tests specified to this option. Default is to run all +tests. One can turn off a particular test by pretending it with minus (e.g. +.I --tests=-TestUseFlags,-TestSpaces +). A list of tests can be obtained from +.I --list-tests +.TP +\fB--list-tests\fI +List available tests. +.SH AUTHORS +Karl Trygve Kalleberg , 2002 +.SH "SEE ALSO" +ebuild(5) +.TP +The \fI/usr/sbin/lintool\fR script. + diff --git a/trunk/src/lintool/lintool.py b/trunk/src/lintool/lintool.py new file mode 100755 index 0000000..721c744 --- /dev/null +++ b/trunk/src/lintool/lintool.py @@ -0,0 +1,320 @@ +#! /usr/bin/python +# +# Copyright 2002 Gentoo Technologies, Inc +# Distributed under the terms of the GNU General Public License v2.0 +# Author Karl Trygve Kalleberg +# +# About: +# lintool aims to check the stylistic and syntactical correctness for +# ebuilds, changelogs and digest files for the Gentoo packaging system. +# +# TODO +# +# - Make HTMLFormatter +# + +VERSION="0.2.4" + +import sys +import getopt + +from lintool import ebuild, changelog, digest + +class TextFormatter: + def section(self, s): + print "\n" + "-"*79 + print " " + s + "\n" + def bullet(self, s): + print "* " + s + def sub(self, s): + print "- " + s + def subwarn(self, s): + print "- (W) " + s + def suberr(self, s): + print "- (E) " + s + def subsub(self, s): + print " |" + s + def subsubwarn(self, s): + print " (W) |" + s + def subsuberr(self, s): + print " (E) |" + s + def line(self,s): + print s + def div(self, left, right): + l = len(left) + r = len(right) + return left + " " * (78-l-r) + right + +class MunchieFormatter: + def section(self, s): + print "[lintool] " + "-" * (78 - len("[lintool] ")) + print "[lintool] " + s + "\n" + def bullet(self, s): + print "[lintool] * " + s + def sub(self, s): + print "[lintool] - " + s + def subwarn(self, s): + print "[lintool] - (W) " + s + def suberr(self, s): + print "[lintool] - (E) " + s + def subsub(self, s): + print "[lintool] |" + s + def subsubwarn(self, s): + print "[lintool] (W) |" + s + def subsuberr(self, s): + print "[lintool] (E) |" + s + def line(self,s): + print "[lintool] " + s + def div(self, left, right): + l = len("[lintool] " + left) + r = len(right) + return left + " " * (78-l-r) + right + +formatters = { "text" : TextFormatter(), "munchie" : MunchieFormatter() } + +def extractFilename(path): + return path + +def runTests(tests,results,ins): + for j in tests: + j.reset() + + ln = 1 + for i in ins.readlines(): + for j in tests: + j.checkLine(i, ln) + ln += 1 + + hasWarning = 0 + hasError = 0 + for j in xrange(len(tests)): + if tests[j].hasErrors(): + results[j][0] += 1 + hasError = 1 + if tests[j].hasWarnings(): + results[j][1] += 1 + hasWarning = 1 + return (hasError, hasWarning) + +def showStatus(options,tests,formatter,file): + if options['showDetails'] or options['showSeparate']: + formatter.section("Status for " + file) + for j in tests: + if options['showSeparate'] or options['showDetails']: + l = len(j.getDesc()) + formatter.bullet(formatter.div(j.getDesc(), ": " + j.getStatus())) + if options['showDetails']: + j.report() + elif options['showShort']: + allOK = 1 + for j in tests: + if j.hasErrors(): + allOK = 0 + break + if allOK: + formatter.div(file, ": OK") + else: + formatter.div(file, ": Not OK") + # else fall through the bottom + +def usage(opts): + print sys.argv[0], "[options] ebuild [ebuild ebuild ... ]" + print + + if opts: + print "Where [options] include:" + for (short,long_,desc) in opts: + short_ = '' + for s in short: + short_ = short_ + '-' + s + ',' + long_ = '--' + long_ + opt = short_ + long_ + opt = opt.rjust(18) + print opt + ' ' + desc + print + +def parse_opts(argv): + options = { 'showSeparate': 0, + 'showTotal': 1, + 'showDetails': 1, + 'showShort': 1, + 'listTests': 0, + 'desiredTests': 0, + 'testMode' : "ebuild", + 'licenseDirs' : [ "/usr/portage/licenses" ], + 'formatter' : 'text' + } + + opts = (('', 'show-separate', + 'Show short summary of tests for each ebuild checked'), + + ('v', 'version', + 'Show program version'), + + ('', 'no-summary', + 'Do not show total summary'), + + ('', 'no-details', + 'Do not show full details of tests for each ebuild checked'), + + ('', 'ebuild', + 'Files to check are ebuilds'), + + ('', 'changelog', + 'Files to check are changelogs'), + + ('', 'digest', + 'Files to check are digests'), + + ('', 'tests=', + 'Comma-separated list of tests to run'), + + ('', 'list-tests', + 'List available tests'), + + ('', 'from-file=', + 'Read ebuilds from '), + + ('', 'formatter=', + "Use 'text' (default) or 'munchie' formatter"), + + ('', 'aux-license-dir=', + 'Add to directories to search for licenses'), + + ('?h', 'help', + 'Show this help'), + ) + + short_options = '' + long_options = [] + for (short,long_,desc) in opts: + short_options = short_options + short + if '=' in long_: + long_ = long_.split('=', 1)[0] + '=' + long_options.append(long_) + + try: + (option_list,args) = getopt.getopt(sys.argv[1:], short_options, long_options) + except getopt.GetoptError, details: + print 'Error parsing command line:',str(details) + sys.exit(1) + + for (option,value) in option_list: + if option in [ '--no-details' ]: + options['showShort'] = 1 + options['showDetails'] = 0 + elif option in [ '--show-separate' ]: + options['showShort'] = 0 + options['showSeparate'] = 1 + elif option in [ '--no-summary']: + options['showTotal'] = 0 + elif option in [ '--from-file' ]: + lines = open(value, 'r').readlines() + lines = [o.strip() for o in lines] + args = lines + args + elif option in [ '--tests' ]: + options['desiredTests'] = value.split(",") + elif option in [ '--formatter' ]: + options['formatter'] = value + elif option in [ '--list-tests' ]: + options['listTests'] = 1 + elif option in [ '--ebuild' ]: + options['testMode'] = 'ebuild' + elif option in [ '--changelog' ]: + options['testMode'] = 'changelog' + elif option in [ '--digest' ]: + options['testMode'] = 'digest' + elif option in [ '--aux-license-dir' ]: + options['licenseDirs'].append(value) + elif option in [ '-v', '--version' ]: + print "Lintool " + VERSION + sys.exit(0) + elif option in [ '-h', '-?', '--help' ]: + usage(opts) + sys.exit(0) + else: + # shouldn't ever happen. better to be safe + print "Unknown option - '%s'!" % (option) + sys.exit(1) + + return (options,args) + +def main(): + (options,args) = parse_opts(sys.argv[1:]) + + formatter = formatters[options['formatter']] + + # Get test suite for given mode + if options['testMode'] == "ebuild": + available_tests = ebuild.getTests(formatter, options) + elif options['testMode'] == "changelog": + available_tests = changelog.getTests(formatter, options) + elif options['testMode'] == "digest": + available_tests = digest.getTests(formatter, options) + + # List available tests, if that was the users request + if options['listTests']: + maxlen = 0 + for i in available_tests: + maxlen = max(len(i.__class__.__name__), maxlen) + for i in available_tests: + n = i.__class__.__name__ + print n + " " * (maxlen - len(n)) + " - " + i.getDesc() + + # Quit with short usage string, if no params given + if len(args) == 0: + usage(None) + sys.exit(1) + + # Create final list of tests to run + tests = [] + notTests = [] + if options['desiredTests']: + for i in options['desiredTests']: + for j in available_tests: + if len(i) and i[0] == "-": + notTests.append(i[1:]) + if j.__class__.__name__ == i: + tests.append(j) + else: + tests = available_tests + + if len(notTests): + for i in available_tests: + if i.__class__.__name__ not in notTests: + tests.append(i) + + results = [[0, 0] for x in range(len(tests))] + + # Set up for test run + numFiles = 0 + totalErrors = 0 + totalWarnings = 0 + + # Iterate through all files given as arguments, testing each file + # against the final list of tests + for i in args: + fn = extractFilename(i) + ins = open(i, "r") + numFiles += 1 + (hasError, hasWarning) = runTests(tests,results,ins) + totalErrors += hasError + totalWarnings += hasWarning + showStatus(options,tests,formatter,fn) + + # Show totals, if options allow it + if options['showTotal']: + formatter.section(formatter.div("Summary for all " + str(numFiles) + " " + options['testMode'] + "(s) checked", "#errors/warns")) + for i in xrange(len(tests)): + l = len(tests[i].getDesc()) + formatter.line(formatter.div(tests[i].getDesc(), ": %3d / %3d" % (results[i][0], results[i][1]))) + formatter.line(formatter.div("Total number of ebuilds with errors", \ + "%3d (%3d%%)" % (totalErrors, totalErrors*100/numFiles))) + formatter.line(formatter.div("Total number of ebuilds with warnings", \ + "%3d (%3d%%)" % (totalWarnings, totalWarnings*100/numFiles))) + if totalErrors: + sys.exit(1) + +if __name__ == "__main__": + main() + diff --git a/trunk/src/lintool/lintool/__init__.py b/trunk/src/lintool/lintool/__init__.py new file mode 100644 index 0000000..4d2e3a6 --- /dev/null +++ b/trunk/src/lintool/lintool/__init__.py @@ -0,0 +1,3 @@ +import ebuild +import changelog +import digest diff --git a/trunk/src/lintool/lintool/changelog.py b/trunk/src/lintool/lintool/changelog.py new file mode 100644 index 0000000..1dad779 --- /dev/null +++ b/trunk/src/lintool/lintool/changelog.py @@ -0,0 +1,105 @@ +# Copyright 2002 Gentoo Technologies, Inc +# Distributed under the terms of the GNU General Public License v2.0 +# Author Karl Trygve Kalleberg + +from test import Test, Regex +import re + +class TestHeaders(Test): + def __init__(self, formatter,options): + Test.__init__(self,formatter,options) + self.desc = "Testing for malformed headers" + self.re = [ (1, # append result of regex match + re.compile("^(# Copyright 1999-(2000|2001).*)"), + "Suspect copyright year"), + (1, + re.compile("^(# /home.*)"), + "Suspect path in header"), + (0, # don't append result of regex match + re.compile("^(# Author.*)"), + "Use of Author field in the header is deprecated. Put name in ChangeLog"), + (0, + re.compile("^(# Maintainer.*)"), + "Use of Maintainer field in the header is deprecated. Put name in ChangeLog"), + (1, + re.compile("^(# /space.*)"), + "Suspect path in header")] + + def checkLine(self, s, ln): + for i in self.re: + k = i[1].match(s) + if k and i[0]: + self.warnings.append(i[2] + ": " + k.groups()[0] ) + elif k and not i[0]: + self.warnings.append(i[2]) + + def report(self): + if len(self.warnings): + self.formatter.subwarn("Has illegal or suspect headers:") + for i in self.warnings: + self.formatter.subsub(i) + +class TestConstructPresence(Test): + + def __init__(self, formatter,options): + Test.__init__(self,formatter,options) + self.desc = "Testing for presence of required constructs" + self.required = [ ["# ChangeLog for " + Regex.category + "/" + Regex.PN, + None, + None, + "proper ChangeLog line" + ], + + ["\*" + Regex.P + " \([0-9]+ [A-Z][a-z][a-z] 2002\).*", + None, + None, + "proper release entry on the form *package-1.0.0 (01 Apr 2000)" + ], + + [" [0-9]+ [A-Z][a-z][a-z] 2002; .* <.*@.*> .*:", + None, + None, + "proper changelog entry" + ] + ] + + for i in self.required: + i[1] = re.compile("^(" + i[0] + ")") + + def checkLine(self, s, ln): + for i in self.required: + k = i[1].match(s) + if k: + i[2] = 1 + + def report(self): + for i in self.required: + if not i[2]: + self.formatter.suberr("Missing " + i[3]) + + def hasErrors(self): + for i in self.required: + if not i[2]: + return 1 + +class TestIndentation(Test): + + def __init__(self, formatter,options): + Test.__init__(self,formatter,options) + self.desc = "Testing for proper indentation" + self.re = re.compile("^( |[#*].|\n).*") + + def checkLine(self, s, ln): + k = self.re.match(s) + if not k: + self.errors.append(s) + + def report(self): + for i in self.errors: + print i.replace(' ','%') + +def getTests(formatter,options): + return [ TestHeaders(formatter,options), + TestConstructPresence(formatter,options), + TestIndentation(formatter,options) + ] diff --git a/trunk/src/lintool/lintool/digest.py b/trunk/src/lintool/lintool/digest.py new file mode 100644 index 0000000..5f174ae --- /dev/null +++ b/trunk/src/lintool/lintool/digest.py @@ -0,0 +1,28 @@ +# Copyright 2002 Gentoo Technologies, Inc +# Distributed under the terms of the GNU General Public License v2.0 +# Author Karl Trygve Kalleberg + +from test import Test +import re + +class TestSyntax(Test): + + def __init__(self,formatter,options): + Test.__init__(self,formatter,options) + self.desc = "Testing for correct syntax" + self.re = [ re.compile("^(MD5 [a-z0-9]+ [a-zA-Z0-9_+.-]+ [0-9]+)") ] + self.errors = [] + + def checkLine(self, s, ln): + for i in self.re: + k = i.match(s) + if not k: + self.errors.append("Invalid line in digest\n |" + s) + + def report(self): + for i in self.errors: + self.formatter.suberr(i) + + +def getTests(formatter,options): + return [ TestSyntax(formatter,options) ] diff --git a/trunk/src/lintool/lintool/ebuild.py b/trunk/src/lintool/lintool/ebuild.py new file mode 100644 index 0000000..825b4ce --- /dev/null +++ b/trunk/src/lintool/lintool/ebuild.py @@ -0,0 +1,350 @@ +# Copyright 2002 Gentoo Technologies, Inc +# Distributed under the terms of the GNU General Public License v2.0 +# Author Karl Trygve Kalleberg + +from test import Test +import re +import os +import os.path +import string + +class TestSpaces(Test): + + def __init__(self, formatter, options): + Test.__init__(self, formatter, options) + self.desc = "Testing for correct formatting" + self.re_spaces = [ re.compile("^([ ][ ]*)([a-zA-Z\.].*)"), + re.compile("(.*)([ \t]+)\n") ] + self.re_backslash = re.compile("([^#]*\S)((\s\s+|\t))\\\\") + self.reset() + + def checkLine(self, s, ln): + for r in self.re_spaces: + k = r.match(s) + if k: + spcs = k.groups()[1] + rest = k.groups()[0] + self.spaces.append((ln, spcs.replace(" ", "%").replace("\t","%") + rest)) + else: + k = self.re_backslash.match(s) + if k: + head = k.group(1) + spcs = k.group(2) + tail = "\\" + self.backslashes.append((ln, head + len(spcs) * "%" + tail)) + + def hasErrors(self): + return 0 + def hasWarnings(self): + return len(self.spaces) + len(self.backslashes) + + def reset(self): + self.spaces = [] + self.backslashes = [] + + def report(self): + if len(self.spaces): + self.formatter.subwarn("Has illegal space characters (marked by %):") + for i in self.spaces: + self.formatter.subsub("[line " + str(i[0]) + "]:" + i[1]) + if len(self.backslashes): + self.formatter.subwarn("Has illegal white space (marked by %), only one space character allowed:") + for i in self.backslashes: + self.formatter.subsub("[line " + str(i[0]) + "]:" + i[1]) + +class TestHeaders(Test): + + def __init__(self, formatter, options): + Test.__init__(self,formatter, options) + self.desc = "Testing for malformed headers" + self.want = [ [ 0, # count + re.compile("^(# Copyright .*2002.*)"), + "Copyright statement" ], + [ 0, # count + re.compile("^(# " + "\$" + "Header:.*" + "\$)"), # Don't want CVS to fix this + "$" + "Header:" + "$" ], # Don't want CVS to fix this either + [ 0, # count + re.compile("^(# Distributed under the terms of the GNU General Public License.*)"), + "GPL license" ] ] + self.dontwant = [ (1, # append result of regex match + re.compile("^(# Copyright 1999-(2000|2001).*)"), + "Suspect copyright year"), + (1, + re.compile("^(# /home.*)"), + "Suspect path in header"), + (0, # don't append result of regex match + re.compile("^(# Author.*)"), + "Use of Author field in the header is deprecated. Put name in ChangeLog"), + (0, + re.compile("^(# Maintainer.*)"), + "Use of Maintainer field in the header is deprecated. Put name in ChangeLog"), + (1, + re.compile("^(# /space.*)"), + "Suspect path in header")] + + def reset(self): + for i in self.want: + i[0] = 0 + self.errors = [] + self.warnings = [] + + def hasErrors(self): + num_error = 0 + for i in self.want: + if i[0] == 0: + num_error += 1 + return num_error + + def checkLine(self, s, ln): + for i in self.dontwant: + k = i[1].match(s) + if k and i[0]: + self.warnings.append(i[2] + ": " + k.groups()[0] ) + elif k and not i[0]: + self.warnings.append(i[2]) + for i in self.want: + k = i[1].match(s) + if k: + i[0] += 1 + + def report(self): + illegal_headers = len(self.warnings) + for i in self.want: + if i[0] == 0: + illegal_headers += 1 + if illegal_headers: + self.formatter.subwarn("Has illegal or suspect headers:") + for i in self.warnings: + self.formatter.subwarn(i) + for i in self.want: + if i[0] == 0: + self.formatter.suberr("Missing " + i[2]) + +class TestTry(Test): + + def __init__(self,formatter, options): + Test.__init__(self,formatter, options) + self.desc = "Testing for occurence of deprecated try" + self.re = [ re.compile("^([ \t][ \t]*try.*)"), + re.compile("(.*=.* try .*)") ] + + def checkLine(self, s, ln): + for i in self.re: + k = i.match(s) + if k: + self.errors.append(k.groups()[0]) + + def report(self): + if len(self.errors): + self.formatter.suberr("Uses try, which is deprecated") + for i in self.errors: + self.formatter.subsub(i) + +class TestA(Test): + + def __init__(self, formatter, options): + Test.__init__(self,formatter, options) + self.desc = "Testing for superfluous A=${P}.tar.gz" + self.re = re.compile("(^A=.*)") + + def checkLine(self, s, ln): + k = self.re.match(s) + if k: + self.errors.append(k.groups()[0]) + + def report(self): + if len(self.errors): + self.formatter.suberr("Contains superfluous " + self.errors[0]) + +class TestDepend(Test): + + def __init__(self, formatter, options): + Test.__init__(self,formatter, options) + self.desc = "Testing for empty DEPEND" + self.re = re.compile("DEPEND=\"\"") + + def checkLine(self, s, ln): + k = self.re.match(s) + if k: + self.warnings.append("") + + def report(self): + if len(self.warnings): + self.formatter.subwarn("DEPEND is suspiciously empty") + +class TestHomepage(Test): + + def __init__(self, formatter,options): + Test.__init__(self,formatter,options) + self.desc = "Testing for empty HOMEPAGE" + self.re = re.compile("HOMEPAGE=\"\"") + + def checkLine(self, s, ln): + k = self.re.match(s) + if k: + self.warnings.append("") + + def report(self): + if len(self.warnings): + self.formatter.subwarn("Is HOMEPAGE really supposed to be empty ?") + +class TestDescription(Test): + + def __init__(self, formatter, options): + Test.__init__(self,formatter, options) + self.desc = "Testing for empty DESCRIPTION" + self.re = re.compile("DESCRIPTION=\"\"") + + def checkLine(self, s, ln): + k = self.re.match(s) + if k: + self.errors.append("") + + def report(self): + if len(self.errors): + self.formatter.suberr("DESCRIPTION must not be empty") + +class TestEnvVarPresence(Test): + + def __init__(self, formatter, options): + Test.__init__(self,formatter, options) + self.desc = "Testing for presence of env vars" + self.re = [] + self.found = [] + self.required = [ ("SRC_URI", "See 2.4"), + ("DESCRIPTION", "See policy, 2.8"), + ("HOMEPAGE", "See policy, 2.8"), + ("DEPEND", "See policy, 2.2"), + ("LICENSE", "See policy, 2.6"), + ("SLOT", "See policy, 2.5"), + ("KEYWORDS", "See policy, 2.3"), + ("IUSE", "See policy, 2.7") + ] + self.desired = [ ("RDEPEND", "Is RDEPEND == DEPEND ? See policy, 2.2") ] + + + for i in self.required: + self.re.append(re.compile("^(" + i[0] + ")=")) + for i in self.desired: + self.re.append(re.compile("^(" + i[0] + ")=")) + + def checkLine(self, s, ln): + for i in self.re: + k = i.match(s) + if k: + self.found.append(k.group(1)) + + def report(self): + for i in self.required: + if i[0] not in self.found: + self.formatter.suberr("Missing " + i[0] + ". " + i[1]) + for i in self.desired: + if i[0] not in self.found: + self.formatter.subwarn("Missing " + i[0] + ". " + i[1]) + + def hasWarnings(self): + for i in self.desired: + if i[0] not in self.found: + return 1 + + def hasErrors(self): + for i in self.required: + if i[0] not in self.found: + return 1 + +class TestLicense(Test): + def __init__(self, formatter, options): + Test.__init__(self,formatter, options) + self.desc = "Testing for proper LICENSE" + self.re = re.compile("^LICENSE=\"(.*)\"") + self.license_dirs = options['licenseDirs'] + self.licenses = self.loadLicenses() + + def loadLicenses(self): + licenses = [] + for i in self.license_dirs: + try: + candidates = os.listdir(i) + except: + self.formatter.line("!!! License directory '" + i + "' does not exist") + continue + for j in candidates: + if os.path.isfile(i + "/" + j): + licenses.append(j) + return licenses + + def checkLine(self, s, ln): + k = self.re.match(s) + if k: + print k.group(1) + licenses = string.split(k.group(1), " ") + for i in licenses: + if i not in self.licenses: + self.errors.append("License '" + i + "' not known") + + def report(self): + for i in self.errors: + self.formatter.suberr(i) + +class TestUseFlags(Test): + def __init__(self, formatter, options): + Test.__init__(self,formatter, options) + self.desc = "Testing for sane USE flag usage" + self.re = re.compile("[^#]*use ([a-z0-9\+]+).*") + self.useflags = self.loadUseFlags() + + def loadUseFlags(self): + ins = open("/usr/portage/profiles/use.desc") + rex = re.compile("^([a-z0-9]+)[ \t]+-.*"); + useflags = [] + for i in ins.readlines(): + k = rex.match(i) + if k: + useflags.append(k.group(1)) + return useflags + + def checkLine(self, s, ln): + k = self.re.match(s) + if k: + flag = k.group(1) + if flag not in self.useflags: + l = k.start(1) + # We want to try and figure pretty exactly if we've hit a real instnce + # of the use command or just some random mumbling inside a string + numApostrophes = 0 + numBackticks = 0 + numTicks = 0 + for i in xrange(l,0,-1): + if s[i] == '\"' and (i == 0 or (i > 0 and s[i-1] != '\\')): + numApostrophes += 1 + if s[i] == '\'' and (i == 0 or (i > 0 and s[i-1] != '\\')): + numTicks += 1 + if s[i] == '`' and (i == 0 or (i > 0 and s[i-1] != '\\')): + numBackticks += 1 + + if numApostrophes % 2 == 0: + foundError = 1 + elif numBackticks % 2 and numTicks % 2 == 0: + foundError = 1 + else: + foundError = 0 + + if foundError: + self.errors.append("Unknown USE flag '" + flag + "'") + + def report(self): + for i in self.errors: + self.formatter.suberr(i) + + +def getTests(formatter,options): + return [ TestSpaces(formatter,options), + TestHeaders(formatter,options), + TestTry(formatter,options), + TestA(formatter,options), + TestDepend(formatter,options), + TestHomepage(formatter,options), + TestDescription(formatter,options), + TestEnvVarPresence(formatter,options), + TestUseFlags(formatter,options), + TestLicense(formatter,options) ] diff --git a/trunk/src/lintool/lintool/test.py b/trunk/src/lintool/lintool/test.py new file mode 100644 index 0000000..0cc56ff --- /dev/null +++ b/trunk/src/lintool/lintool/test.py @@ -0,0 +1,30 @@ +# Copyright 2002 Gentoo Technologies, Inc +# Distributed under the terms of the GNU General Public License v2.0 +# Author Karl Trygve Kalleberg + +class Test: + def __init__(self, formatter,options=None): + self.formatter = formatter + self.errors = [] + self.warnings = [] + def reset(self): + self.errors = [] + self.warnings = [] + def hasWarnings(self): + return len(self.warnings) + def hasErrors(self): + return len(self.errors) + def getDesc(self): + return self.desc + def getStatus(self): + if self.hasErrors(): + return "failed" + else: + return "passed" + +class Regex: + PN = "[a-zA-Z_.-]+" + PV = "[a-z0-9A-Z_.-]+" + P = PN + "-" + PV + "(-r[0-9]+)?" + category = "[a-z0-9]+-[a-z0-9]+" + full = category + "/" + P