blob: 48928dd9f5e21b5cd85e3e4a6b463762f6cce622 [file] [log] [blame]
"""%prog RELEASE_AREA [action ...]
Perform needed actions to release mechanize, doing the work in directory
RELEASE_AREA.
If no actions are given, print the tree of actions and do nothing.
This is only intended to work on Unix (unlike mechanize itself). Some of it
only works on Ubuntu 10.04 (lucid).
Warning:
* Many actions do rm -rf on RELEASE_AREA or subdirectories of RELEASE_AREA.
* The install_deps action installs some debian packages system-wide. The
clean action doesn't uninstall them.
* The install_deps action adds a PPA.
* The install_deps action downloads and installs software to RELEASE_AREA.
The clean action uninstalls (by rm -rf).
"""
# This script depends on the code from this git repository:
# git://github.com/jjlee/mechanize-build-tools.git
# TODO
# * Tag mechanize-build-tools repository when releasing so that builds are
# reproducible
# * 0install package?
# * test in a Windows VM
import glob
import optparse
import os
import re
import shutil
import subprocess
import sys
import tempfile
import time
import unittest
# Stop the test runner from reporting import failure if these modules aren't
# available or not running under Python >= 2.6. AttributeError occurs if run
# with Python < 2.6, due to lack of collections.namedtuple
try:
import action_tree
import build_log
import cmd_env
import buildtools.release as release
except (ImportError, AttributeError):
# fake module
class action_tree(object):
@staticmethod
def action_node(func):
return func
# based on Mark Seaborn's plash build-tools (action_tree) and Cmed's in-chroot
# (cmd_env) -- which is also Mark's idea
class WrongVersionError(Exception):
def __init__(self, version):
Exception.__init__(self, version)
self.version = version
def __str__(self):
return str(self.version)
class MissingVersionError(Exception):
def __init__(self, path, release_version):
Exception.__init__(self, path, release_version)
self.path = path
self.release_version = release_version
def __str__(self):
return ("Release version string not found in %s: should be %s" %
(self.path, self.release_version))
class CSSValidationError(Exception):
def __init__(self, path, details):
Exception.__init__(self, path, details)
self.path = path
self.details = details
def __str__(self):
return ("CSS validation of %s failed:\n%s" %
(self.path, self.details))
def run_performance_tests(path):
# TODO: use a better/standard test runner
sys.path.insert(0, os.path.join(path, "test"))
test_runner = unittest.TextTestRunner(verbosity=1)
test_loader = unittest.defaultTestLoader
modules = []
for module_name in ["test_performance"]:
module = __import__(module_name)
for part in module_name.split('.')[1:]:
module = getattr(module, part)
modules.append(module)
suite = unittest.TestSuite()
for module in modules:
test = test_loader.loadTestsFromModule(module)
suite.addTest(test)
result = test_runner.run(test)
return result
def clean_environ_env(env):
return cmd_env.PrefixCmdEnv(
["sh", "-c", 'env -i HOME="$HOME" PATH="$PATH" "$@"',
"clean_environ_env"], env)
def check_version_equals(env, version, python):
try:
output = release.get_cmd_stdout(
env,
[python, "-c",
"import mechanize; print mechanize.__version__"],
stderr=subprocess.PIPE)
except cmd_env.CommandFailedError:
raise WrongVersionError(None)
else:
version_tuple_string = output.strip()
assert len(version.tuple) == 6, len(version.tuple)
if not(version_tuple_string == str(version.tuple) or
version_tuple_string == str(version.tuple[:-1])):
raise WrongVersionError(version_tuple_string)
def check_not_installed(env, python):
bogus_version = release.parse_version("0.0.0")
try:
check_version_equals(env, bogus_version, python)
except WrongVersionError, exc:
if exc.version is not None:
raise
else:
raise WrongVersionError(bogus_version)
class EasyInstallTester(object):
def __init__(self, env, install_dir, project_name,
test_cmd, expected_version,
easy_install_cmd=("easy_install",),
python="python"):
self._env = env
self._install_dir = install_dir
self._project_name = project_name
self._test_cmd = test_cmd
self._expected_version = expected_version
self._easy_install_cmd = list(easy_install_cmd)
self._python = python
self._install_dir_on_pythonpath = cmd_env.set_environ_vars_env(
[("PYTHONPATH", self._install_dir)], env)
def easy_install(self, log):
release.clean_dir(self._env, self._install_dir)
check_not_installed(self._install_dir_on_pythonpath, self._python)
output = release.get_cmd_stdout(
self._install_dir_on_pythonpath,
self._easy_install_cmd + ["-d", self._install_dir,
self._project_name])
# easy_install doesn't fail properly :-(
if "SyntaxError" in output:
raise Exception(output)
check_version_equals(self._install_dir_on_pythonpath,
self._expected_version,
self._python)
def test(self, log):
self._install_dir_on_pythonpath.cmd(self._test_cmd)
@action_tree.action_node
def easy_install_test(self):
return [
self.easy_install,
self.test,
]
def make_source_dist_easy_install_test_step(env, install_dir,
source_dir,
test_cmd, expected_version,
python_version):
python = "python%d.%d" % python_version
tester = EasyInstallTester(
env,
install_dir,
project_name=".",
test_cmd=test_cmd,
expected_version=expected_version,
easy_install_cmd=(cmd_env.in_dir(source_dir) +
[python, "setup.py", "easy_install"]),
python=python)
return tester.easy_install_test
def make_pypi_easy_install_test_step(env, install_dir,
test_cmd, expected_version,
python_version):
easy_install = "easy_install-%d.%d" % python_version
python = "python%d.%d" % python_version
tester = EasyInstallTester(
env,
install_dir,
project_name="mechanize",
test_cmd=test_cmd,
expected_version=expected_version,
easy_install_cmd=[easy_install],
python=python)
return tester.easy_install_test
def make_tarball_easy_install_test_step(env, install_dir,
tarball_path,
test_cmd, expected_version,
python_version):
easy_install = "easy_install-%d.%d" % python_version
python = "python%d.%d" % python_version
tester = EasyInstallTester(
env,
install_dir,
project_name=tarball_path,
test_cmd=test_cmd,
expected_version=expected_version,
easy_install_cmd=[easy_install],
python=python)
return tester.easy_install_test
class Releaser(object):
def __init__(self, env, git_repository_path, release_area, mirror_path,
build_tools_repo_path=None, run_in_repository=False,
tag_name=None, test_uri=None):
self._release_area = release_area
self._release_dir = release_dir = os.path.join(release_area, "release")
self._opt_dir = os.path.join(release_dir, "opt")
self._bin_dir = os.path.join(self._opt_dir, "bin")
AddToPathEnv = release.make_env_maker(release.add_to_path_cmd)
self._env = AddToPathEnv(release.GitPagerWrapper(env), self._bin_dir)
self._source_repo_path = git_repository_path
self._in_source_repo = release.CwdEnv(self._env,
self._source_repo_path)
self._tag_name = tag_name
self._set_next_release_version()
self._clone_path = os.path.join(release_dir, "clone")
self._in_clone = release.CwdEnv(self._env, self._clone_path)
if run_in_repository:
self._in_repo = self._in_source_repo
self._repo_path = self._source_repo_path
else:
self._in_repo = self._in_clone
self._repo_path = self._clone_path
self._docs_dir = os.path.join(self._repo_path, "docs")
self._in_docs_dir = release.CwdEnv(self._env, self._docs_dir)
self._in_release_dir = release.CwdEnv(self._env, self._release_dir)
self._build_tools_path = build_tools_repo_path
if self._build_tools_path is not None:
self._website_source_path = os.path.join(self._build_tools_path,
"website")
self._mirror_path = mirror_path
self._in_mirror = release.CwdEnv(self._env, self._mirror_path)
self._css_validator_path = "css_validator"
self._test_uri = test_uri
self._test_deps_dir = os.path.join(release_dir, "test_deps")
self._easy_install_test_dir = os.path.join(release_dir,
"easy_install_test")
self._in_easy_install_dir = release.CwdEnv(self._env,
self._easy_install_test_dir)
# prevent anything other than functional test dependencies being on
# sys.path due to cwd or PYTHONPATH
self._easy_install_env = clean_environ_env(
release.CwdEnv(env, self._test_deps_dir))
self._zope_testbrowser_dir = os.path.join(release_dir,
"zope_testbrowser_test")
def _mkdtemp(self):
temp_dir = tempfile.mkdtemp(prefix="tmp-%s-" % self.__class__.__name__)
def tear_down():
shutil.rmtree(temp_dir)
return temp_dir, tear_down
def _get_next_release_version(self):
# --pretend / git not installed
most_recent, next = "dummy version", "dummy version"
try:
tags = release.get_cmd_stdout(self._in_source_repo,
["git", "tag", "-l"]).split()
except cmd_env.CommandFailedError:
pass
else:
versions = [release.parse_version(tag) for tag in tags]
if versions:
most_recent = max(versions)
next = most_recent.next_version()
return most_recent, next
def _set_next_release_version(self):
self._previous_version, self._release_version = \
self._get_next_release_version()
if self._tag_name is not None:
self._release_version = release.parse_version(self._tag_name)
self._source_distributions = self._get_source_distributions(
self._release_version)
def _get_source_distributions(self, version):
def dist_basename(version, format):
return "mechanize-%s.%s" % (version, format)
return set([dist_basename(version, "zip"),
dist_basename(version, "tar.gz")])
def git_fetch(self, log):
# for tags
self._in_source_repo.cmd(["git", "fetch"])
self._set_next_release_version()
def print_next_tag(self, log):
print self._release_version
def _verify_version(self, path):
if str(self._release_version) not in \
release.read_file_from_env(self._in_repo, path):
raise MissingVersionError(path, self._release_version)
def _verify_versions(self):
for path in ["ChangeLog", "mechanize/_version.py"]:
self._verify_version(path)
def clone(self, log):
self._env.cmd(["git", "clone",
self._source_repo_path, self._clone_path])
def checks(self, log):
self._verify_versions()
def _ensure_installed(self, package_name, ppa):
release.ensure_installed(self._env,
cmd_env.PrefixCmdEnv(["sudo"], self._env),
package_name,
ppa=ppa)
def install_css_validator_in_release_area(self, log):
jar_dir = os.path.join(self._release_area, self._css_validator_path)
release.clean_dir(self._env, jar_dir)
in_jar_dir = release.CwdEnv(self._env, jar_dir)
in_jar_dir.cmd([
"wget",
"http://www.w3.org/QA/Tools/css-validator/css-validator.jar"])
in_jar_dir.cmd(["wget",
"http://jigsaw.w3.org/Distrib/jigsaw_2.2.6.tar.bz2"])
in_jar_dir.cmd(["sh", "-c", "tar xf jigsaw_*.tar.bz2"])
in_jar_dir.cmd(["ln", "-s", "Jigsaw/classes/jigsaw.jar"])
@action_tree.action_node
def install_deps(self):
dependency_actions = []
standard_dependency_actions = []
def add_dependency(package_name, ppa=None):
if ppa is None:
actions = standard_dependency_actions
else:
actions = dependency_actions
actions.append(
(package_name.replace(".", ""),
lambda log: self._ensure_installed(package_name, ppa)))
add_dependency("python2.6")
# required, but ubuntu doesn't have them any more :-( I installed these
# (and zope.interface and twisted SVN trunk) by hand
# add_dependency("python2.4"),
# add_dependency("python2.5")
# add_dependency("python2.7")
add_dependency("python-setuptools")
add_dependency("git-core")
# for running zope_testbrowser tests
add_dependency("python-virtualenv")
add_dependency("python2.6-dev")
# for deployment to SF and local collation of files for release
add_dependency("rsync")
# for running functional tests against local web server
add_dependency("python-twisted-web2")
# for generating .html docs from .txt markdown files
add_dependency("pandoc")
# for generating docs from .in templates
add_dependency("python-empy")
# for post-processing generated HTML
add_dependency("python-lxml")
# for the validate command
add_dependency("wdg-html-validator")
# for collecting code coverage data and generating coverage reports
# no 64 bit .deb ATM
# add_dependency("python-figleaf", ppa="jjl/figleaf")
# for css validator
add_dependency("default-jre")
add_dependency("libcommons-collections3-java")
add_dependency("libcommons-lang-java")
add_dependency("libxerces2-java")
add_dependency("libtagsoup-java")
# OMG, it depends on piles of java web server stuff, even for local
# command-line validation. You're doing it wrong!
add_dependency("velocity")
dependency_actions.append(self.install_css_validator_in_release_area)
dependency_actions.insert(0, action_tree.make_node(
standard_dependency_actions, "standard_dependencies"))
return dependency_actions
def copy_test_dependencies(self, log):
# so test.py can be run without the mechanize alongside it being on
# sys.path
# TODO: move mechanize package into a top-level directory, so it's not
# automatically on sys.path
def copy_in(src):
self._env.cmd(["cp", "-r", src, self._test_deps_dir])
release.clean_dir(self._env, self._test_deps_dir)
copy_in(os.path.join(self._repo_path, "test.py"))
copy_in(os.path.join(self._repo_path, "test"))
copy_in(os.path.join(self._repo_path, "test-tools"))
copy_in(os.path.join(self._repo_path, "examples"))
def _make_test_cmd(self, python_version,
local_server=True,
uri=None,
coverage=False):
python = "python%d.%d" % python_version
if coverage:
# python-figleaf only supports Python 2.6 ATM
assert python_version == (2, 6), python_version
python = "figleaf"
test_cmd = [python, "test.py"]
if not local_server:
test_cmd.append("--no-local-server")
# running against wwwsearch.sourceforge.net is slow, want to
# see where it failed
test_cmd.append("-v")
if coverage:
# TODO: Fix figleaf traceback with doctests
test_cmd.append("--skip-doctests")
if uri is not None:
test_cmd.extend(["--uri", uri])
return test_cmd
def performance_test(self, log):
result = run_performance_tests(self._repo_path)
if not result.wasSuccessful():
raise Exception("performance tests failed")
def clean_coverage(self, log):
self._in_repo.cmd(["rm", "-f", ".figleaf"])
self._in_repo.cmd(release.rm_rf_cmd("html"))
def _make_test_step(self, env, **kwds):
test_cmd = self._make_test_cmd(**kwds)
def test_step(log):
env.cmd(test_cmd)
return test_step
def _make_easy_install_test_cmd(self, **kwds):
test_cmd = self._make_test_cmd(**kwds)
test_cmd.extend(["discover", "--start-directory", self._test_deps_dir])
return test_cmd
def _make_source_dist_easy_install_test_step(self, env, **kwds):
test_cmd = self._make_easy_install_test_cmd(**kwds)
return make_source_dist_easy_install_test_step(
self._easy_install_env, self._easy_install_test_dir,
self._repo_path, test_cmd, self._release_version,
kwds["python_version"])
def _make_pypi_easy_install_test_step(self, env, **kwds):
test_cmd = self._make_easy_install_test_cmd(**kwds)
return make_pypi_easy_install_test_step(
self._easy_install_env, self._easy_install_test_dir,
test_cmd, self._release_version, kwds["python_version"])
def _make_tarball_easy_install_test_step(self, env, **kwds):
test_cmd = self._make_easy_install_test_cmd(**kwds)
[tarball] = list(d for d in self._source_distributions if
d.endswith(".tar.gz"))
return make_tarball_easy_install_test_step(
self._easy_install_env, self._easy_install_test_dir,
os.path.abspath(os.path.join(self._repo_path, "dist", tarball)),
test_cmd, self._release_version, kwds["python_version"])
def _make_unpacked_tarball_test_step(self, env, **kwds):
# This catches mistakes in listing test files in MANIFEST.in (the tests
# don't get installed, so these don't get caught by testing installed
# code).
test_cmd = self._make_test_cmd(**kwds)
[tarball] = list(d for d in self._source_distributions if
d.endswith(".tar.gz"))
tarball_path = os.path.abspath(
os.path.join(self._repo_path, "dist", tarball))
def test_step(log):
target_dir, tear_down = self._mkdtemp()
try:
env.cmd(["tar", "-C", target_dir, "-xf", tarball_path])
[source_dir] = glob.glob(
os.path.join(target_dir, "mechanize-*"))
test_env = clean_environ_env(release.CwdEnv(env, source_dir))
test_env.cmd(test_cmd)
finally:
tear_down()
return test_step
@action_tree.action_node
def test(self):
r = []
r.append(("python27_test",
self._make_test_step(self._in_repo, python_version=(2, 7))))
r.append(("python27_easy_install_test",
self._make_source_dist_easy_install_test_step(
self._in_repo, python_version=(2, 7))))
r.append(("python26_test",
self._make_test_step(self._in_repo, python_version=(2, 6))))
# disabled for the moment -- think I probably built the launchpad .deb
# from wrong branch, without bug fixes
# r.append(("python26_coverage",
# self._make_test_step(self._in_repo, python_version=(2, 6),
# coverage=True)))
r.append(("python25_easy_install_test",
self._make_source_dist_easy_install_test_step(
self._in_repo, python_version=(2, 5))))
r.append(("python24_easy_install_test",
self._make_source_dist_easy_install_test_step(
self._in_repo, python_version=(2, 4))))
r.append(self.performance_test)
return r
def make_coverage_html(self, log):
self._in_repo.cmd(["figleaf2html"])
def tag(self, log):
self._in_repo.cmd(["git", "checkout", "master"])
self._in_repo.cmd(["git", "tag",
"-m", "Tagging release %s" % self._release_version,
str(self._release_version)])
def clean_docs(self, log):
self._in_docs_dir.cmd(release.rm_rf_cmd("html"))
def make_docs(self, log):
self._in_docs_dir.cmd(["mkdir", "-p", "html"])
site_map = release.site_map()
def pandoc(filename, source_filename):
last_modified = release.last_modified(source_filename,
self._in_docs_dir)
if filename == "download.txt":
last_modified = time.gmtime()
variables = [
("last_modified_iso",
time.strftime("%Y-%m-%d", last_modified)),
("last_modified_month_year",
time.strftime("%B %Y", last_modified))]
page_name = os.path.splitext(os.path.basename(filename))[0]
variables.append(("nav", release.nav_html(site_map, page_name)))
variables.append(("subnav", release.subnav_html(site_map,
page_name)))
release.pandoc(self._in_docs_dir, filename, variables=variables)
release.empy(self._in_docs_dir, "forms.txt.in")
release.empy(self._in_docs_dir, "download.txt.in",
defines=["version=%r" % str(self._release_version)])
for page in site_map.iter_pages():
if page.name in ["Root", "Changelog"]:
continue
source_filename = filename = page.name + ".txt"
if page.name in ["forms", "download"]:
source_filename += ".in"
pandoc(filename, source_filename)
self._in_repo.cmd(["cp", "-r", "ChangeLog", "docs/html/ChangeLog.txt"])
if self._build_tools_path is not None:
styles = release.ensure_trailing_slash(
os.path.join(self._website_source_path, "styles"))
self._env.cmd(["rsync", "-a", styles,
os.path.join(self._docs_dir, "styles")])
def setup_py_sdist(self, log):
self._in_repo.cmd(release.rm_rf_cmd("dist"))
# write empty setup.cfg so source distribution is built using a version
# number without ".dev" and today's date appended
self._in_repo.cmd(cmd_env.write_file_cmd("setup.cfg", ""))
self._in_repo.cmd(["python", "setup.py", "sdist",
"--formats=gztar,zip"])
archives = set(os.listdir(os.path.join(self._repo_path, "dist")))
assert archives == self._source_distributions, \
(archives, self._source_distributions)
@action_tree.action_node
def build_sdist(self):
return [
self.clean_docs,
self.make_docs,
self.setup_py_sdist,
]
def _stage(self, path, dest_dir, dest_basename=None,
source_base_path=None):
# IIRC not using rsync because didn't see easy way to avoid updating
# timestamp of unchanged files, which was upsetting git
# note: files in the website repository that are no longer generated
# must be manually deleted from the repository
if source_base_path is None:
source_base_path = self._repo_path
full_path = os.path.join(source_base_path, path)
try:
self._env.cmd(["readlink", "-e", full_path],
stdout=open(os.devnull, "w"))
except cmd_env.CommandFailedError:
print "not staging (does not exist):", full_path
return
if dest_basename is None:
dest_basename = os.path.basename(path)
dest = os.path.join(self._mirror_path, dest_dir, dest_basename)
try:
self._env.cmd(["cmp", full_path, dest])
except cmd_env.CommandFailedError:
print "staging: %s -> %s" % (full_path, dest)
self._env.cmd(["cp", full_path, dest])
else:
print "not staging (unchanged): %s -> %s" % (full_path, dest)
def ensure_unmodified(self, log):
if self._build_tools_path:
release.ensure_unmodified(self._env, self._website_source_path)
release.ensure_unmodified(self._env, self._mirror_path)
def _stage_flat_dir(self, path, dest):
self._env.cmd(["mkdir", "-p", os.path.join(self._mirror_path, dest)])
for filename in os.listdir(path):
self._stage(os.path.join(path, filename), dest)
def _symlink_flat_dir(self, path, exclude):
for filename in os.listdir(path):
if filename in exclude:
continue
link_dir = os.path.dirname(path)
target = os.path.relpath(os.path.join(path, filename), link_dir)
link_path = os.path.join(link_dir, filename)
if not os.path.islink(link_path) or \
os.path.realpath(link_path) != target:
self._env.cmd(["ln", "-f", "-s", "-t", link_dir, target])
def collate_from_mechanize(self, log):
html_dir = os.path.join(self._docs_dir, "html")
self._stage_flat_dir(html_dir, "htdocs/mechanize/docs")
self._symlink_flat_dir(
os.path.join(self._mirror_path, "htdocs/mechanize/docs"),
exclude=[".git", ".htaccess", ".svn", "CVS"])
self._stage("test-tools/cookietest.cgi", "cgi-bin")
self._stage("examples/forms/echo.cgi", "cgi-bin")
self._stage("examples/forms/example.html", "htdocs/mechanize")
for archive in self._source_distributions:
placeholder = os.path.join("htdocs/mechanize/src", archive)
self._in_mirror.cmd(["touch", placeholder])
def collate_from_build_tools(self, log):
self._stage(os.path.join(self._website_source_path, "frontpage.html"),
"htdocs", "index.html")
self._stage_flat_dir(
os.path.join(self._website_source_path, "styles"), "htdocs/styles")
@action_tree.action_node
def collate(self):
r = [self.collate_from_mechanize]
if self._build_tools_path is not None:
r.append(self.collate_from_build_tools)
return r
def collate_pypi_upload_built_items(self, log):
for archive in self._source_distributions:
self._stage(os.path.join("dist", archive), "htdocs/mechanize/src")
def commit_staging_website(self, log):
self._in_mirror.cmd(["git", "add", "--all"])
self._in_mirror.cmd(
["git", "commit",
"-m", "Automated update for release %s" % self._release_version])
def validate_html(self, log):
exclusions = set(f for f in """\
./cookietest.html
htdocs/basic_auth/index.html
htdocs/digest_auth/index.html
htdocs/mechanize/example.html
htdocs/test_fixtures/index.html
htdocs/test_fixtures/mechanize_reload_test.html
htdocs/test_fixtures/referertest.html
""".splitlines() if not f.startswith("#"))
for dirpath, dirnames, filenames in os.walk(self._mirror_path):
try:
# archived website
dirnames.remove("old")
except ValueError:
pass
for filename in filenames:
if filename.endswith(".html"):
page_path = os.path.join(
os.path.relpath(dirpath, self._mirror_path), filename)
if page_path not in exclusions:
self._in_mirror.cmd(["validate", page_path])
def _classpath_cmd(self):
from_packages = ["/usr/share/java/commons-collections3.jar",
"/usr/share/java/commons-lang.jar",
"/usr/share/java/xercesImpl.jar",
"/usr/share/java/tagsoup.jar",
"/usr/share/java/velocity.jar",
]
jar_dir = os.path.join(self._release_area, self._css_validator_path)
local = glob.glob(os.path.join(jar_dir, "*.jar"))
path = ":".join(local + from_packages)
return ["env", "CLASSPATH=%s" % path]
def _sanitise_css(self, path):
temp_dir, tear_down = self._mkdtemp()
temp_path = os.path.join(temp_dir, os.path.basename(path))
temp = open(temp_path, "w")
try:
for line in open(path):
if line.rstrip().endswith("/*novalidate*/"):
# temp.write("/*%s*/\n" % line.rstrip())
temp.write("/*sanitised*/\n")
else:
temp.write(line)
finally:
temp.close()
return temp_path, tear_down
def validate_css(self, log):
env = cmd_env.PrefixCmdEnv(self._classpath_cmd(), self._in_release_dir)
# env.cmd(["java", "org.w3c.css.css.CssValidator", "--help"])
"""
Usage: java org.w3c.css.css.CssValidator [OPTIONS] | [URL]*
OPTIONS
-p, --printCSS
Prints the validated CSS (only with text output, the CSS is printed with other outputs)
-profile PROFILE, --profile=PROFILE
Checks the Stylesheet against PROFILE
Possible values for PROFILE are css1, css2, css21 (default), css3, svg, svgbasic, svgtiny, atsc-tv, mobile, tv
-medium MEDIUM, --medium=MEDIUM
Checks the Stylesheet using the medium MEDIUM
Possible values for MEDIUM are all (default), aural, braille, embossed, handheld, print, projection, screen, tty, tv, presentation
-output OUTPUT, --output=OUTPUT
Prints the result in the selected format
Possible values for OUTPUT are text (default), xhtml, html (same result as xhtml), soap12
-lang LANG, --lang=LANG
Prints the result in the specified language
Possible values for LANG are de, en (default), es, fr, ja, ko, nl, zh-cn, pl, it
-warning WARN, --warning=WARN
Warnings verbosity level
Possible values for WARN are -1 (no warning), 0, 1, 2 (default, all the warnings
URL
URL can either represent a distant web resource (http://) or a local file (file:/)
"""
validate_cmd = ["java", "org.w3c.css.css.CssValidator"]
for dirpath, dirnames, filenames in os.walk(self._mirror_path):
for filename in filenames:
if filename.endswith(".css"):
path = os.path.join(dirpath, filename)
temp_path, tear_down = self._sanitise_css(path)
try:
page_url = "file://" + temp_path
output = release.get_cmd_stdout(
env, validate_cmd + [page_url])
finally:
tear_down()
# the validator doesn't fail properly: it exits
# successfully on validation failure
if "Sorry! We found the following errors" in output:
raise CSSValidationError(path, output)
def fetch_zope_testbrowser(self, log):
release.clean_dir(self._env, self._zope_testbrowser_dir)
in_testbrowser = release.CwdEnv(self._env, self._zope_testbrowser_dir)
in_testbrowser.cmd(["easy_install", "--editable",
"--build-directory", ".",
"zope.testbrowser[test]"])
in_testbrowser.cmd(
["virtualenv", "--no-site-packages", "zope.testbrowser"])
project_dir = os.path.join(self._zope_testbrowser_dir,
"zope.testbrowser")
in_project_dir = clean_environ_env(
release.CwdEnv(self._env, project_dir))
check_not_installed(in_project_dir, "bin/python")
in_project_dir.cmd(
["sed", "-i", "-e", "s/mechanize[^\"']*/mechanize/", "setup.py"])
in_project_dir.cmd(["bin/easy_install", "zc.buildout"])
in_project_dir.cmd(["bin/buildout", "init"])
[mechanize_tarball] = list(d for d in self._source_distributions if
d.endswith(".tar.gz"))
tarball_path = os.path.join(self._repo_path, "dist", mechanize_tarball)
in_project_dir.cmd(["bin/easy_install", tarball_path])
in_project_dir.cmd(["bin/buildout", "install"])
def test_zope_testbrowser(self, log):
project_dir = os.path.join(self._zope_testbrowser_dir,
"zope.testbrowser")
env = clean_environ_env(release.CwdEnv(self._env, project_dir))
check_version_equals(env, self._release_version, "bin/python")
env.cmd(["bin/test"])
@action_tree.action_node
def zope_testbrowser(self):
return [self.fetch_zope_testbrowser,
self.test_zope_testbrowser,
]
def upload_to_pypi(self, log):
self._in_repo.cmd(["python", "setup.py", "sdist",
"--formats=gztar,zip", "upload"])
def sync_to_sf(self, log):
assert os.path.isdir(
os.path.join(self._mirror_path, "htdocs/mechanize"))
self._env.cmd(["rsync", "-rlptvuz", "--exclude", "*~", "--delete",
release.ensure_trailing_slash(self._mirror_path),
"jjlee,wwwsearch@web.sourceforge.net:"])
@action_tree.action_node
def upload(self):
r = []
r.append(self.upload_to_pypi)
# setup.py upload requires sdist command to upload zip files, and the
# sdist comment insists on rebuilding source distributions, so it's not
# possible to use the upload command to upload the already-built zip
# file. Work around that by copying the rebuilt source distributions
# into website repository only now (rather than at build/test time), so
# don't end up with two different sets of source distributions with
# different md5 sums due to timestamps in the archives.
r.append(self.collate_pypi_upload_built_items)
r.append(self.commit_staging_website)
if self._mirror_path is not None:
r.append(self.sync_to_sf)
return r
def clean(self, log):
release.clean_dir(self._env, self._release_area)
def clean_most(self, log):
# not dependencies installed in release area (css validator)
release.clean_dir(self._env, self._release_dir)
def write_email(self, log):
log = release.get_cmd_stdout(self._in_repo,
["git", "log", '--pretty=format: * %s',
"%s..HEAD" % self._previous_version])
# filter out some uninteresting commits
log = "".join(line for line in log.splitlines(True) if not
re.match("^ \* Update (?:changelog|version)$", line,
re.I))
self._in_release_dir.cmd(cmd_env.write_file_cmd(
"announce_email.txt", u"""\
ANN: mechanize {version} released
http://wwwsearch.sourceforge.net/mechanize/
This is a stable bugfix release.
Changes since {previous_version}:
{log}
About mechanize
=============================================
Requires Python 2.4, 2.5, 2.6, or 2.7.
Stateful programmatic web browsing, after Andy Lester's Perl module
WWW::Mechanize.
Example:
import re
from mechanize import Browser
b = Browser()
b.open("http://www.example.com/")
# follow second link with element text matching regular expression
response = b.follow_link(text_regex=re.compile(r"cheese\s*shop"), nr=1)
b.select_form(name="order")
# Browser passes through unknown attributes (including methods)
# to the selected HTMLForm
b["cheeses"] = ["mozzarella", "caerphilly"] # (the method here is __setitem__)
response2 = b.submit() # submit current form
response3 = b.back() # back to cheese shop
response4 = b.reload()
for link in b.forms():
print form
# .links() optionally accepts the keyword args of .follow_/.find_link()
for link in b.links(url_regex=re.compile("python.org")):
print link
b.follow_link(link) # can be EITHER Link instance OR keyword args
b.back()
John
""".format(log=log,
version=self._release_version,
previous_version=self._previous_version)))
def edit_email(self, log):
self._in_release_dir.cmd(["sensible-editor", "announce_email.txt"])
def push_tag(self, log):
self._in_repo.cmd(["git", "push", "git@github.com:jjlee/mechanize.git",
"tag", str(self._release_version)])
def send_email(self, log):
text = release.read_file_from_env(self._in_release_dir,
"announce_email.txt")
subject, sep, body = text.partition("\n")
body = body.lstrip()
assert len(body) > 0, body
release.send_email(
from_address="John J Lee <jjl@pobox.com>",
to_address="wwwsearch-general@lists.sourceforge.net",
subject=subject,
body=body)
@action_tree.action_node
def build(self):
return [
self.clean,
self.install_deps,
self.clean_most,
self.git_fetch,
self.print_next_tag,
self.clone,
self.checks,
# self.clean_coverage,
self.copy_test_dependencies,
self.test,
# self.make_coverage_html,
self.tag,
self.build_sdist,
("unpacked_tarball_test", self._make_unpacked_tarball_test_step(
self._env, python_version=(2,6))),
("easy_install_test", self._make_tarball_easy_install_test_step(
self._in_repo, python_version=(2, 6),
local_server=False, uri=self._test_uri)),
self.zope_testbrowser,
self.write_email,
self.edit_email,
]
def update_version(self, log):
version_path = "mechanize/_version.py"
template = """\
"%(text)s"
__version__ = %(tuple)s
"""
old_text = release.read_file_from_env(self._in_source_repo,
version_path)
old_version = old_text.splitlines()[0].strip(' "')
assert old_version == str(self._release_version), \
(old_version, str(self._release_version))
def version_text(version):
return template % {"text": str(version),
"tuple": repr(tuple(version.tuple[:-1]))}
assert old_text == version_text(release.parse_version(old_version)), \
(old_text, version_text(release.parse_version(old_version)))
self._in_source_repo.cmd(cmd_env.write_file_cmd(
version_path,
version_text(self._release_version.next_version())))
self._in_source_repo.cmd(["git", "commit", "-m", "Update version",
version_path])
@action_tree.action_node
def update_staging_website(self):
if self._mirror_path is None:
return []
return [
self.ensure_unmodified,
self.collate,
self.validate_html,
self.validate_css,
self.commit_staging_website,
]
@action_tree.action_node
def tell_the_world(self):
return [
self.push_tag,
self.upload,
("easy_install_test_internet",
self._make_pypi_easy_install_test_step(
self._in_repo, python_version=(2, 6),
local_server=False,
uri="http://wwwsearch.sourceforge.net/")),
self.send_email,
]
@action_tree.action_node
def all(self):
return [
self.build,
self.update_staging_website,
self.update_version,
self.tell_the_world,
]
def parse_options(args):
parser = optparse.OptionParser(usage=__doc__.strip())
release.add_basic_env_options(parser)
action_tree.add_options(parser)
parser.add_option("--mechanize-repository", metavar="DIRECTORY",
dest="git_repository_path",
help="path to mechanize git repository (default is cwd)")
parser.add_option("--build-tools-repository", metavar="DIRECTORY",
help=("path of mechanize-build-tools git repository, "
"from which to get other website source files "
"(default is not to build those files)"))
parser.add_option("--website-repository", metavar="DIRECTORY",
dest="mirror_path",
help=("path of local website mirror git repository into "
"which built files will be copied (default is not "
"to copy the files)"))
parser.add_option("--in-source-repository", action="store_true",
dest="in_repository",
help=("run all commands in original repository "
"(specified by --git-repository), rather than in "
"the clone of it in the release area"))
parser.add_option("--tag-name", metavar="TAG_NAME")
parser.add_option("--uri", default="http://wwwsearch.sourceforge.net/",
help=("base URI to run tests against when not using a "
"built-in web server"))
options, remaining_args = parser.parse_args(args)
nr_args = len(remaining_args)
try:
options.release_area = remaining_args.pop(0)
except IndexError:
parser.error("Expected at least 1 argument, got %d" % nr_args)
if options.git_repository_path is None:
options.git_repository_path = os.getcwd()
if not release.is_git_repository(options.git_repository_path):
parser.error("incorrect git repository path")
if options.build_tools_repository is not None and \
not release.is_git_repository(options.build_tools_repository):
parser.error("incorrect mechanize-build-tools repository path")
mirror_path = options.mirror_path
if mirror_path is not None:
if not release.is_git_repository(options.mirror_path):
parser.error("mirror path is not a git reporsitory")
mirror_path = os.path.join(mirror_path, "mirror")
if not os.path.isdir(mirror_path):
parser.error("%r does not exist" % mirror_path)
options.mirror_path = mirror_path
return options, remaining_args
def main(argv):
if not hasattr(action_tree, "action_main"):
sys.exit("failed to import required modules")
options, action_tree_args = parse_options(argv[1:])
env = release.get_env_from_options(options)
releaser = Releaser(env, options.git_repository_path, options.release_area,
options.mirror_path, options.build_tools_repository,
options.in_repository, options.tag_name, options.uri)
log = build_log.PrintTitlesLogWriter(sys.stdout,
build_log.DummyLogWriter())
action_tree.action_main_(releaser.all, options, action_tree_args, log=log)
if __name__ == "__main__":
main(sys.argv)