forked from conan-io/conan-extensions
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
initial version of deploy-lipo, implements conan-io#52
- Loading branch information
Showing
6 changed files
with
1,260 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
## Lipo commands | ||
|
||
These are commands to deploy macOS or iOS universal binaries. | ||
This wraps around Conan's full_deploy deployer and then runs `lipo` to | ||
produce universal binaries. | ||
|
||
|
||
#### [Deploy lipo](cmd_deploy_lipo.py) | ||
|
||
|
||
**Parameters** | ||
|
||
The same parameters as `deploy` except multiple profiles should be specified. | ||
|
||
``` | ||
$ conan deploy-lipo . -pr x8_64 -pr armv8 -b missing -r conancenter | ||
``` | ||
|
||
This assumes profiles named x86_64 and armv8 with corresponding architectures. | ||
Universal binaries can only have one binary per architecture but each can have | ||
different settings, e.g. minimum deployment OS: | ||
|
||
## x86_64 | ||
``` | ||
[settings] | ||
arch=x86_64 | ||
build_type=Release | ||
compiler=apple-clang | ||
compiler.cppstd=gnu17 | ||
compiler.libcxx=libc++ | ||
compiler.version=14 | ||
os=Macos | ||
os.version=10.13 | ||
``` | ||
|
||
## armv8 | ||
``` | ||
[settings] | ||
arch=armv8 | ||
build_type=Release | ||
compiler=apple-clang | ||
compiler.cppstd=gnu17 | ||
compiler.libcxx=libc++ | ||
compiler.version=14 | ||
os=Macos | ||
os.version=11.0 | ||
``` | ||
|
||
|
||
#### [Example project](xcode) | ||
|
||
|
||
``` | ||
cd ./xcode | ||
conan deploy-lipo . -pr x86_64 -pr armv8 -b missing | ||
conan build . | ||
``` | ||
|
||
Verify the architectures: | ||
``` | ||
file ./build/Release/example | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,95 @@ | ||
import os | ||
import shutil | ||
from subprocess import run | ||
|
||
|
||
__all__ = ['is_macho_binary', 'lipo'] | ||
|
||
# These are for optimization only, to avoid unnecessarily reading files. | ||
_binary_exts = ['.a', '.dylib'] | ||
_regular_exts = [ | ||
'.h', '.hpp', '.hxx', '.c', '.cc', '.cxx', '.cpp', '.m', '.mm', '.txt', '.md', '.html', '.jpg', '.png' | ||
] | ||
|
||
|
||
def is_macho_binary(filename): | ||
ext = os.path.splitext(filename)[1] | ||
if ext in _binary_exts: | ||
return True | ||
if ext in _regular_exts: | ||
return False | ||
with open(filename, "rb") as f: | ||
header = f.read(4) | ||
if header == b'\xcf\xfa\xed\xfe': | ||
# cffaedfe is Mach-O binary | ||
return True | ||
elif header == b'\xca\xfe\xba\xbe': | ||
# cafebabe is Mach-O fat binary | ||
return True | ||
elif header == b'!<arch>\n': | ||
# ar archive | ||
return True | ||
return False | ||
|
||
|
||
def copy_arch_file(src, dst, top=None, arch_folders=()): | ||
if os.path.isfile(src): | ||
if top and arch_folders and is_macho_binary(src): | ||
# Try to lipo all available archs on the first path. | ||
src_components = src.split(os.path.sep) | ||
top_components = top.split(os.path.sep) | ||
if src_components[:len(top_components)] == top_components: | ||
paths = [os.path.join(a, *(src_components[len(top_components):])) for a in arch_folders] | ||
paths = [p for p in paths if os.path.isfile(p)] | ||
if len(paths) > 1: | ||
run(['lipo', '-output', dst, '-create'] + paths, check=True) | ||
return | ||
if os.path.exists(dst): | ||
pass # don't overwrite existing files | ||
else: | ||
shutil.copy2(src, dst) | ||
|
||
|
||
# Modified copytree to copy new files to an existing tree. | ||
def graft_tree(src, dst, symlinks=False, copy_function=shutil.copy2, dirs_exist_ok=False): | ||
names = os.listdir(src) | ||
os.makedirs(dst, exist_ok=dirs_exist_ok) | ||
errors = [] | ||
for name in names: | ||
srcname = os.path.join(src, name) | ||
dstname = os.path.join(dst, name) | ||
try: | ||
if symlinks and os.path.islink(srcname): | ||
if os.path.exists(dstname): | ||
continue | ||
linkto = os.readlink(srcname) | ||
os.symlink(linkto, dstname) | ||
elif os.path.isdir(srcname): | ||
graft_tree(srcname, dstname, symlinks, copy_function, dirs_exist_ok) | ||
else: | ||
copy_function(srcname, dstname) | ||
# What about devices, sockets etc.? | ||
# catch the Error from the recursive graft_tree so that we can | ||
# continue with other files | ||
except shutil.Error as err: | ||
errors.extend(err.args[0]) | ||
except OSError as why: | ||
errors.append((srcname, dstname, str(why))) | ||
try: | ||
shutil.copystat(src, dst) | ||
except OSError as why: | ||
# can't copy file access times on Windows | ||
if why.winerror is None: # pylint: disable=no-member | ||
errors.extend((src, dst, str(why))) | ||
if errors: | ||
raise shutil.Error(errors) | ||
|
||
def lipo(dst_folder, arch_folders): | ||
for folder in arch_folders: | ||
graft_tree(folder, | ||
dst_folder, | ||
symlinks=True, | ||
copy_function=lambda s, d, top=folder: copy_arch_file(s, d, | ||
top=top, | ||
arch_folders=arch_folders), | ||
dirs_exist_ok=True) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,60 @@ | ||
import os | ||
import shutil | ||
from subprocess import run | ||
|
||
from conan.api.conan_api import ConanAPI | ||
from conan.api.output import ConanOutput | ||
from conan.cli.command import conan_command | ||
from conan.cli.args import common_graph_args | ||
from conan.errors import ConanException | ||
|
||
from _lipo import lipo as lipo_folder | ||
|
||
|
||
_valid_archs = [ | ||
'x86', | ||
'x86_64', | ||
'armv7', | ||
'armv8', | ||
'armv8_32', | ||
'armv8.3', | ||
'armv7s', | ||
'armv7k' | ||
] | ||
|
||
@conan_command(group="Consumer") | ||
def deploy_lipo(conan_api: ConanAPI, parser, *args): | ||
""" | ||
Deploy dependencies for multiple profiles and lipo into universal binaries | ||
""" | ||
common_graph_args(parser) | ||
parsed = parser.parse_args(*args) | ||
profiles = parsed.profile_host or parsed.profile | ||
if not profiles: | ||
raise ConanException("Please provide profiles with -pr or -pr:h") | ||
other_args = [] | ||
i = 0 | ||
while i < len(args[0]): | ||
arg = args[0][i] | ||
if arg in ['-pr', '-pr:h', '--profile', '--profile:host']: | ||
i += 2 | ||
else: | ||
other_args.append(arg) | ||
i += 1 | ||
for profile in profiles: | ||
run(['conan', 'install', | ||
'--deploy=full_deploy', | ||
'-pr:h', profile | ||
] + other_args) | ||
output_dir = os.path.join('full_deploy', 'host') | ||
for package in os.listdir(output_dir): | ||
package_dir = os.path.join(output_dir, package) | ||
for version in os.listdir(package_dir): | ||
version_dir = os.path.join(package_dir, version) | ||
for build in os.listdir(version_dir): | ||
d = os.path.join(version_dir, build) | ||
archs = [os.path.join(d, x) for x in os.listdir(d) if x in _valid_archs] | ||
# We could skip if len(archs) == 1 but the dir layout would be different | ||
lipo_folder(d, archs) | ||
for arch in archs: | ||
shutil.rmtree(arch) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
from conan import ConanFile | ||
from conan.tools.apple import XcodeBuild | ||
|
||
|
||
class UniversalRecipe(ConanFile): | ||
# Note that we don't depend on arch | ||
settings = "os", "compiler", "build_type" | ||
requires = ("libtiff/4.5.0",) | ||
|
||
def build(self): | ||
# xcodebuild = XcodeBuild(self) | ||
# Don't use XcodeBuild because it passes a single -arch flag | ||
build_type = self.settings.get_safe("build_type") | ||
project = 'example.xcodeproj' | ||
self.run('xcodebuild -configuration {} -project {} -alltargets'.format(build_type, project)) |
Oops, something went wrong.