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
>>
>>
>

Reply via email to