|
|
@@ -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() |