[CalendarServer-changes] [12295] twext/trunk
source_changes at macosforge.org
source_changes at macosforge.org
Wed Mar 12 11:20:57 PDT 2014
Revision: 12295
http://trac.calendarserver.org//changeset/12295
Author: wsanchez at apple.com
Date: 2014-01-10 17:09:58 -0800 (Fri, 10 Jan 2014)
Log Message:
-----------
Handle C deps
Modified Paths:
--------------
twext/trunk/develop
Added Paths:
-----------
twext/trunk/bin/_build.sh
twext/trunk/bin/_py.sh
Added: twext/trunk/bin/_build.sh
===================================================================
--- twext/trunk/bin/_build.sh (rev 0)
+++ twext/trunk/bin/_build.sh 2014-01-11 01:09:58 UTC (rev 12295)
@@ -0,0 +1,501 @@
+# -*- sh-basic-offset: 2 -*-
+##
+# Copyright (c) 2005-2014 Apple Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+##
+
+set -e
+set -u
+
+. "${wd}/bin/_py.sh";
+
+
+echo_header () {
+ echo "$@";
+ echo "";
+}
+
+
+using_system () {
+ local name="$1"; shift;
+ echo_header "Using system version of ${name}.";
+}
+
+
+# Provide a default value: if the variable named by the first argument is
+# empty, set it to the default in the second argument.
+conditional_set () {
+ local var="$1"; shift;
+ local default="$1"; shift;
+ if [ -z "$(eval echo "\${${var}:-}")" ]; then
+ eval "${var}=\${default:-}";
+ fi;
+}
+
+
+# Checks for presence of a C header, optionally with a version comparison.
+# With only a header file name, try to include it, returning nonzero if absent.
+# With 3 params, also attempt a version check, returning nonzero if too old.
+# Param 2 is a minimum acceptable version number
+# Param 3 is a #define from the source that holds the installed version number
+# Examples:
+# Assert that ldap.h is present
+# find_header "ldap.h"
+# Assert that ldap.h is present with a version >= 20344
+# find_header "ldap.h" 20344 "LDAP_VENDOR_VERSION"
+find_header () {
+ sys_header="$1"; shift;
+ if [ $# -ge 1 ]; then
+ min_version="$1"; shift;
+ version_macro="$1"; shift;
+ fi;
+
+ # No min_version given:
+ # Check for presence of a header. We use the "-c" cc option because we don't
+ # need to emit a file; cc exits nonzero if it can't find the header
+ if [ -z "${min_version:-}" ]; then
+ echo "#include <${sys_header}>" | cc -x c -c - -o /dev/null 2> /dev/null;
+ return "$?";
+ fi;
+
+ # Check for presence of a header of specified version
+ found_version="$(printf "#include <${sys_header}>\n${version_macro}\n" | cc -x c -E - | tail -1)";
+
+ if [ "${found_version}" == "${version_macro}" ]; then
+ # Macro was not replaced
+ return 1;
+ fi;
+
+ if cmp_version "${min_version}" "${found_version}"; then
+ return 0;
+ else
+ return 1;
+ fi;
+};
+
+
+# Initialize all the global state required to use this library.
+init_build () {
+ init_py;
+
+ verbose="";
+ do_get="true";
+ do_setup="true";
+ force_setup="false";
+
+ dev_home="${wd}/.develop";
+ dev_root="${dev_home}/root";
+ dev_libdir="${dev_root}/lib/python";
+ dev_bindir="${dev_root}/bin";
+ dep_packages="${dev_home}/pkg";
+ dep_sources="${dev_home}/src";
+
+ mkdir -p "${dep_sources}";
+
+ # These variables are defaults for things which might be configured by
+ # environment; only set them if they're un-set.
+ conditional_set wd "$(pwd)";
+
+ # Find some hashing commands
+ # sha1() = sha1 hash, if available
+ # md5() = md5 hash, if available
+ # hash() = default hash function
+ # $hash = name of the type of hash used by hash()
+
+ hash="";
+
+ if type -ft openssl > /dev/null; then
+ if [ -z "${hash}" ]; then hash="md5"; fi;
+ md5 () { "$(type -p openssl)" dgst -md5 "$@"; }
+ elif type -ft md5 > /dev/null; then
+ if [ -z "${hash}" ]; then hash="md5"; fi;
+ md5 () { "$(type -p md5)" "$@"; }
+ elif type -ft md5sum > /dev/null; then
+ if [ -z "${hash}" ]; then hash="md5"; fi;
+ md5 () { "$(type -p md5sum)" "$@"; }
+ fi;
+
+ if type -ft sha1sum > /dev/null; then
+ if [ -z "${hash}" ]; then hash="sha1sum"; fi;
+ sha1 () { "$(type -p sha1sum)" "$@"; }
+ fi;
+ if type -ft shasum > /dev/null; then
+ if [ -z "${hash}" ]; then hash="sha1"; fi;
+ sha1 () { "$(type -p shasum)" "$@"; }
+ fi;
+
+ if [ "${hash}" == "sha1" ]; then
+ hash () { sha1 "$@"; }
+ elif [ "${hash}" == "md5" ]; then
+ hash () { md5 "$@"; }
+ elif type -t cksum > /dev/null; then
+ hash="hash";
+ hash () { cksum "$@" | cut -f 1 -d " "; }
+ elif type -t sum > /dev/null; then
+ hash="hash";
+ hash () { sum "$@" | cut -f 1 -d " "; }
+ else
+ hash () { echo "INTERNAL ERROR: No hash function."; exit 1; }
+ fi;
+}
+
+
+# If do_get is turned on, get an archive file containing a dependency via HTTP.
+www_get () {
+ if ! "${do_get}"; then return 0; fi;
+
+ local md5="";
+ local sha1="";
+
+ OPTIND=1;
+ while getopts "m:s:" option; do
+ case "${option}" in
+ 'm') md5="${OPTARG}"; ;;
+ 's') sha1="${OPTARG}"; ;;
+ esac;
+ done;
+ shift $((${OPTIND} - 1));
+
+ local name="$1"; shift;
+ local path="$1"; shift;
+ local url="$1"; shift;
+
+ if "${force_setup}" || [ ! -d "${path}" ]; then
+ local ext="$(echo "${url}" | sed 's|^.*\.\([^.]*\)$|\1|')";
+
+ untar () { tar -xvf -; }
+ unzipstream () { tmp="$(mktemp -t ccsXXXXX)"; cat > "${tmp}"; unzip "${tmp}"; rm "${tmp}"; }
+ case "${ext}" in
+ gz|tgz) decompress="gzip -d -c"; unpack="untar"; ;;
+ bz2) decompress="bzip2 -d -c"; unpack="untar"; ;;
+ tar) decompress="untar"; unpack="untar"; ;;
+ zip) decompress="cat"; unpack="unzipstream"; ;;
+ *)
+ echo "Error in www_get of URL ${url}: Unknown extension ${ext}";
+ exit 1;
+ ;;
+ esac;
+
+ echo "";
+
+ if [ -n "${dep_packages}" ] && [ -n "${hash}" ]; then
+ mkdir -p "${dep_packages}";
+
+ local cache_basename="$(echo ${name} | tr '[ ]' '_')-$(echo "${url}" | hash)-$(basename "${url}")";
+ local cache_file="${dep_packages}/${cache_basename}";
+
+ check_hash () {
+ local file="$1"; shift;
+
+ local sum="$(md5 "${file}" | perl -pe 's|^.*([0-9a-f]{32}).*$|\1|')";
+ if [ -n "${md5}" ]; then
+ echo "Checking MD5 sum for ${name}...";
+ if [ "${md5}" != "${sum}" ]; then
+ echo "ERROR: MD5 sum for downloaded file is wrong: ${sum} != ${md5}";
+ return 1;
+ fi;
+ else
+ echo "MD5 sum for ${name} is ${sum}";
+ fi;
+
+ local sum="$(sha1 "${file}" | perl -pe 's|^.*([0-9a-f]{40}).*$|\1|')";
+ if [ -n "${sha1}" ]; then
+ echo "Checking SHA1 sum for ${name}...";
+ if [ "${sha1}" != "${sum}" ]; then
+ echo "ERROR: SHA1 sum for downloaded file is wrong: ${sum} != ${sha1}";
+ return 1;
+ fi;
+ else
+ echo "SHA1 sum for ${name} is ${sum}";
+ fi;
+ }
+
+ if [ ! -f "${cache_file}" ]; then
+ echo "No cache file: ${cache_file}";
+
+ echo "Downloading ${name}...";
+
+ local pkg_host="static.calendarserver.org";
+ local pkg_path="/pkg";
+
+ #
+ # Try getting a copy from calendarserver.org.
+ #
+ local tmp="$(mktemp "/tmp/${cache_basename}.XXXXXX")";
+ curl -L "http://${pkg_host}${pkg_path}/${cache_basename}" -o "${tmp}" || true;
+ echo "";
+ if [ ! -s "${tmp}" ] || grep '<title>404 Not Found</title>' "${tmp}" > /dev/null; then
+ rm -f "${tmp}";
+ echo "${name} is not available from calendarserver.org; trying upstream source.";
+ elif ! check_hash "${tmp}"; then
+ rm -f "${tmp}";
+ echo "${name} from calendarserver.org is invalid; trying upstream source.";
+ fi;
+
+ #
+ # That didn't work. Try getting a copy from the upstream source.
+ #
+ if [ ! -f "${tmp}" ]; then
+ curl -L "${url}" -o "${tmp}";
+ echo "";
+
+ if [ ! -s "${tmp}" ] || grep '<title>404 Not Found</title>' "${tmp}" > /dev/null; then
+ rm -f "${tmp}";
+ echo "${name} is not available from upstream source: ${url}";
+ exit 1;
+ elif ! check_hash "${tmp}"; then
+ rm -f "${tmp}";
+ echo "${name} from upstream source is invalid: ${url}";
+ exit 1;
+ fi;
+
+ if egrep "^${pkg_host}" "${HOME}/.ssh/known_hosts" > /dev/null 2>&1; then
+ echo "Copying cache file up to ${pkg_host}.";
+ if ! scp "${tmp}" "${pkg_host}:/var/www/static${pkg_path}/${cache_basename}"; then
+ echo "Failed to copy cache file up to ${pkg_host}.";
+ fi;
+ echo ""
+ fi;
+ fi;
+
+ #
+ # OK, we should be good
+ #
+ mv "${tmp}" "${cache_file}";
+ else
+ #
+ # We have the file cached, just verify hash
+ #
+ if ! check_hash "${cache_file}"; then
+ exit 1;
+ fi;
+ fi;
+
+ echo "Unpacking ${name} from cache...";
+ get () { cat "${cache_file}"; }
+ else
+ echo "Downloading ${name}...";
+ get () { curl -L "${url}"; }
+ fi;
+
+ rm -rf "${path}";
+ cd "$(dirname "${path}")";
+ get | ${decompress} | ${unpack};
+ cd /;
+ fi;
+}
+
+
+# If do_get is turned on, check a name out from SVN.
+svn_get () {
+ if ! "${do_get}"; then
+ return 0;
+ fi;
+
+ local name="$1"; shift;
+ local path="$1"; shift;
+ local uri="$1"; shift;
+ local revision="$1"; shift;
+
+ if [ -d "${path}" ]; then
+ local wc_uri="$(svn info --xml "${path}" 2> /dev/null | sed -n 's|^.*<url>\(.*\)</url>.*$|\1|p')";
+
+ if "${force_setup}"; then
+ # Verify that we have a working copy checked out from the correct URI
+ if [ "${wc_uri}" != "${uri}" ]; then
+ echo "Current working copy (${path}) is from the wrong URI: ${wc_uri} != ${uri}";
+ rm -rf "${path}";
+ svn_get "${name}" "${path}" "${uri}" "${revision}";
+ return $?;
+ fi;
+
+ echo "Reverting ${name}...";
+ svn revert -R "${path}";
+
+ echo "Updating ${name}...";
+ svn update -r "${revision}" "${path}";
+ else
+ # Verify that we have a working copy checked out from the correct URI
+ if [ "${wc_uri}" != "${uri}" ]; then
+ echo "Current working copy (${path}) is from the wrong URI: ${wc_uri} != ${uri}";
+ echo "Performing repository switch for ${name}...";
+ svn switch -r "${revision}" "${uri}" "${path}";
+ else
+ local svnversion="$(svnversion "${path}")";
+ if [ "${svnversion%%[M:]*}" != "${revision}" ]; then
+ echo "Updating ${name}...";
+ svn update -r "${revision}" "${path}";
+ fi;
+ fi;
+ fi;
+ else
+ checkout () {
+ echo "Checking out ${name}...";
+ svn checkout -r "${revision}" "${uri}@${revision}" "${path}";
+ }
+
+ if [ "${revision}" != "HEAD" ] && \
+ [ -n "${dep_packages}" ] && \
+ [ -n "${hash}" ] \
+ ; then
+ local cacheid="${name}-$(echo "${uri}" | hash)";
+ local cache_file="${dep_packages}/${cacheid}@r${revision}.tgz";
+
+ mkdir -p "${dep_packages}";
+
+ if [ -f "${cache_file}" ]; then
+ echo "Unpacking ${name} from cache...";
+ mkdir -p "${path}";
+ tar -C "${path}" -xvzf "${cache_file}";
+ else
+ checkout;
+ echo "Caching ${name}...";
+ tar -C "${path}" -cvzf "${cache_file}" .;
+ fi;
+ else
+ checkout;
+ fi;
+ fi;
+}
+
+
+# Run 'make' with the given command line, prepending a -j option appropriate to
+# the number of CPUs on the current machine, if that can be determined.
+jmake () {
+ case "$(uname -s)" in
+ Darwin|Linux)
+ ncpu="$(getconf _NPROCESSORS_ONLN)";
+ ;;
+ FreeBSD)
+ ncpu="$(sysctl hw.ncpu)";
+ ncpu="${ncpu##hw.ncpu: }";
+ ;;
+ esac;
+
+ if [ -n "${ncpu:-}" ] && [[ "${ncpu}" =~ ^[0-9]+$ ]]; then
+ make -j "${ncpu}" "$@";
+ else
+ make "$@";
+ fi;
+}
+
+# Declare a dependency on a C project built with autotools.
+c_dependency () {
+ local f_hash="";
+
+ OPTIND=1;
+ while getopts "m:s:" option; do
+ case "${option}" in
+ 'm') f_hash="-m ${OPTARG}"; ;;
+ 's') f_hash="-s ${OPTARG}"; ;;
+ esac;
+ done;
+ shift $((${OPTIND} - 1));
+
+ local name="$1"; shift;
+ local path="$1"; shift;
+ local uri="$1"; shift;
+
+ # Extra arguments are processed below, as arguments to './configure'.
+
+ srcdir="${dep_sources}/${path}";
+ # local dstroot="${srcdir}/_root";
+ local dstroot="${dev_root}";
+
+ www_get ${f_hash} "${name}" "${srcdir}" "${uri}";
+
+ export PATH="${dstroot}/bin:${PATH}";
+ export C_INCLUDE_PATH="${dstroot}/include:${C_INCLUDE_PATH:-}";
+ export LD_LIBRARY_PATH="${dstroot}/lib:${dstroot}/lib64:${LD_LIBRARY_PATH:-}";
+ export CPPFLAGS="-I${dstroot}/include ${CPPFLAGS:-} ";
+ export LDFLAGS="-L${dstroot}/lib -L${dstroot}/lib64 ${LDFLAGS:-} ";
+ export DYLD_LIBRARY_PATH="${dstroot}/lib:${dstroot}/lib64:${DYLD_LIBRARY_PATH:-}";
+ export PKG_CONFIG_PATH="${dstroot}/lib/pkgconfig:${PKG_CONFIG_PATH:-}";
+
+ if "${do_setup}"; then
+ if "${force_setup}" || [ ! -d "${dstroot}" ]; then
+ echo "Building ${name}...";
+ cd "${srcdir}";
+ ./configure --prefix="${dstroot}" "$@";
+ jmake;
+ jmake install;
+ else
+ echo "Using built ${name}.";
+ echo "";
+ fi;
+ fi;
+}
+
+
+#
+# Build C dependencies
+#
+c_dependencies () {
+
+ if find_header ldap.h 20428 LDAP_VENDOR_VERSION; then
+ using_system "OpenLDAP";
+ else
+ local v="2.4.38";
+ local n="openldap";
+ local p="${n}-${v}";
+ c_dependency -m "39831848c731bcaef235a04e0d14412f" \
+ "OpenLDAP" "${p}" \
+ "http://www.openldap.org/software/download/OpenLDAP/${n}-release/${p}.tgz" \
+ --disable-bdb --disable-hdb;
+ fi;
+
+ if find_header sasl.h; then
+ using_system "SASL";
+ else
+ local v="2.1.26";
+ local n="cyrus-sasl";
+ local p="${n}-${v}";
+ c_dependency -m "a7f4e5e559a0e37b3ffc438c9456e425" \
+ "Cyrus SASL" "${p}" \
+ "ftp://ftp.cyrusimap.org/cyrus-sasl/${p}.tar.gz";
+ fi;
+
+}
+
+
+#
+# Build Python dependencies
+#
+py_dependencies () {
+ mkdir -p "${dev_root}";
+ mkdir -p "${dev_libdir}";
+
+ export PYTHONPATH="${dev_libdir}:${PYTHONPATH:-}"
+
+ export PATH="${dev_root}/bin:${PATH}";
+ export C_INCLUDE_PATH="${dev_root}/include:${C_INCLUDE_PATH:-}";
+ export LD_LIBRARY_PATH="${dev_root}/lib:${dev_root}/lib64:${LD_LIBRARY_PATH:-}";
+ export CPPFLAGS="-I${dev_root}/include ${CPPFLAGS:-} ";
+ export LDFLAGS="-L${dev_root}/lib -L${dev_root}/lib64 ${LDFLAGS:-} ";
+ export DYLD_LIBRARY_PATH="${dev_root}/lib:${dev_root}/lib64:${DYLD_LIBRARY_PATH:-}";
+ export PKG_CONFIG_PATH="${dev_root}/lib/pkgconfig:${PKG_CONFIG_PATH:-}";
+ export CFLAGS="${CPPFLAGS} ${LDFLAGS}";
+
+ cd "${wd}";
+
+ if ! "${python}" ./setup.py develop \
+ --install-dir "${dev_libdir}" \
+ --script-dir "${dev_bindir}" \
+ > "${dev_root}/setup.log" 2>&1; then
+ err=$?;
+ echo "Unable to set up for development:"
+ cat "${dev_root}/setup.log";
+ exit ${err};
+ fi;
+}
Added: twext/trunk/bin/_py.sh
===================================================================
--- twext/trunk/bin/_py.sh (rev 0)
+++ twext/trunk/bin/_py.sh 2014-01-11 01:09:58 UTC (rev 12295)
@@ -0,0 +1,151 @@
+# -*- sh-basic-offset: 2 -*-
+##
+# Copyright (c) 2005-2014 Apple Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+##
+
+set -e
+set -u
+
+# Echo the major.minor version of the given Python interpreter.
+
+py_version () {
+ local python="$1"; shift;
+ echo "$("${python}" -c "from distutils.sysconfig import get_python_version; print get_python_version()")";
+}
+
+#
+# Test if a particular python interpreter is available, given the full path to
+# that interpreter.
+#
+try_python () {
+ local python="$1"; shift;
+
+ if [ -z "${python}" ]; then
+ return 1;
+ fi;
+
+ if ! type "${python}" > /dev/null 2>&1; then
+ return 1;
+ fi;
+
+ local py_version="$(py_version "${python}")";
+ if [ "${py_version/./}" -lt "25" ]; then
+ return 1;
+ fi;
+ return 0;
+}
+
+
+#
+# Detect which version of Python to use, then print out which one was detected.
+#
+detect_python_version () {
+ local v;
+ local p;
+ for v in "2.7" "2.6" ""
+ do
+ for p in \
+ "${PYTHON:=}" \
+ "python${v}" \
+ "/usr/local/bin/python${v}" \
+ "/usr/local/python/bin/python${v}" \
+ "/usr/local/python${v}/bin/python${v}" \
+ "/opt/bin/python${v}" \
+ "/opt/python/bin/python${v}" \
+ "/opt/python${v}/bin/python${v}" \
+ "/Library/Frameworks/Python.framework/Versions/${v}/bin/python" \
+ "/opt/local/bin/python${v}" \
+ "/sw/bin/python${v}" \
+ ;
+ do
+ if try_python "${p}"; then
+ echo "${p}";
+ return 0;
+ fi;
+ done;
+ done;
+ return 1;
+}
+
+
+#
+# Compare version numbers
+#
+cmp_version () {
+ local v="$1"; shift;
+ local mv="$1"; shift;
+
+ local result;
+
+ while true; do
+ vh="${v%%.*}"; # Get highest-order segment
+ mvh="${mv%%.*}";
+
+ if [ "${vh}" -gt "${mvh}" ]; then
+ result=1;
+ break;
+ fi;
+
+ if [ "${vh}" -lt "${mvh}" ]; then
+ result=0;
+ break;
+ fi;
+
+ if [ "${v}" == "${v#*.}" ]; then
+ # No dots left, so we're ok
+ result=0;
+ break;
+ fi;
+
+ if [ "${mv}" == "${mv#*.}" ]; then
+ # No dots left, so we're not gonna match
+ result=1;
+ break;
+ fi;
+
+ v="${v#*.}";
+ mv="${mv#*.}";
+ done;
+
+ return ${result};
+}
+
+
+#
+# Detect which python to use, and store it in the 'python' variable, as well as
+# setting up variables related to version and build configuration.
+#
+init_py () {
+ # First, detect the appropriate version of Python to use, based on our version
+ # requirements and the environment. Note that all invocations of python in
+ # our build scripts should therefore be '"${python}"', not 'python'; this is
+ # important on systems with older system pythons (2.4 or earlier) with an
+ # alternate install of Python, or alternate python installation mechanisms
+ # like virtualenv.
+ python="$(detect_python_version)";
+
+ # Set the $PYTHON environment variable to an absolute path pointing at the
+ # appropriate python executable, a standard-ish mechanism used by certain
+ # non-distutils things that need to find the "right" python. For instance,
+ # the part of the PostgreSQL build process which builds pl_python. Note that
+ # detect_python_version, above, already honors $PYTHON, so if this is already
+ # set it won't be stomped on, it will just be re-set to the same value.
+ export PYTHON="$(type -p ${python})";
+
+ if [ -z "${python:-}" ]; then
+ echo "No suitable python found. Python 2.6+ is required.";
+ exit 1;
+ fi
+}
Modified: twext/trunk/develop
===================================================================
--- twext/trunk/develop 2014-01-11 00:03:44 UTC (rev 12294)
+++ twext/trunk/develop 2014-01-11 01:09:58 UTC (rev 12295)
@@ -21,33 +21,7 @@
wd="$(cd "$(dirname "$0")" && pwd)";
-if [ -n "${PYTHON:-}" ]; then
- python="${PYTHON}";
-else
- python=python;
-fi;
-
-##
-# Enable development mode
-##
-
-dev_root="${wd}/.develop";
-dev_libdir="${dev_root}/lib";
-dev_bindir="${dev_root}/bin";
-
-mkdir -p "${dev_root}";
-mkdir -p "${dev_libdir}";
-
-export PYTHONPATH="${dev_libdir}:${PYTHONPATH:-}"
-
-cd "${wd}";
-
-if ! ./setup.py develop \
- --install-dir "${dev_libdir}" \
- --script-dir "${dev_bindir}" \
- > "${dev_root}/setup.log" 2>&1; then
- err=$?;
- echo "Unable to set up for development:"
- cat "${dev_root}/setup.log";
- exit ${err};
-fi;
+. "${wd}/bin/_build.sh";
+init_build;
+c_dependencies;
+py_dependencies;
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.macosforge.org/pipermail/calendarserver-changes/attachments/20140312/abb05dd0/attachment.html>
More information about the calendarserver-changes
mailing list