1. Ronald Oussoren
  2. objective.metadata

Commits

Ronald Oussoren  committed 88f4d53

Add protocols command

The protocols command generates a fragment of an extension module
with a C function that is not expected to be called, but ensures
that all protocols defined in a framework are available at runtime.

  • Participants
  • Parent commits 024fb58
  • Branches default

Comments (0)

Files changed (2)

File objective/metadata/main.py

View file
 from . import compile
 from . import merge_xml
 from . import scan
+from . import protocols
 
 parser = argparse.ArgumentParser(prog="objective-metadata-tool")
 parser.add_argument("--verbose", action="store_true", help="print progress information", default=False)
 import_command.add_argument("--bridgesupport-file", metavar="FILE", help="Use this bridgesupport file")
 
 compile_command = subparsers.add_parser("compile", help="Compile data into the PyObjC data file")
+protocols_command = subparsers.add_parser("protocols", help="Compile data into the protocols C file")
 
 
 def make_pairs(sequence):
         else:
             info["compiled"] = os.path.join(ini_dir, "..", "Lib", info["python-package"].replace('.', os.path.sep), "_metadata.py")
 
+        if cfg.has_option(section, "protocols-file"):
+            info["protocols-file"] = os.path.join(ini_dir, cfg.get(section, "protocols-file"))
+        else:
+            info["protocols-file"] = os.path.join(ini_dir, "..", "Modules", "_" + info["framework"] + "_protocols.m")
+
         if cfg.has_option(section, "only-headers"):
             info["only-headers"] = [x.strip() for x in cfg.get(section, "only-headers").split(",")]
         else:
                     glob.glob(info["raw"] + "/*"), 
             )
 
+        elif args.command == "protocols":
+            protocols.compile_protocol_file(
+                info["protocols-file"],
+                info["exceptions"],
+                glob.glob(info["raw"] + "/*"),
+            )
+
+
         else:
             print "Internal error: Unimplemented command: %s"%(args.command,)
             sys.exit(1)

File objective/metadata/protocols.py

View file
+from __future__ import absolute_import
+import sys, glob, textwrap, time, itertools, collections, operator
+from .storage import load_framework_info
+import re
+import collections
+
+from .compile import _is32Bit, _is64Bit, _isLittleEndian, _isBigEndian
+
+
+class MergeNeededException (Exception):
+    pass
+
+
+HEADER=textwrap.dedent("""\
+    /* 
+     * This file is generated by objective.metadata
+     *
+     * Last update: %(timestamp)s
+     */
+
+    static void __attribute__((__used__)) use_protocols(void)
+    {
+        PyObject* p;
+""")
+
+FOOTER=textwrap.dedent("""\
+    }
+""")
+
+FIND_VERSION=re.compile(r'MacOSX(\d+\.\d+)u?.sdk')
+
+
+def classify_archs(archs):
+    if _is32Bit(archs):
+        return '!defined(__LP64__)'
+
+    if _is64Bit(archs):
+        print([(a, _is32Bit(a)) for a in archs])
+        return 'defined(__LP64__)'
+
+    if 0:
+        # XXX: disabled for now because we don't
+        #      have ppc metadata
+        if _isLittleEndian(archs):
+            return '!defined(__BIG_ENDIAN__)'
+
+        if _isBigEndian(archs):
+            return 'defined(__BIG_ENDIAN__)'
+
+
+    return None
+
+def classify_versions(versions):
+    min_version = min(versions)
+    if min_version == '10.6':
+        return None
+
+    return '(MAC_OS_X_VERSION_MIN_REQUIRED <= MAC_OS_X_VERSION_%s) && defined(MAC_OS_X_VERSION_%s)'%(
+            min_version.replace('.', '_'),
+            min_version.replace('.', '_'),
+            )
+
+
+def protocol_selector(found_info):
+    versions_seen = { info[0] for info in found_info }
+    archs_seen = { info[1] for info in found_info }
+
+    selectors = []
+    arch_selector = classify_archs(archs_seen)
+    if arch_selector is not None:
+        selectors.append(arch_selector)
+
+    version_selector = classify_versions(versions_seen)
+    if version_selector is not None:
+        selectors.append(version_selector)
+
+    if selectors:
+        return ' && '.join(selectors)
+
+    return None
+
+
+
+
+
+def compile_protocol_file(output_fn, exceptions_fn, headerinfo_fns):
+    """
+    Combine the data from header files scans and manual exceptions
+    into a file than is usable for the metadata support in 
+    pyobjc 2.4 or later.
+    """
+    exceptions = load_framework_info(exceptions_fn)
+    headerinfo = [load_framework_info(fn) for fn in headerinfo_fns]
+    with open(output_fn, 'w') as fp:
+        fp.write(HEADER % dict(
+            timestamp=time.ctime(),
+        ))
+
+        protocols = collections.defaultdict(list)
+
+        for info in headerinfo:
+            sdk = FIND_VERSION.search(info['sdk']).group(1)
+            arch = info['arch']
+
+            for name in info['definitions'].get('formal_protocols', ()):
+                protocols[name].append((sdk, arch))
+
+        grouped_names = collections.defaultdict(list)
+
+        for name in protocols:
+            selector = protocol_selector(protocols[name])
+            grouped_names[selector].append(name)
+
+        for selector in sorted(grouped_names):
+            if selector is not None: fp.write('#if %s\n'%(selector,))
+            for nm in sorted(grouped_names[selector]):
+                fp.write("    p = PyObjC_ObjCToPython(\"@\", @protocol(%s)); Py_XDECREF(p);\n"%(nm,))
+            if selector is not None: fp.write('#endif /* %s */\n'%(selector,))
+
+        fp.write(FOOTER)