Looks like I should have filed this as an Ant Enhancement task. I've just done so and filed Ant Bug 55899 along with a patch:
https://issues.apache.org/bugzilla/show_bug.cgi?id=55899 Ben On Tue, Dec 17, 2013 at 10:50 AM, Ben Gertzfield <bgertzfi...@gmail.com>wrote: > Excellent. I'm not sure what the process is for code review, but here's a > patch against SVN trunk @ r1551525. > > The patch adds an OS X .pkg installer generation script under > 'release/build-osx-pkg.py' and updates build.xml to conditionally build an > OS X .pkg installer when it's run on OS X hosts. > > From bf8c0746ef979aff0e439b4e2fe917e78023356a Mon Sep 17 00:00:00 2001 > From: Ben Gertzfield <b...@fb.com> > Date: Tue, 17 Dec 2013 10:43:53 -0800 > Subject: [PATCH] Add OS X build script. Split targets zip_distribution, > tar_distribution, pkg_distribution off main_distribution. > > --- > build.xml | 27 ++++++- > release/build-osx-pkg.py | 179 > +++++++++++++++++++++++++++++++++++++++++++++++ > 2 files changed, 204 insertions(+), 2 deletions(-) > create mode 100755 release/build-osx-pkg.py > > diff --git a/build.xml b/build.xml > index af9123c..c1e58f1 100644 > --- a/build.xml > +++ b/build.xml > @@ -1148,8 +1148,7 @@ > Create the binary distribution > =================================================================== > --> > - <target name="main_distribution" depends="jars-sources,test-jar-source" > - description="--> creates the zip and tar distributions"> > + <target name="-distribution_prep"> > <delete dir="${dist.base}"/> > <delete dir="${dist.name}"/> > <delete dir="${java-repository.dir}"/> > @@ -1161,6 +1160,10 @@ > <antcall inheritAll="false" target="internal_dist"> > <param name="dist.dir" value="${dist.name}"/> > </antcall> > + </target> > + > + <target name="zip_distribution" depends="jars,-distribution_prep" > + description="--> creates the zip distribution"> > <zip destfile="${dist.base.binaries}/${dist.name}-bin.zip"> > <zipfileset dir="${dist.name}/.." filemode="755"> > <include name="${dist.name}/bin/ant"/> > @@ -1176,6 +1179,22 @@ > <exclude name="${dist.name}/bin/*.py"/> > </fileset> > </zip> > + </target> > + > + <condition property="buildosxpackage"> > + <os family="mac"/> > + </condition> > + > + <target name="pkg_distribution" depends="zip_distribution" > if="buildosxpackage"> > + <exec executable="release/build-osx-pkg.py"> > + <arg value="--output-dir"/> > + <arg value="${dist.base.binaries}"/> > + <arg value="${dist.base.binaries}/${dist.name}-bin.zip"/> > + </exec> > + </target> > + > + <target name="tar_distribution" depends="jars,-distribution_prep" > + description="--> creates the tar distribution"> > <tar longfile="gnu" > destfile="${dist.base.binaries}/${dist.name}-bin.tar"> > <!-- removes redundant definition of permissions, but seems to > @@ -1201,6 +1220,10 @@ > <bzip2 destfile="${dist.base.binaries}/${dist.name}-bin.tar.bz2" > src="${dist.base.binaries}/${dist.name}-bin.tar"/> > <delete file="${dist.base.binaries}/${dist.name}-bin.tar"/> > + </target> > + > + <target name="main_distribution" > depends="zip_distribution,pkg_distribution,tar_distribution,jars-sources,test-jar-source" > + description="--> creates the zip, pkg, and tar distributions"> > > <copy todir="${java-repository.dir}"> > <fileset dir="${dist.name}/lib"> > diff --git a/release/build-osx-pkg.py b/release/build-osx-pkg.py > new file mode 100755 > index 0000000..4144a03 > --- /dev/null > +++ b/release/build-osx-pkg.py > @@ -0,0 +1,179 @@ > +#!/usr/bin/env python > + > +# Licensed to the Apache Software Foundation (ASF) under one or more > +# contributor license agreements. See the NOTICE file distributed with > +# this work for additional information regarding copyright ownership. > +# The ASF licenses this file to You 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. > + > +# Builds a Mac OS X .pkg from a binary ZIP archive of Apache Ant. > + > +import collections > +import contextlib > +import os > + > +ApacheAntURL = collections.namedtuple( > + 'ApacheAntURL', > + ('url', 'url_scheme', 'version', 'directory_name')) > + > +@contextlib.contextmanager > +def make_temp_directory(): > + '''Creates a temporary directory which is recursively deleted when > out of scope.''' > + import shutil > + import tempfile > + temp_dir = tempfile.mkdtemp() > + yield temp_dir > + shutil.rmtree(temp_dir) > + > +@contextlib.contextmanager > +def self_closing_url(url): > + '''Opens a URL and returns a self-closing file-like object.''' > + import urllib2 > + url_fp = urllib2.urlopen(url) > + yield url_fp > + url_fp.close() > + > +def apache_ant_url(url_string): > + '''Parses a URL string into an ApacheAntURL object.''' > + import argparse, collections, os.path, urlparse > + parse_result = urlparse.urlparse(url_string) > + filename = os.path.split(parse_result.path)[1] > + if not (filename.startswith('apache-ant-') and > filename.endswith('-bin.zip')): > + raise argparse.ArgumentTypeError( > + 'Expected [%s] to end with apache-ant-X.Y.Z-bin.zip' % > (url_string)) > + extracted_directory = filename.replace('-bin.zip', '') > + extracted_version = extracted_directory.replace('apache-ant-', '') > + return ApacheAntURL( > + url=url_string, > + url_scheme=parse_result.scheme, > + version=extracted_version, > + directory_name=extracted_directory) > + > +def fetch_url(url, local_output_file): > + '''Downloads the contents of 'url' and writes them the opened file > 'output_file'.''' > + import shutil > + import urllib2 > + CHUNK_SIZE = 16 * 1024 > + print 'Fetching {url}...'.format(url=url) > + with self_closing_url(url) as url_input_file: > + while True: > + chunk = url_input_file.read(CHUNK_SIZE) > + if not chunk: > + break > + local_output_file.write(chunk) > + local_output_file.seek(0) > + > +def fetch_apache_ant_url(apache_ant_url, temp_dir): > + '''If the ApacheAntURL object is remote, fetches and returns the > local file object. > + Otherwise, opens and returns a file object.''' > + import tempfile > + if apache_ant_url.url_scheme == '' or apache_ant_url.url_scheme == > 'file': > + return open(apache_ant_url.url, 'rb') > + else: > + fp = tempfile.TemporaryFile(dir=temp_dir) > + fetch_url(apache_ant_url.url, fp) > + return fp > + > +def uncompress_contents(temp_dir, archive_file, directory_name, > path_prefix): > + '''Uncompresses the contents of 'archive_file' to 'temp_dir'. > + > + Strips the prefix 'directory_name' and prepends 'path_prefix' to each > entry > + of the zip file. > + ''' > + import shutil, zipfile > + output_path = os.path.join(temp_dir, 'pkg') > + os.mkdir(output_path) > + z = zipfile.ZipFile(archive_file) > + print 'Extracting archive to {output_path}...'.format( > + output_path=output_path) > + for entry in z.infolist(): > + # We can't just extract directly, since we want to map: > + # > + # apache-ant-X.Y.Z/bin/foo > + # > + # to > + # > + # usr/local/ant/bin/foo > + # > + # So, we strip out the apache-ant-X.Y.Z prefix, then instead of > + # using ZipFile.extract(), we use ZipFile.open() to get a read fd > to > + # the source file, then os.fdopen() with the appropriate > permissions > + # to geta write fd to the modified destination path. > + expected_prefix = directory_name + '/' > + if not entry.filename.startswith(expected_prefix): > + raise Exeption('Unexpected entry in zip file: > [{filename}]'.format( > + filename=entry.filename)) > + entry_path = entry.filename.replace(expected_prefix, '', 1) > + > + # Using os.path.join is annoying here (we'd have to explode > output_path > + # and entry_path). > + entry_output_path = output_path + path_prefix + '/' + entry_path > + > + # Zip file paths are normalized with '/' at the end for > directories. > + if entry_output_path.endswith('/'): > + print 'Creating directory > {path}'.format(path=entry_output_path) > + os.makedirs(entry_output_path) > + else: > + # Yes, this is really how you extract permissions from a > ZipInfo entry. > + perms = (entry.external_attr >> 16L) & 0777 > + print 'Extracting {entry_filename} to {path} with mode > 0{mode:o}'.format( > + entry_filename=entry.filename, path=entry_output_path, > mode=perms) > + with z.open(entry) as source: > + with os.fdopen( > + os.open(entry_output_path, os.O_WRONLY | os.O_CREAT, > perms), 'w') \ > + as destination: > + shutil.copyfileobj(source, destination) > + return output_path > + > +def write_paths_d_entry(paths_d_directory, filename): > + os.makedirs(paths_d_directory) > + output_file = os.path.join(paths_d_directory, filename) > + with open(output_file, 'w') as f: > + print >>f, '/usr/local/ant/bin' > + > +def make_pkg(pkg_dir, pkg_identifier, pkg_version, output_pkg_path): > + import subprocess > + print 'Building package at {output_pkg_path}...'.format( > + output_pkg_path=output_pkg_path) > + subprocess.call( > + ['pkgbuild', > + '--root', pkg_dir, > + '--identifier', pkg_identifier, > + '--version', pkg_version, > + output_pkg_path]) > + > +def main(): > + import argparse > + parser = argparse.ArgumentParser(description='Builds a Mac OS X .pkg > of ant.') > + parser.add_argument( > + 'apache_ant_url', > + metavar='file-or-url', > + help='Source file or URL from which to uncompress > apache-ant-X.Y.Z-bin.zip', > + type=apache_ant_url) > + parser.add_argument( > + '--output-dir', > + default='.', > + help='Directory to which .pkg will be written. Defaults to > current directory.') > + args = parser.parse_args() > + with make_temp_directory() as temp_dir: > + archive_file = fetch_apache_ant_url(args.apache_ant_url, temp_dir) > + pkg_dir = uncompress_contents( > + temp_dir, archive_file, args.apache_ant_url.directory_name, > '/usr/local/ant') > + etc_paths_d_dir = os.path.join(pkg_dir, 'etc', 'paths.d') > + write_paths_d_entry(etc_paths_d_dir, 'org.apache.ant') > + pkg_identifier = 'org.apache.ant' > + output_pkg_path = os.path.join( > + args.output_dir, args.apache_ant_url.directory_name + '.pkg') > + make_pkg(pkg_dir, pkg_identifier, args.apache_ant_url.version, > output_pkg_path) > + > +if __name__ == '__main__': > + main() > -- > 1.8.3.4 (Apple Git-47) > > > > On Mon, Dec 16, 2013 at 9:17 PM, Antoine Levy Lambert <anto...@gmx.de>wrote: > >> Hello Ben, >> >> this should be living in ant core. Either in the root directory or in the >> release directory. >> >> Regards, >> >> Antoine >> On Dec 16, 2013, at 8:18 PM, Ben Gertzfield wrote: >> >> > Hi Ant folks, >> > >> > As of OS X version 10.9 ("Mavericks"), OS X no longer includes Apache >> Ant. >> > >> > To help people who don't want to install Homebrew or similar systems to >> > grab Ant, I wrote a Python script to build a OS X .pkg installer of Ant: >> > >> > http://pastebin.com/raw.php?i=StmYCeZd >> > >> > I'd like to contribute this script (which runs on OS X) to the project, >> but >> > I'm not sure which Ant repo it belongs in (core? build? antlibs?). >> > >> > Where should this script live? >> > >> > Thanks, >> > >> > Ben >> >> >> --------------------------------------------------------------------- >> To unsubscribe, e-mail: dev-unsubscr...@ant.apache.org >> For additional commands, e-mail: dev-h...@ant.apache.org >> >> >