Changes in v2:
- 'sdist' support

Changes in v3:
- version substituted properly in docs & mans
- cleaner handling of install_data
---
 .gitignore           |   1 +
 MANIFEST.in          |  18 ++
 Makefile             | 215 -----------------
 doc/Makefile         |  11 -
 doc/fragment/date    |   0
 doc/fragment/version |   1 -
 mkrelease.sh         | 141 -----------
 pym/portage/const.py |   4 +-
 setup.py             | 657 +++++++++++++++++++++++++++++++++++++++++++++++++++
 9 files changed, 678 insertions(+), 370 deletions(-)
 create mode 100644 MANIFEST.in
 delete mode 100644 Makefile
 delete mode 100644 doc/Makefile
 delete mode 100644 doc/fragment/date
 delete mode 100644 doc/fragment/version
 delete mode 100755 mkrelease.sh
 create mode 100755 setup.py

diff --git a/.gitignore b/.gitignore
index 074bb86..c2dd534 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,4 +1,5 @@
 *.py[co]
 __pycache__/
 *.class
+/build
 /tags
diff --git a/MANIFEST.in b/MANIFEST.in
new file mode 100644
index 0000000..d65c874
--- /dev/null
+++ b/MANIFEST.in
@@ -0,0 +1,18 @@
+# docs
+include DEVELOPING
+include LICENSE
+include TEST-NOTES
+
+# docbook sources
+include doc/custom.xsl
+recursive-include doc *.docbook
+
+# extra conf files used in ebuild
+include cnf/make.conf.example.*
+
+# extra files for tests
+include .portage_not_installed
+include cnf/metadata.dtd
+
+# extra scripts
+include misc/*
diff --git a/Makefile b/Makefile
deleted file mode 100644
index 9eb6e66..0000000
--- a/Makefile
+++ /dev/null
@@ -1,215 +0,0 @@
-SHELL = /bin/sh
-PN ?= portage
-PF ?= portage
-HOMEPAGE ?= http://www.gentoo.org/proj/en/portage/index.xml
-PWD ?= $(shell pwd)
-S ?= $(PWD)
-WORKDIR ?= $(PWD)
-DESTDIR = $(PWD)/image/
-srcdir = $(S)
-prefix = /usr
-sysconfdir = /etc
-exec_prefix = $(prefix)
-bindir = $(exec_prefix)/bin
-sbindir = $(exec_prefix)/sbin
-libdir = $(exec_prefix)/lib
-datarootdir = $(prefix)/share
-datadir = $(datarootdir)
-mandir = $(datarootdir)/man
-docdir = $(datarootdir)/doc/$(PF)
-htmldir = $(docdir)/html
-portage_datadir = $(datarootdir)/$(PN)
-portage_confdir = $(portage_datadir)/config
-portage_setsdir = $(portage_confdir)/sets
-portage_base = $(libdir)/$(PN)
-EPYDOC_OPTS = -qqqqq --no-frames --show-imports
-INSMODE = 0644
-EXEMODE = 0755
-DIRMODE = 0755
-SYSCONFDIR_FILES = etc-update.conf dispatch-conf.conf
-PORTAGE_CONFDIR_FILES = make.conf.example make.globals repos.conf
-LOGROTATE_FILES = elog-save-summary
-BINDIR_FILES = ebuild egencache emerge emerge-webrsync \
-       emirrordist portageq quickpkg repoman
-SBINDIR_FILES = archive-conf dispatch-conf emaint \
-       env-update etc-update fixpackages regenworld
-DOCS = ChangeLog NEWS RELEASE-NOTES
-LINGUAS ?= $(shell cd "$(srcdir)/man" && find -mindepth 1 -type d)
-
-ifdef PYTHONPATH
-       PYTHONPATH := $(srcdir)/pym:$(PYTHONPATH)
-else
-       PYTHONPATH := $(srcdir)/pym
-endif
-
-all: docbook epydoc
-
-docbook:
-       set -e; \
-       touch "$(srcdir)/doc/fragment/date"; \
-       $(MAKE) -C "$(srcdir)/doc" xhtml xhtml-nochunks
-
-epydoc:
-       set -e; \
-       env PYTHONPATH="$(PYTHONPATH)" epydoc \
-               -o "$(WORKDIR)/epydoc" \
-               --name $(PN) \
-               --url "$(HOMEPAGE)" \
-               $(EPYDOC_OPTS) \
-               $$(cd "$(srcdir)" && find pym -name '*.py' | sed \
-               -e s:/__init__.py$$:: \
-               -e s:\.py$$:: \
-               -e s:^pym/:: \
-               -e s:/:.:g \
-               | sort); \
-       rm -f "$(WORKDIR)/epydoc/api-objects.txt"; \
-
-test:
-       set -e; \
-       "$(srcdir)/pym/portage/tests/runTests.py"; \
-
-install:
-       set -e; \
-       cd "$(srcdir)/cnf"; \
-       install -d -m$(DIRMODE) "$(DESTDIR)$(sysconfdir)"; \
-       install -m$(INSMODE) $(SYSCONFDIR_FILES) "$(DESTDIR)$(sysconfdir)"; \
-       \
-       install -d -m$(DIRMODE) "$(DESTDIR)$(portage_confdir)"; \
-       cd "$(srcdir)/cnf"; \
-       install -m$(INSMODE) $(PORTAGE_CONFDIR_FILES) \
-               "$(DESTDIR)$(portage_confdir)"; \
-       install -d -m$(DIRMODE) "$(DESTDIR)$(portage_setsdir)"; \
-       cd "$(S)/cnf/sets"; \
-       install -m$(INSMODE) *.conf "$(DESTDIR)$(portage_setsdir)"; \
-       \
-       install -d -m$(DIRMODE) "$(DESTDIR)$(sysconfdir)/logrotate.d"; \
-       cd "$(srcdir)/cnf/logrotate.d"; \
-       install -m$(INSMODE) $(LOGROTATE_FILES) \
-               "$(DESTDIR)$(sysconfdir)/logrotate.d"; \
-       \
-       for x in $$(cd "$(srcdir)" && find bin -type d) ; do \
-               cd "$(srcdir)/$$x"; \
-               install -d -m$(DIRMODE) "$(DESTDIR)$(portage_base)/$$x"; \
-               files=$$(find . -mindepth 1 -maxdepth 1 -type f ! -type l); \
-               if [ -n "$$files" ] ; then \
-                       install -m$(EXEMODE) $$files \
-                               "$(DESTDIR)$(portage_base)/$$x"; \
-               fi; \
-               symlinks=$$(find . -mindepth 1 -maxdepth 1 -type l); \
-               if [ -n "$$symlinks" ] ; then \
-                       cp -P $$symlinks "$(DESTDIR)$(portage_base)/$$x"; \
-               fi; \
-       done; \
-       \
-       for x in $$(cd "$(srcdir)" && find pym/* -type d \
-               ! -path "pym/portage/tests*") ; do \
-               cd "$(srcdir)/$$x"; \
-               files=$$(echo *.py); \
-               if [ -z "$$files" ] || [ "$$files" = "*.py" ]; then \
-                       # __pycache__ directories contain no py files \
-                       continue; \
-               fi; \
-               install -d -m$(DIRMODE) "$(DESTDIR)$(portage_base)/$$x"; \
-               install -m$(INSMODE) $$files "$(DESTDIR)$(portage_base)/$$x"; \
-       done; \
-       \
-       install -d -m$(DIRMODE) "$(DESTDIR)$(bindir)"; \
-       relative_path=".."; \
-       x=$(bindir) ; \
-       y="$(portage_base)"; \
-       if [ "$${x#$(prefix)}" != "$$x" ] && \
-               [ "$${y#$(prefix)}" != "$$y" ]; then \
-               x=$${x#$(prefix)}; \
-               y=$${y#$(prefix)}; \
-       fi; \
-       x=$${x%/*}; \
-       while [ -n "$$x" ] ; do \
-               relative_path=$${relative_path}/..; \
-               x=$${x%/*}; \
-       done; \
-       relative_path=$$relative_path$$y; \
-       for x in $(BINDIR_FILES) ; do \
-               ln -sf "$$relative_path/bin/$$x" \
-                       "$(DESTDIR)$(bindir)/$$x"; \
-       done; \
-       \
-       install -d -m$(DIRMODE) "$(DESTDIR)$(sbindir)"; \
-       relative_path=".."; \
-       x=$(sbindir) ; \
-       y="$(portage_base)"; \
-       if [ "$${x#$(prefix)}" != "$$x" ] && \
-               [ "$${y#$(prefix)}" != "$$y" ]; then \
-               x=$${x#$(prefix)}; \
-               y=$${y#$(prefix)}; \
-       fi; \
-       x=$${x%/*}; \
-       while [ -n "$$x" ] ; do \
-               relative_path=$${relative_path}/..; \
-               x=$${x%/*}; \
-       done; \
-       relative_path=$$relative_path$$y; \
-       for x in $(SBINDIR_FILES) ; do \
-               ln -sf "$$relative_path/bin/$$x" \
-                       "$(DESTDIR)$(sbindir)/$$x"; \
-       done; \
-       \
-       ln -sf "$$relative_path/bin/env-update" \
-               "$(DESTDIR)$(sbindir)/update-env"; \
-       ln -sf "$$relative_path/bin/etc-update" \
-               "$(DESTDIR)$(sbindir)/update-etc"; \
-       \
-       # We install some minimal tests for use as a preinst sanity check. \
-       # These tests must be able to run without a full source tree and \
-       # without relying on a previous portage instance being installed. \
-       install -d -m$(DIRMODE) \
-               "$(DESTDIR)$(portage_base)/pym/portage/tests"; \
-       install -m$(EXEMODE) "$(srcdir)/pym/portage/tests/runTests" \
-               "$(DESTDIR)$(portage_base)/pym/portage/tests"; \
-       cd "$(srcdir)/pym/portage/tests"; \
-       install -m$(INSMODE) *.py \
-               "$(DESTDIR)$(portage_base)/pym/portage/tests"; \
-       install -d -m$(DIRMODE) \
-               "$(DESTDIR)$(portage_base)/pym/portage/tests/lint"; \
-       cd "$(srcdir)/pym/portage/tests/lint"; \
-       install -m$(INSMODE) *.py __test__ \
-               "$(DESTDIR)$(portage_base)/pym/portage/tests/lint"; \
-       \
-       install -d -m$(DIRMODE) "$(DESTDIR)$(docdir)"; \
-       cd "$(srcdir)"; \
-       install -m $(INSMODE) $(DOCS) "$(DESTDIR)$(docdir)"; \
-       \
-       for x in "" $(LINGUAS); do \
-               for y in 1 5 ; do \
-                       if [ -d "$(srcdir)/man/$$x" ]; then \
-                               cd "$(srcdir)/man/$$x"; \
-                               files=$$(echo *.$$y); \
-                               if [ -z "$$files" ] || [ "$$files" = "*.$$y" ]; 
then \
-                                       continue; \
-                               fi; \
-                               install -d -m$(DIRMODE) 
"$(DESTDIR)$(mandir)/$$x/man$$y"; \
-                               install -m$(INSMODE) *.$$y 
"$(DESTDIR)$(mandir)/$$x/man$$y"; \
-                       fi; \
-               done; \
-       done; \
-       \
-       if [ -f "$(srcdir)/doc/portage.html" ] ; then \
-               install -d -m$(DIRMODE) "$(DESTDIR)$(htmldir)"; \
-               cd "$(srcdir)/doc"; \
-               install -m$(INSMODE) *.html "$(DESTDIR)$(htmldir)"; \
-       fi; \
-       \
-       if [ -d "$(WORKDIR)/epydoc" ] ; then \
-               install -d -m$(DIRMODE) "$(DESTDIR)$(htmldir)"; \
-               cp -pPR "$(WORKDIR)/epydoc" \
-                       "$(DESTDIR)$(htmldir)/api"; \
-               cd "$(DESTDIR)$(htmldir)/api"; \
-               find . -type d | xargs chmod $(DIRMODE); \
-               find . -type f | xargs chmod $(INSMODE); \
-       fi; \
-
-clean:
-       set -e; \
-       $(MAKE) -C "$(srcdir)/doc" clean; \
-       rm -rf "$(WORKDIR)/epydoc"; \
-
-.PHONY: all clean docbook epydoc install test
diff --git a/doc/Makefile b/doc/Makefile
deleted file mode 100644
index 261a0b4..0000000
--- a/doc/Makefile
+++ /dev/null
@@ -1,11 +0,0 @@
-all: xhtml xhtml-nochunks
-
-XMLTO_FLAGS = -m custom.xsl
-man pdf txt xhtml xhtml-nochunks:
-       xmlto $@ $(XMLTO_FLAGS) portage.docbook
-
-clean distclean:
-       rm -f *.1 *.html portage.txt
-
-.PHONY: all clean distclean \
-        man pdf txt xhtml xhtml-nochunks
diff --git a/doc/fragment/date b/doc/fragment/date
deleted file mode 100644
index e69de29..0000000
diff --git a/doc/fragment/version b/doc/fragment/version
deleted file mode 100644
index f85674c..0000000
--- a/doc/fragment/version
+++ /dev/null
@@ -1 +0,0 @@
-<releaseinfo>VERSION</releaseinfo>
diff --git a/mkrelease.sh b/mkrelease.sh
deleted file mode 100755
index f9f7564..0000000
--- a/mkrelease.sh
+++ /dev/null
@@ -1,141 +0,0 @@
-#!/bin/bash
-# Copyright 2008-2014 Gentoo Foundation
-# Distributed under the terms of the GNU General Public License v2
-
-RELEASE_BUILDDIR=${RELEASE_BUILDDIR:-/var/tmp/portage-release}
-SOURCE_DIR=${RELEASE_BUILDDIR}/checkout
-BRANCH=${BRANCH:-master}
-USE_TAG=false
-CHANGELOG_REVISION=
-UPLOAD_LOCATION=
-RUNTESTS=false
-USER=
-
-usage() {
-       echo "Usage: ${0##*/} [--changelog-rev <tree-ish>] [-t|--tag] 
[-u|--upload <location>]  [--user <username>] [--runtests] <version>"
-       exit ${1:-0}
-}
-
-die() {
-       printf 'error: %s\n' "$*"
-       usage 1
-}
-
-ARGS=$(getopt -o htu: --long help,changelog-rev:,runtests,tag,upload:,user: \
-       -n "${0##*/}" -- "$@")
-[ $? != 0 ] && die "initialization error"
-
-eval set -- "${ARGS}"
-
-while true; do
-       case $1 in
-               --changelog-rev)
-                       CHANGELOG_REVISION=$2
-                       shift 2
-                       ;;
-               -t|--tag)
-                       USE_TAG=true
-                       shift
-                       ;;
-               -u|--upload)
-                       UPLOAD_LOCATION=$2
-                       shift 2
-                       ;;
-               --user)
-                       USER=$2"@"
-                       shift 2
-                       ;;
-               -h|--help)
-                       usage
-                       ;;
-               --runtests)
-                       RUNTESTS=true
-                       shift
-                       ;;
-               --)
-                       shift
-                       break
-                       ;;
-               *)
-                       die "unknown option: $1"
-                       ;;
-       esac
-done
-
-[ $# != 1 ] && die "Need version argument"
-[[ -n ${1/[0-9]*} ]] && die "Invalid version argument"
-
-VERSION=$1
-RELEASE=portage-${VERSION}
-RELEASE_DIR=${RELEASE_BUILDDIR}/${RELEASE}
-RELEASE_TARBALL="${RELEASE_BUILDDIR}/${RELEASE}.tar.bz2"
-TREE_ISH=${BRANCH}
-if [[ ${USE_TAG} == "true" ]] ; then
-       TREE_ISH="v${VERSION}"
-fi
-
-echo ">>> Cleaning working directories ${RELEASE_DIR} ${SOURCE_DIR}"
-rm -rf "${RELEASE_DIR}" "${SOURCE_DIR}" || die "directory cleanup failed"
-mkdir -p "${RELEASE_DIR}" || die "directory creation failed"
-mkdir -p "${SOURCE_DIR}" || die "mkdir failed"
-
-echo ">>> Starting GIT archive"
-git archive --format=tar ${TREE_ISH} | \
-       tar -xf - -C "${SOURCE_DIR}" || die "git archive failed"
-
-echo ">>> Building release tree"
-cp -a "${SOURCE_DIR}/"{bin,cnf,doc,man,misc,pym} "${RELEASE_DIR}/" || die 
"directory copy failed"
-cp 
"${SOURCE_DIR}/"{.portage_not_installed,DEVELOPING,LICENSE,Makefile,NEWS,README,RELEASE-NOTES,TEST-NOTES}
 \
-       "${RELEASE_DIR}/" || die "file copy failed"
-
-if [[ ${RUNTESTS} == "true" ]] ; then
-       pushd "${SOURCE_DIR}" >/dev/null
-       ./runtests.sh --python-versions=supported || die "tests failed"
-       popd >/dev/null
-fi
-
-rm -rf "${SOURCE_DIR}" || die "directory cleanup failed"
-
-echo ">>> Setting portage.VERSION"
-sed -e "s/^VERSION = .*/VERSION = \"${VERSION}\"/" \
-       -i "${RELEASE_DIR}/pym/portage/__init__.py" || \
-       die "Failed to patch portage.VERSION"
-
-echo ">>> Creating Changelog"
-git_log_opts=""
-if [[ -n ${CHANGELOG_REVISION} ]] ; then
-       git_log_opts+=" ${CHANGELOG_REVISION}^..${TREE_ISH}"
-else
-       git_log_opts+=" ${TREE_ISH}"
-fi
-skip_next=false
-git log ${git_log_opts} | fmt -w 80 -p "    " | while read -r ; do
-       if [[ ${skip_next} == "true" ]] ; then
-               skip_next=false
-       elif [[ ${REPLY} == "    svn path="* ]] ; then
-               skip_next=true
-       else
-               echo "${REPLY}"
-       fi
-done > "${RELEASE_DIR}/ChangeLog" || die "ChangeLog creation failed"
-
-cd "${RELEASE_BUILDDIR}"
-
-echo ">>> Creating release tarball ${RELEASE_TARBALL}"
-tar --owner portage --group portage -cjf "${RELEASE_TARBALL}" "${RELEASE}" || \
-       die "tarball creation failed"
-
-DISTDIR=$(portageq distdir)
-if [[ -n ${DISTDIR} && -d ${DISTDIR} && -w ${DISTDIR} ]] ; then
-       echo ">>> Copying release tarball into ${DISTDIR}"
-       cp "${RELEASE_TARBALL}" "${DISTDIR}"/ || echo "!!! tarball copy failed"
-fi
-
-if [[ -n ${UPLOAD_LOCATION} ]] ; then
-       echo ">>> Uploading ${RELEASE_TARBALL} to 
${USER}dev.gentoo.org:${UPLOAD_LOCATION}"
-       scp "${RELEASE_TARBALL}" "${USER}dev.gentoo.org:${UPLOAD_LOCATION}" || 
die "upload failed"
-else
-       du -h "${RELEASE_TARBALL}"
-fi
-
-exit 0
diff --git a/pym/portage/const.py b/pym/portage/const.py
index f518b47..acb90f9 100644
--- a/pym/portage/const.py
+++ b/pym/portage/const.py
@@ -60,8 +60,8 @@ GLOBAL_CONFIG_PATH       = "/usr/share/portage/config"
 # these variables are not used with target_root or config_root
 # NOTE: Use realpath(__file__) so that python module symlinks in site-packages
 # are followed back to the real location of the whole portage installation.
-PORTAGE_BASE_PATH        = os.path.join(os.sep, os.sep.join(os.path.realpath(
-                               __file__.rstrip("co")).split(os.sep)[:-3]))
+# NOTE: Please keep PORTAGE_BASE_PATH in one line to help substitutions.
+PORTAGE_BASE_PATH        = os.path.join(os.sep, 
os.sep.join(os.path.realpath(__file__.rstrip("co")).split(os.sep)[:-3]))
 PORTAGE_BIN_PATH         = PORTAGE_BASE_PATH + "/bin"
 PORTAGE_PYM_PATH         = os.path.realpath(os.path.join(__file__, '../..'))
 LOCALE_DATA_PATH         = PORTAGE_BASE_PATH + "/locale"  # FIXME: not used
diff --git a/setup.py b/setup.py
new file mode 100755
index 0000000..e5c3631
--- /dev/null
+++ b/setup.py
@@ -0,0 +1,657 @@
+#!/usr/bin/env python
+# Copyright 1998-2014 Gentoo Foundation
+# Distributed under the terms of the GNU General Public License v2
+
+from distutils.core import setup, Command
+from distutils.command.build import build
+from distutils.command.build_scripts import build_scripts
+from distutils.command.clean import clean
+from distutils.command.install import install
+from distutils.command.install_data import install_data
+from distutils.command.install_lib import install_lib
+from distutils.command.install_scripts import install_scripts
+from distutils.command.sdist import sdist
+from distutils.dep_util import newer
+from distutils.dir_util import mkpath, remove_tree
+from distutils.util import change_root, subst_vars
+
+import codecs, collections, errno, glob, os, os.path, re, subprocess, sys
+
+# TODO:
+# - smarter rebuilds of docs w/ 'install_docbook' and 'install_epydoc'.
+
+x_scripts = {
+       'bin': [
+               'bin/ebuild', 'bin/egencache', 'bin/emerge', 
'bin/emerge-webrsync',
+               'bin/emirrordist', 'bin/portageq', 'bin/quickpkg', 'bin/repoman'
+       ],
+       'sbin': [
+               'bin/archive-conf', 'bin/dispatch-conf', 'bin/emaint', 
'bin/env-update',
+               'bin/etc-update', 'bin/fixpackages', 'bin/regenworld'
+       ],
+}
+
+
+class x_build(build):
+       """ Build command with extra build_man call. """
+
+       def run(self):
+               build.run(self)
+               self.run_command('build_man')
+
+
+class build_man(Command):
+       """ Perform substitutions in manpages. """
+
+       user_options = [
+       ]
+
+       def initialize_options(self):
+               self.build_base = None
+
+       def finalize_options(self):
+               self.set_undefined_options('build',
+                       ('build_base', 'build_base'))
+
+       def run(self):
+               for d, files in self.distribution.data_files:
+                       if not d.startswith('$mandir/'):
+                               continue
+
+                       for source in files:
+                               target = os.path.join(self.build_base, source)
+                               mkpath(os.path.dirname(target))
+
+                               if not newer(source, target) and not 
newer(__file__, target):
+                                       continue
+
+                               print('copying and updating %s -> %s' % (
+                                       source, target))
+
+                               with codecs.open(source, 'r', 'utf8') as f:
+                                       data = f.readlines()
+                               data[0] = data[0].replace('VERSION',
+                                               self.distribution.get_version())
+                               with codecs.open(target, 'w', 'utf8') as f:
+                                       f.writelines(data)
+
+
+class docbook(Command):
+       """ Build docs using docbook. """
+
+       user_options = [
+               ('doc-formats=', None, 'Documentation formats to build (all 
xmlto formats for docbook are allowed, comma-separated'),
+       ]
+
+       def initialize_options(self):
+               self.doc_formats = 'xhtml,xhtml-nochunks'
+
+       def finalize_options(self):
+               self.doc_formats = self.doc_formats.replace(',', ' ').split()
+
+       def run(self):
+               if not os.path.isdir('doc/fragment'):
+                       mkpath('doc/fragment')
+
+               with open('doc/fragment/date', 'w'):
+                       pass
+               with open('doc/fragment/version', 'w') as f:
+                       f.write('<releaseinfo>%s</releaseinfo>' % 
self.distribution.get_version())
+
+               for f in self.doc_formats:
+                       print('Building docs in %s format...' % f)
+                       subprocess.check_call(['xmlto', '-o', 'doc',
+                               '-m', 'doc/custom.xsl', f, 
'doc/portage.docbook'])
+
+
+class epydoc(Command):
+       """ Build API docs using epydoc. """
+
+       user_options = [
+       ]
+
+       def initialize_options(self):
+               self.build_lib = None
+
+       def finalize_options(self):
+               self.set_undefined_options('build_py', ('build_lib', 
'build_lib'))
+
+       def run(self):
+               self.run_command('build_py')
+
+               print('Building API documentation...')
+
+               process_env = os.environ.copy()
+               pythonpath = self.build_lib
+               try:
+                       pythonpath += ':' + process_env['PYTHONPATH']
+               except KeyError:
+                       pass
+               process_env['PYTHONPATH'] = pythonpath
+
+               subprocess.check_call(['epydoc', '-o', 'epydoc',
+                       '--name', self.distribution.get_name(),
+                       '--url', self.distribution.get_url(),
+                       '-qq', '--no-frames', '--show-imports',
+                       '--exclude', 'portage.tests',
+                       '_emerge', 'portage', 'repoman'],
+                       env = process_env)
+               os.remove('epydoc/api-objects.txt')
+
+
+class install_docbook(install_data):
+       """ install_data for docbook docs """
+
+       user_options = install_data.user_options
+
+       def initialize_options(self):
+               install_data.initialize_options(self)
+               self.htmldir = None
+
+       def finalize_options(self):
+               self.set_undefined_options('install', ('htmldir', 'htmldir'))
+               install_data.finalize_options(self)
+
+       def run(self):
+               if not os.path.exists('doc/portage.html'):
+                       self.run_command('docbook')
+               self.data_files = [
+                       (self.htmldir, glob.glob('doc/*.html')),
+               ]
+               install_data.run(self)
+
+
+class install_epydoc(install_data):
+       """ install_data for epydoc docs """
+
+       user_options = install_data.user_options
+
+       def initialize_options(self):
+               install_data.initialize_options(self)
+               self.htmldir = None
+
+       def finalize_options(self):
+               self.set_undefined_options('install', ('htmldir', 'htmldir'))
+               install_data.finalize_options(self)
+
+       def run(self):
+               if not os.path.exists('epydoc/index.html'):
+                       self.run_command('epydoc')
+               self.data_files = [
+                       (os.path.join(self.htmldir, 'api'), 
glob.glob('epydoc/*')),
+               ]
+               install_data.run(self)
+
+
+class x_build_scripts_custom(build_scripts):
+       def finalize_options(self):
+               build_scripts.finalize_options(self)
+               if 'dir_name' in dir(self):
+                       self.build_dir = os.path.join(self.build_dir, 
self.dir_name)
+                       if self.dir_name in x_scripts:
+                               self.scripts = x_scripts[self.dir_name]
+                       else:
+                               self.scripts = set(self.scripts)
+                               for other_files in x_scripts.values():
+                                       
self.scripts.difference_update(other_files)
+
+       def run(self):
+               # group scripts by subdirectory
+               split_scripts = collections.defaultdict(list)
+               for f in self.scripts:
+                       dir_name = os.path.dirname(f[len('bin/'):])
+                       split_scripts[dir_name].append(f)
+
+               base_dir = self.build_dir
+               base_scripts = self.scripts
+               for d, files in split_scripts.items():
+                       self.build_dir = os.path.join(base_dir, d)
+                       self.scripts = files
+                       self.copy_scripts()
+
+               # restore previous values
+               self.build_dir = base_dir
+               self.scripts = base_scripts
+
+
+class x_build_scripts_bin(x_build_scripts_custom):
+       dir_name = 'bin'
+
+
+class x_build_scripts_sbin(x_build_scripts_custom):
+       dir_name = 'sbin'
+
+
+class x_build_scripts_portagebin(x_build_scripts_custom):
+       dir_name = 'portage'
+
+
+class x_build_scripts(build_scripts):
+       def initialize_option(self):
+               build_scripts.initialize_options(self)
+
+       def finalize_options(self):
+               build_scripts.finalize_options(self)
+
+       def run(self):
+               self.run_command('build_scripts_bin')
+               self.run_command('build_scripts_portagebin')
+               self.run_command('build_scripts_sbin')
+
+
+class x_clean(clean):
+       """ clean extended for doc & post-test cleaning """
+
+       def clean_docs(self):
+               def get_doc_outfiles():
+                       for dirpath, dirnames, filenames in os.walk('doc'):
+                               for f in filenames:
+                                       if f.endswith('.docbook') or f == 
'custom.xsl':
+                                               pass
+                                       else:
+                                               yield os.path.join(dirpath, f)
+
+                               # do not recurse
+                               break
+
+
+               for f in get_doc_outfiles():
+                       print('removing %s' % repr(f))
+                       os.remove(f)
+
+               if os.path.isdir('doc/fragment'):
+                       remove_tree('doc/fragment')
+
+               if os.path.isdir('epydoc'):
+                       remove_tree('epydoc')
+
+       def clean_tests(self):
+               # do not remove incorrect dirs accidentally
+               top_dir = os.path.normpath(os.path.join(self.build_lib, '..'))
+               cprefix = os.path.commonprefix((self.build_base, top_dir))
+               if cprefix != self.build_base:
+                       return
+
+               bin_dir = os.path.join(top_dir, 'bin')
+               if os.path.exists(bin_dir):
+                       remove_tree(bin_dir)
+
+               conf_dir = os.path.join(top_dir, 'cnf')
+               if os.path.islink(conf_dir):
+                       print('removing %s symlink' % repr(conf_dir))
+                       os.unlink(conf_dir)
+
+               pni_file = os.path.join(top_dir, '.portage_not_installed')
+               if os.path.exists(pni_file):
+                       print('removing %s' % repr(pni_file))
+                       os.unlink(pni_file)
+
+       def clean_man(self):
+               man_dir = os.path.join(self.build_base, 'man')
+               if os.path.exists(man_dir):
+                       remove_tree(man_dir)
+
+       def run(self):
+               if self.all:
+                       self.clean_tests()
+                       self.clean_docs()
+                       self.clean_man()
+
+               clean.run(self)
+
+
+class x_install(install):
+       """ install command with extra Portage paths """
+
+       user_options = install.user_options + [
+               # note: $prefix and $exec_prefix are reserved for Python install
+               ('system-prefix=', None, "Prefix for architecture-independent 
data"),
+               ('system-exec-prefix=', None, "Prefix for architecture-specific 
data"),
+
+               ('bindir=', None, "Install directory for main executables"),
+               ('datarootdir=', None, "Data install root directory"),
+               ('docdir=', None, "Documentation install directory"),
+               ('htmldir=', None, "HTML documentation install directory"),
+               ('mandir=', None, "Manpage root install directory"),
+               ('portage-base=', 'b', "Portage install base"),
+               ('portage-bindir=', None, "Install directory for Portage 
internal-use executables"),
+               ('portage-datadir=', None, 'Install directory for data files'),
+               ('sbindir=', None, "Install directory for superuser-intended 
executables"),
+               ('sysconfdir=', None, 'System configuration path'),
+       ]
+
+       # note: the order is important for proper substitution
+       paths = [
+               ('system_prefix', '/usr'),
+               ('system_exec_prefix', '$system_prefix'),
+
+               ('bindir', '$system_exec_prefix/bin'),
+               ('sbindir', '$system_exec_prefix/sbin'),
+               ('sysconfdir', '/etc'),
+
+               ('datarootdir', '$system_prefix/share'),
+               ('docdir', '$datarootdir/doc/$package-$version'),
+               ('htmldir', '$docdir/html'),
+               ('mandir', '$datarootdir/man'),
+
+               ('portage_base', '$system_exec_prefix/lib/portage'),
+               ('portage_bindir', '$portage_base/bin'),
+               ('portage_datadir', '$datarootdir/portage'),
+
+               # not customized at the moment
+               ('logrotatedir', '$sysconfdir/logrotate'),
+               ('portage_confdir', '$portage_datadir/config'),
+               ('portage_setsdir', '$portage_confdir/sets'),
+       ]
+
+       def initialize_options(self):
+               install.initialize_options(self)
+
+               for key, default in self.paths:
+                       setattr(self, key, default)
+               self.subst_paths = {}
+
+       def finalize_options(self):
+               install.finalize_options(self)
+
+               # substitute variables
+               new_paths = {
+                       'package': self.distribution.get_name(),
+                       'version': self.distribution.get_version(),
+               }
+               for key, default in self.paths:
+                       new_paths[key] = subst_vars(getattr(self, key), 
new_paths)
+                       setattr(self, key, new_paths[key])
+               self.subst_paths = new_paths
+
+
+class x_install_data(install_data):
+       """ install_data with customized path support """
+
+       user_options = install_data.user_options
+
+       def initialize_options(self):
+               install_data.initialize_options(self)
+               self.build_base = None
+               self.paths = None
+
+       def finalize_options(self):
+               install_data.finalize_options(self)
+               self.set_undefined_options('build',
+                       ('build_base', 'build_base'))
+               self.set_undefined_options('install',
+                       ('subst_paths', 'paths'))
+
+       def run(self):
+               self.run_command('build_man')
+
+               def process_data_files(df):
+                       for d, files in df:
+                               # substitute man sources
+                               if d.startswith('$mandir/'):
+                                       files = [os.path.join(self.build_base, 
v) for v in files]
+
+                               # substitute variables in path
+                               d = subst_vars(d, self.paths)
+                               yield (d, files)
+
+               old_data_files = self.data_files
+               self.data_files = process_data_files(self.data_files)
+
+               install_data.run(self)
+               self.data_files = old_data_files
+
+
+class x_install_lib(install_lib):
+       """ install_lib command with Portage path substitution """
+
+       user_options = install_lib.user_options
+
+       def initialize_options(self):
+               install_lib.initialize_options(self)
+               self.portage_base = None
+               self.portage_bindir = None
+               self.portage_confdir = None
+
+       def finalize_options(self):
+               install_lib.finalize_options(self)
+               self.set_undefined_options('install',
+                       ('portage_base', 'portage_base'),
+                       ('portage_bindir', 'portage_bindir'),
+                       ('portage_confdir', 'portage_confdir'))
+
+       def install(self):
+               ret = install_lib.install(self)
+
+               def rewrite_file(path, val_dict):
+                       path = os.path.join(self.install_dir, path)
+                       print('Rewriting %s' % path)
+                       with codecs.open(path, 'r', 'utf-8') as f:
+                               data = f.read()
+
+                       for varname, val in val_dict.items():
+                               regexp = r'(?m)^(%s\s*=).*$' % varname
+                               repl = r'\1 %s' % repr(val)
+
+                               data = re.sub(regexp, repl, data)
+
+                       with codecs.open(path, 'w', 'utf-8') as f:
+                               f.write(data)
+
+               rewrite_file('portage/__init__.py', {
+                       'VERSION': self.distribution.get_version(),
+               })
+               rewrite_file('portage/const.py', {
+                       'PORTAGE_BASE_PATH': self.portage_base,
+                       'PORTAGE_BIN_PATH': self.portage_bindir,
+                       'PORTAGE_CONFIG_PATH': self.portage_confdir,
+               })
+
+               return ret
+
+
+class x_install_scripts_custom(install_scripts):
+       def initialize_options(self):
+               install_scripts.initialize_options(self)
+               self.root = None
+
+       def finalize_options(self):
+               self.set_undefined_options('install',
+                       ('root', 'root'),
+                       (self.var_name, 'install_dir'))
+               install_scripts.finalize_options(self)
+               self.build_dir = os.path.join(self.build_dir, self.dir_name)
+
+               # prepend root
+               if self.root is not None:
+                       self.install_dir = change_root(self.root, 
self.install_dir)
+
+
+class x_install_scripts_bin(x_install_scripts_custom):
+       dir_name = 'bin'
+       var_name = 'bindir'
+
+
+class x_install_scripts_sbin(x_install_scripts_custom):
+       dir_name = 'sbin'
+       var_name = 'sbindir'
+
+
+class x_install_scripts_portagebin(x_install_scripts_custom):
+       dir_name = 'portage'
+       var_name = 'portage_bindir'
+
+
+class x_install_scripts(install_scripts):
+       def initialize_option(self):
+               pass
+
+       def finalize_options(self):
+               pass
+
+       def run(self):
+               self.run_command('install_scripts_bin')
+               self.run_command('install_scripts_portagebin')
+               self.run_command('install_scripts_sbin')
+
+
+class x_sdist(sdist):
+       """ sdist defaulting to .tar.bz2 format """
+
+       def finalize_options(self):
+               if self.formats is None:
+                       self.formats = ['bztar']
+
+               sdist.finalize_options(self)
+
+
+class build_tests(x_build_scripts_custom):
+       """ Prepare build dir for running tests. """
+
+       def initialize_options(self):
+               x_build_scripts_custom.initialize_options(self)
+               self.build_base = None
+               self.build_lib = None
+
+       def finalize_options(self):
+               x_build_scripts_custom.finalize_options(self)
+               self.set_undefined_options('build',
+                       ('build_base', 'build_base'),
+                       ('build_lib', 'build_lib'))
+
+               # since we will be writing to $build_lib/.., it is important
+               # that we do not leave $build_base
+               self.top_dir = os.path.normpath(os.path.join(self.build_lib, 
'..'))
+               cprefix = os.path.commonprefix((self.build_base, self.top_dir))
+               if cprefix != self.build_base:
+                       raise SystemError('build_lib must be a subdirectory of 
build_base')
+
+               self.build_dir = os.path.join(self.top_dir, 'bin')
+
+       def run(self):
+               self.run_command('build_py')
+
+               # install all scripts $build_lib/../bin
+               # (we can't do a symlink since we want shebangs corrected)
+               x_build_scripts_custom.run(self)
+
+               # symlink 'cnf' directory
+               conf_dir = os.path.join(self.top_dir, 'cnf')
+               if os.path.exists(conf_dir):
+                       if not os.path.islink(conf_dir):
+                               raise SystemError('%s exists and is not a 
symlink (collision)'
+                                       % repr(conf_dir))
+                       os.unlink(conf_dir)
+               conf_src = os.path.relpath('cnf', self.top_dir)
+               print('Symlinking %s -> %s' % (conf_dir, conf_src))
+               os.symlink(conf_src, conf_dir)
+
+               # create $build_lib/../.portage_not_installed
+               # to enable proper paths in tests
+               with open(os.path.join(self.top_dir, '.portage_not_installed'), 
'w') as f:
+                       pass
+
+
+class test(Command):
+       """ run tests """
+
+       user_options = []
+
+       def initialize_options(self):
+               self.build_lib = None
+
+       def finalize_options(self):
+               self.set_undefined_options('build',
+                       ('build_lib', 'build_lib'))
+
+       def run(self):
+               self.run_command('build_tests')
+               subprocess.check_call([
+                       sys.executable, '-bWd',
+                       os.path.join(self.build_lib, 
'portage/tests/runTests.py')
+               ])
+
+
+def find_packages():
+       for dirpath, dirnames, filenames in os.walk('pym'):
+               if '__init__.py' in filenames:
+                       yield os.path.relpath(dirpath, 'pym')
+
+
+def find_scripts():
+       for dirpath, dirnames, filenames in os.walk('bin'):
+               for f in filenames:
+                       yield os.path.join(dirpath, f)
+
+
+def get_manpages():
+       linguas = os.environ.get('LINGUAS')
+       if linguas is not None:
+               linguas = linguas.split()
+
+       for dirpath, dirnames, filenames in os.walk('man'):
+               groups = collections.defaultdict(list)
+               for f in filenames:
+                       fn, suffix = f.rsplit('.', 1)
+                       groups[suffix].append(os.path.join(dirpath, f))
+
+               topdir = dirpath[len('man/'):]
+               if not topdir or linguas is None or topdir in linguas:
+                       for g, mans in groups.items():
+                               yield [os.path.join('$mandir', topdir, 'man%s' 
% g), mans]
+
+setup(
+       name = 'portage',
+       version = '2.2.12',
+       url = 'https://wiki.gentoo.org/wiki/Project:Portage',
+       author = 'Gentoo Portage Development Team',
+       author_email = 'dev-port...@gentoo.org',
+
+       package_dir = {'': 'pym'},
+       packages = list(find_packages()),
+       # something to cheat build & install commands
+       scripts = list(find_scripts()),
+
+       data_files = list(get_manpages()) + [
+               ['$sysconfdir', ['cnf/etc-update.conf', 
'cnf/dispatch-conf.conf']],
+               ['$logrotatedir', ['cnf/logrotate.d/elog-save-summary']],
+               ['$portage_confdir', [
+                       'cnf/make.conf.example', 'cnf/make.globals', 
'cnf/repos.conf']],
+               ['$portage_setsdir', ['cnf/sets/portage.conf']],
+               ['$docdir', ['NEWS', 'RELEASE-NOTES']],
+       ],
+
+       cmdclass = {
+               'build': x_build,
+               'build_man': build_man,
+               'build_scripts': x_build_scripts,
+               'build_scripts_bin': x_build_scripts_bin,
+               'build_scripts_portagebin': x_build_scripts_portagebin,
+               'build_scripts_sbin': x_build_scripts_sbin,
+               'build_tests': build_tests,
+               'clean': x_clean,
+               'docbook': docbook,
+               'epydoc': epydoc,
+               'install': x_install,
+               'install_data': x_install_data,
+               'install_docbook': install_docbook,
+               'install_epydoc': install_epydoc,
+               'install_lib': x_install_lib,
+               'install_scripts': x_install_scripts,
+               'install_scripts_bin': x_install_scripts_bin,
+               'install_scripts_portagebin': x_install_scripts_portagebin,
+               'install_scripts_sbin': x_install_scripts_sbin,
+               'sdist': x_sdist,
+               'test': test,
+       },
+
+       classifiers = [
+               'Development Status :: 5 - Production/Stable',
+               'Environment :: Console',
+               'Intended Audience :: System Administrators',
+               'License :: OSI Approved :: GNU General Public License v2 
(GPLv2)',
+               'Operating System :: POSIX',
+               'Programming Language :: Python',
+               'Topic :: System :: Installation/Setup'
+       ]
+)
-- 
2.1.0


Reply via email to