Skip to content

Instantly share code, notes, and snippets.

@jakirkham
Forked from minrk/conda-inspect-mac-target
Created November 28, 2016 17:52
Show Gist options
  • Save jakirkham/8f26e372e50a3d4a6ad87a43a463de83 to your computer and use it in GitHub Desktop.
Save jakirkham/8f26e372e50a3d4a6ad87a43a463de83 to your computer and use it in GitHub Desktop.
#!/usr/bin/env python
"""
Inspect MACOSX deployment targets of conda packages
Uses `otool -l` and checks for LC_VERSION_MIN_MACOSX command to verify that dylibs target the right version of OS X
BY: Min RK
LICENSE: CC0, Public Domain
"""
from __future__ import print_function
from distutils.version import LooseVersion as V
import os
import sys
from subprocess import check_output
from conda.install import linked_data
def _otool_l(f):
"""Run and parse `otool -l` output on a file
Returns a list of command dicts, parsed from the
"""
output = check_output(['otool', '-l', f]).decode('utf8', 'replace').splitlines()
commands = []
cmd = None
for line in output:
if not line.startswith((' ', '\t')):
# new command, save the last one
if cmd:
commands.append(cmd)
cmd = {
'name': line,
'data': {},
}
elif cmd is not None:
parts = line.strip().split(None, 1)
if len(parts) == 2:
cmd['data'][parts[0]] = parts[1]
else:
# ignore these lines?
pass
return commands
def get_min_macosx_target(dylib):
"""Get the minimum macosx target for a dylib
Returns None if none can be found.
"""
commands = _otool_l(dylib)
min_macosx_commands = [ c for c in commands if c['data'].get('cmd') == 'LC_VERSION_MIN_MACOSX' ]
if not min_macosx_commands:
return
command = min_macosx_commands[0]
return command['data']['version']
def inspect_macosx_targets(selected_packages=None, target_version='10.9', prefix=sys.prefix,
strict=False, verbose=False):
"""Inspect dylibs for compliance with a given MACOSX_DEPLOYMENT_TARGET
Any dylibs with deployment target greater than target_version will fail.
Default behavior is to treat dylibs with no target as okay.
If strict=True, dylibs with no target will fail.
"""
packages = linked_data(prefix)
target_v = V(target_version)
bad = {}
seen = set()
for pkg in packages.values():
name = pkg['name']
if selected_packages:
if name in selected_packages:
seen.add(name)
else:
continue
for f in pkg['files']:
if not f.endswith('.dylib'):
continue
path = os.path.join(prefix, f)
macosx_target = get_min_macosx_target(path)
if not macosx_target:
if verbose:
print("%s:%s - no target" % (name, path), file=sys.stderr)
if strict:
bad.setdefault(name, [])
bad[name].append((f, macosx_target))
elif V(macosx_target) > target_v:
if verbose:
print("%s:%s - %s" % (name, path, macosx_target), file=sys.stderr)
bad.setdefault(name, [])
bad[name].append((f, macosx_target))
elif verbose:
print("%s:%s - %s" % (name, path, macosx_target))
if selected_packages:
missed = set(selected_packages).difference(seen)
if missed:
print("No such packages: %s" % ', '.join(sorted(missed)), file=sys.stderr)
return True
if bad:
for name, bad_files in bad.items():
print("%s has %i files with bad targets:" % (name, len(bad_files)), file=sys.stderr)
for (f, macosx_target) in bad_files:
print(" %s: %s" % (f, macosx_target), file=sys.stderr)
return True
def main():
import argparse
parser = argparse.ArgumentParser()
parser.add_argument(nargs='*', dest='packages', help='packages to inspect (default: all packages in the env)')
parser.add_argument('--target', default='10.9',
help='target threshold for minimum macOS version')
parser.add_argument('--prefix', default=os.environ.get('CONDA_PREFIX') or sys.prefix,
help='conda prefix in which to run (default: current env)')
parser.add_argument('--strict', action='store_true',
help='treat dylibs with no target as fail rather than pass')
parser.add_argument('-v', '--verbose', action='store_true',
help='verbose output')
opts = parser.parse_args()
bad = inspect_macosx_targets(opts.packages, opts.target, prefix=opts.prefix,
strict=opts.strict, verbose=opts.verbose)
if bad:
sys.exit(1)
if __name__ == '__main__':
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment