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