Skip to content

Commit

Permalink
build: OpenCV 4 for iOS
Browse files Browse the repository at this point in the history
  • Loading branch information
homuler committed Aug 25, 2024
1 parent e4f9f9d commit e5b215c
Show file tree
Hide file tree
Showing 5 changed files with 331 additions and 3 deletions.
6 changes: 3 additions & 3 deletions WORKSPACE
Original file line number Diff line number Diff line change
Expand Up @@ -435,18 +435,18 @@ http_archive(
url = "https://github.com/opencv/opencv/releases/download/3.2.0/opencv-3.2.0-ios-framework.zip",
)

# Building an opencv.xcframework from the OpenCV 4.5.3 sources is necessary for
# Building an opencv.xcframework from the OpenCV 4.5.5 sources is necessary for
# MediaPipe iOS Task Libraries to be supported on arm64(M1) Macs. An
# `opencv.xcframework` archive has not been released and it is recommended to
# build the same from source using a script provided in OpenCV 4.5.0 upwards.
# OpenCV is fixed to version to 4.5.3 since swift support can only be disabled
# OpenCV is fixed to version to 4.5.5 since swift support can only be disabled
# from 4.5.3 upwards. This is needed to avoid errors when the library is linked
# in Xcode. Swift support will be added in when the final binary MediaPipe iOS
# Task libraries are built.
http_archive(
name = "ios_opencv_source",
sha256 = "a61e7a4618d353140c857f25843f39b2abe5f451b018aab1604ef0bc34cd23d5",
build_file = "@mediapipe//third_party:opencv_ios_source.BUILD",
build_file = "@//third_party:opencv_ios_source.BUILD",
type = "zip",
url = "https://github.com/opencv/opencv/archive/refs/tags/4.5.3.zip",
)
Expand Down
13 changes: 13 additions & 0 deletions third_party/mediapipe_workaround.diff
Original file line number Diff line number Diff line change
Expand Up @@ -34,3 +34,16 @@ index a11a23fc..dbb5fe6c 100644
core::TaskRunner::Create(
std::move(graph_config), std::move(resolver),
std::move(packets_callback), std::move(default_executor),
diff --git a/third_party/opencv_ios_source.bzl b/third_party/opencv_ios_source.bzl
index 54bb4d5e..9632afd8 100644
--- a/third_party/opencv_ios_source.bzl
+++ b/third_party/opencv_ios_source.bzl
@@ -15,7 +15,7 @@
"""Custom rules for building iOS OpenCV xcframework from sources."""

load(
- "@//third_party:opencv_ios_xcframework_files.bzl",
+ "@mediapipe//third_party:opencv_ios_xcframework_files.bzl",
"OPENCV_XCFRAMEWORK_INFO_PLIST_PATH",
"OPENCV_XCFRAMEWORK_IOS_DEVICE_FILE_PATHS",
"OPENCV_XCFRAMEWORK_IOS_SIMULATOR_FILE_PATHS",
11 changes: 11 additions & 0 deletions third_party/opencv.BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,16 @@ config_setting(
},
)

config_setting(
name = "opencv_ios_source_build",
flag_values = {
":switch": "cmake",
},
values = {
"apple_platform_type": "ios",
},
)

selects.config_setting_group(
name = "cmake_static_win",
match_all = ["@bazel_tools//src/conditions:windows", ":cmake_static"],
Expand All @@ -62,6 +72,7 @@ alias(
name = "opencv",
actual = select({
":cmake_static": ":opencv_cmake",
":opencv_ios_source_build": "@ios_opencv_source//:opencv",
"//conditions:default": ":opencv_binary",
}),
)
Expand Down
145 changes: 145 additions & 0 deletions third_party/opencv_ios_source.BUILD
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
# Copyright 2023 The MediaPipe Authors.
#
# Licensed 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.

# CHANGES:
# - OpenCV 4.5.3 -> 4.5.5

# Description:
# OpenCV xcframework for video/image processing on iOS.

load(
"@mediapipe//third_party:opencv_ios_source.bzl",
"select_headers",
"unzip_opencv_xcframework",
)
load(
"@build_bazel_rules_apple//apple:apple.bzl",
"apple_static_xcframework_import",
)

licenses(["notice"]) # BSD license

exports_files(["LICENSE"])

# Build opencv2.xcframework from source using a convenience script provided in
# OPENCV sources and zip the xcframework. We only build the modules required by MediaPipe by specifying
# the modules to be ignored as command line arguments.
# We also specify the simulator and device architectures we are building for.
# Currently we only support iOS arm64 (M1 Macs) and x86_64(Intel Macs) simulators
# and arm64 iOS devices.
# Bitcode and Swift support. Swift support will be added in when the final binary
# for MediaPipe iOS Task libraries are built. Shipping with OPENCV built with
# Swift support throws linker errors when the MediaPipe framework is used from
# an iOS project.
# When building on M1 Macs, cmake version cannot be higher than 3.24.0. This is
# is mentioned in an open issue in the opencv github repo.
genrule(
name = "build_opencv_xcframework",
srcs = glob(["opencv-4.5.3/**"]),
outs = ["opencv2.xcframework.zip"],
cmd = "&&".join([
"OPENCV_SKIP_XCODEBUILD_FORCE_TRYCOMPILE_DEBUG=1 $(location opencv-4.5.3/platforms/apple/build_xcframework.py) \
--iphonesimulator_archs arm64,x86_64 \
--iphoneos_archs arm64 \
--without dnn \
--without ml \
--without stitching \
--without photo \
--without objdetect \
--without gapi \
--without flann \
--without highgui \
--without videoio \
--disable PROTOBUF \
--disable-swift \
--build_only_specified_archs \
--out $(@D)",
"cd $(@D)",
"zip --symlinks -r opencv2.xcframework.zip opencv2.xcframework",
]),
)

# Unzips `opencv2.xcframework.zip` built from source by `build_opencv_xcframework`
# genrule and returns an exhaustive list of all its files including symlinks.
unzip_opencv_xcframework(
name = "opencv2_unzipped_xcframework_files",
zip_file = "opencv2.xcframework.zip",
)

# Imports the files of the unzipped `opencv2.xcframework` as an apple static
# framework which can be linked to iOS targets.
apple_static_xcframework_import(
name = "opencv_xcframework",
visibility = ["//visibility:public"],
xcframework_imports = [":opencv2_unzipped_xcframework_files"],
)

# Filters the headers for each platform in `opencv2.xcframework` which will be
# used as headers in a `cc_library` that can be linked to C++ targets.
select_headers(
name = "opencv_xcframework_device_headers",
srcs = [":opencv_xcframework"],
platform = "ios-arm64",
)

select_headers(
name = "opencv_xcframework_simulator_headers",
srcs = [":opencv_xcframework"],
platform = "ios-arm64_x86_64-simulator",
)

# `cc_library` that can be linked to C++ targets to import opencv headers.
cc_library(
name = "opencv",
hdrs = select({
"@mediapipe//mediapipe:ios_x86_64": [
":opencv_xcframework_simulator_headers",
],
"@mediapipe//mediapipe:ios_sim_arm64": [
":opencv_xcframework_simulator_headers",
],
"@mediapipe//mediapipe:ios_arm64": [
":opencv_xcframework_device_headers",
],
# A value from above is chosen arbitarily.
"//conditions:default": [
":opencv_xcframework_simulator_headers",
],
}),
copts = [
"-std=c++11",
"-x objective-c++",
],
include_prefix = "opencv2",
linkopts = [
"-framework AssetsLibrary",
"-framework CoreFoundation",
"-framework CoreGraphics",
"-framework CoreMedia",
"-framework Accelerate",
"-framework CoreImage",
"-framework AVFoundation",
"-framework CoreVideo",
"-framework QuartzCore",
],
strip_include_prefix = select({
"@mediapipe//mediapipe:ios_x86_64": "opencv2.xcframework/ios-arm64_x86_64-simulator/opencv2.framework/Versions/A/Headers",
"@mediapipe//mediapipe:ios_sim_arm64": "opencv2.xcframework/ios-arm64_x86_64-simulator/opencv2.framework/Versions/A/Headers",
"@mediapipe//mediapipe:ios_arm64": "opencv2.xcframework/ios-arm64/opencv2.framework/Versions/A/Headers",
# Random value is selected for default cases.
"//conditions:default": "opencv2.xcframework/ios-arm64_x86_64-simulator/opencv2.framework/Versions/A/Headers",
}),
visibility = ["//visibility:public"],
deps = [":opencv_xcframework"],
)
159 changes: 159 additions & 0 deletions third_party/opencv_ios_source.bzl
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
# Copyright 2023 The MediaPipe Authors. All rights reserved.
#
# Licensed 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.

"""Custom rules for building iOS OpenCV xcframework from sources."""

load(
"@mediapipe//third_party:opencv_ios_xcframework_files.bzl",
"OPENCV_XCFRAMEWORK_INFO_PLIST_PATH",
"OPENCV_XCFRAMEWORK_IOS_DEVICE_FILE_PATHS",
"OPENCV_XCFRAMEWORK_IOS_SIMULATOR_FILE_PATHS",
)

_OPENCV_XCFRAMEWORK_DIR_NAME = "opencv2.xcframework"
_OPENCV_FRAMEWORK_DIR_NAME = "opencv2.framework"
_OPENCV_SIMULATOR_PLATFORM_DIR_NAME = "ios-arm64_x86_64-simulator"
_OPENCV_DEVICE_PLATFORM_DIR_NAME = "ios-arm64"

def _select_headers_impl(ctx):
# Should match with `/`. Othewise `ios-arm64` matches with `ios-arm64_x86-64`
_files = [
f
for f in ctx.files.srcs
if (f.basename.endswith(".h") or f.basename.endswith(".hpp")) and
f.dirname.find(ctx.attr.platform + "/") != -1
]
return [DefaultInfo(files = depset(_files))]

# This rule selects only the headers from an apple static xcframework filtered by
# an input platform string.
select_headers = rule(
implementation = _select_headers_impl,
attrs = {
"srcs": attr.label_list(mandatory = True, allow_files = True),
"platform": attr.string(mandatory = True),
},
)

# This function declares and returns symlinks to the directories within each platform
# in `opencv2.xcframework` expected to be present.
# The symlinks are created according to the structure stipulated by apple xcframeworks
# do that they can be correctly consumed by `apple_static_xcframework_import` rule.
def _opencv2_directory_symlinks(ctx, platforms):
basenames = ["Resources", "Headers", "Modules", "Versions/Current"]
symlinks = []

for platform in platforms:
symlinks = symlinks + [
ctx.actions.declare_symlink(
_OPENCV_XCFRAMEWORK_DIR_NAME + "/{}/{}/{}".format(platform, _OPENCV_FRAMEWORK_DIR_NAME, name),
)
for name in basenames
]

return symlinks

# This function declares and returns all the files for each platform expected
# to be present in `opencv2.xcframework` after the unzipping action is run.
def _opencv2_file_list(ctx, platform_filepath_lists):
binary_name = "opencv2"
output_files = []
binaries_to_symlink = []

for (platform, filepaths) in platform_filepath_lists:
for path in filepaths:
file = ctx.actions.declare_file(path)
output_files.append(file)
if path.endswith(binary_name):
symlink_output = ctx.actions.declare_file(
_OPENCV_XCFRAMEWORK_DIR_NAME + "/{}/{}/{}".format(
platform,
_OPENCV_FRAMEWORK_DIR_NAME,
binary_name,
),
)
binaries_to_symlink.append((symlink_output, file))

return output_files, binaries_to_symlink

def _unzip_opencv_xcframework_impl(ctx):
# Array to iterate over the various platforms to declare output files and
# symlinks.
platform_filepath_lists = [
(_OPENCV_SIMULATOR_PLATFORM_DIR_NAME, OPENCV_XCFRAMEWORK_IOS_SIMULATOR_FILE_PATHS),
(_OPENCV_DEVICE_PLATFORM_DIR_NAME, OPENCV_XCFRAMEWORK_IOS_DEVICE_FILE_PATHS),
]

# Gets an exhaustive list of output files which are present in the xcframework.
# Also gets array of `(binary simlink, binary)` pairs which are to be symlinked
# using `ctx.actions.symlink()`.
output_files, binaries_to_symlink = _opencv2_file_list(ctx, platform_filepath_lists)
output_files.append(ctx.actions.declare_file(OPENCV_XCFRAMEWORK_INFO_PLIST_PATH))

# xcframeworks have a directory structure in which the `opencv2.framework` folders for each
# platform contain directories which are symlinked to the respective folders of the version
# in use. Simply unzipping the zip of the framework will not make Bazel treat these
# as symlinks. They have to be explicity declared as symlinks using `ctx.actions.declare_symlink()`.
directory_symlinks = _opencv2_directory_symlinks(
ctx,
[_OPENCV_SIMULATOR_PLATFORM_DIR_NAME, _OPENCV_DEVICE_PLATFORM_DIR_NAME],
)

output_files = output_files + directory_symlinks

args = ctx.actions.args()

# Add the path of the zip file to be unzipped as an argument to be passed to
# `run_shell` action.
args.add(ctx.file.zip_file.path)

# Add the path to the directory in which the framework is to be unzipped to.
args.add(ctx.file.zip_file.dirname)

ctx.actions.run_shell(
inputs = [ctx.file.zip_file],
outputs = output_files,
arguments = [args],
progress_message = "Unzipping %s" % ctx.file.zip_file.short_path,
command = "unzip -qq $1 -d $2",
)

# The symlinks of the opencv2 binaries for each platform in the xcframework
# have to be symlinked using the `ctx.actions.symlink` unlike the directory
# symlinks which can be expected to be valid when unzipping is completed.
# Otherwise, when tests are run, the linker complaints that the binary is
# not found.
binary_symlink_files = []
for (symlink_output, binary_file) in binaries_to_symlink:
ctx.actions.symlink(output = symlink_output, target_file = binary_file)
binary_symlink_files.append(symlink_output)

# Return all the declared output files and symlinks as the output of this
# rule.
return [DefaultInfo(files = depset(output_files + binary_symlink_files))]

# This rule unzips an `opencv2.xcframework.zip` created by a genrule that
# invokes a python script in the opencv 4.5.1 github archive.
# It returns all the contents of opencv2.xcframework as a list of files in the
# output. This rule works by explicitly declaring files at hardcoded
# paths in the opencv2 xcframework bundle which are expected to be present when
# the zip file is unzipped. This is a prerequisite since the outputs of this rule
# will be consumed by apple_static_xcframework_import which can only take a list
# of files as inputs.
unzip_opencv_xcframework = rule(
implementation = _unzip_opencv_xcframework_impl,
attrs = {
"zip_file": attr.label(mandatory = True, allow_single_file = True),
},
)

0 comments on commit e5b215c

Please sign in to comment.