Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix static linking on iOS #6614

Merged
merged 1 commit into from
Oct 18, 2024
Merged

Fix static linking on iOS #6614

merged 1 commit into from
Oct 18, 2024

Conversation

tomekzaw
Copy link
Member

@tomekzaw tomekzaw commented Oct 18, 2024

Summary

Fixes #6607.

This is more of a workaround to fix issue with missing headers when building Reanimated with static linkage for iOS. I spent several hours on trying to make it right but it would work correctly only partially. I will post my findings here or internally later on and I hope to find a better solution sometime in the future.

This workaround adds paths to Common/cpp and apple/ directories to HEADER_SEARCH_PATHS – the preprocessor looks for headers in these locations. I can't use an absolute path (calculated in Ruby) because it would make the output setup-dependent and thus affect checksums in Podfile.lock which is unwanted as some apps check against checksum changes on CI.

Test plan

Tested on fabric-example app with USE_FRAMEWORKS=static and without.


My findings

When installing pods in fabric-example with cd ios && bundle install && bundle exec pod install, the headers are visible in ios/Pods directory:

$ find . -name "REAUIKit.h"
./Pods/Headers/Public/RNReanimated/reanimated/apple/REAUIKit.h
./Pods/Headers/Private/RNReanimated/reanimated/apple/REAUIKit.h

However, after running USE_FRAMEWORKS=static bundle exec pod install, the headers are no longer present:

$ find . -name "REAUIKit.h"
<no output>

Because of this, #import <reanimated/apple/REAUIKit.h> doesn't work, but when changed back to #import <RNReanimated/REAUIKit.h> it works fine (jump to file also works in Xcode).

This made me wonder what's the location of REAUIKit.h. Obviously, the file is located in react-native-reanimated/packages/react-native-reanimated/apple/reanimated/apple/REAUIKit.h (using symlinks as a Development Pod), but the path is not included in header search paths. Hence, I decided to add react-native-reanimated/packages/react-native-reanimated/apple/ to header search paths. However, this can't be done using absolute paths because they are likely to be machine-specific (e.g. containing the home directory name) which also affects checksum in Podfile.lock which is inconvenient since some setups assume the checksum to be stable (unless the version of the library is changed) due to security concerns.

A better idea would be to take inspiration from react-native itself. I've noticed that ReactCommon has a similar feature – the headers are in nested subdirectories (e.g. react/renderer/core/ShadowNode.h) and the imports don't assume flat structure (e.g. it's #import <react/renderer/core/ShadowNode.h> instead of #import <ReactCommon/ShadowNode.h>).

I remembered that frameworks create FrameworkName.framework directories. I finally found RNReanimated.framework in Xcode build folder that you can open from menu bar:

Screenshot 2024-10-18 at 18 04 45

Taking a quick look inside and comparing ReactCommon and RNReanimated, I've noticed that RNReanimated.framework doesn't contain any header files (except for umbrella header) while ReactCommon does:

Screenshot 2024-10-18 at 18 05 33

When I commented out all 3 occurrences of header_mappings_dir in RNReanimated.podspec and run the Xcode build (first build lasts until the first error, if you hit the play button once again then, I assume it runs the remaining tasks), the headers were finally there but the directory structure was flattened:

-ss.header_mappings_dir = "Common/cpp/reanimated"
+#ss.header_mappings_dir = "Common/cpp/reanimated"
Screenshot 2024-10-18 at 18 14 52

Then I tried passing an absolute path to appropriate directories to see if this would fix the structure:

-ss.header_mappings_dir = "Common/cpp/reanimated"
+ss.header_mappings_dir = "/Users/tomekzaw/RNOS/react-native-reanimated/packages/react-native-reanimated/Common/cpp"

-sss.header_mappings_dir = "apple/reanimated"
+sss.header_mappings_dir = "/Users/tomekzaw/RNOS/react-native-reanimated/packages/react-native-reanimated/

-ss.header_mappings_dir = "Common/cpp/worklets"
+ss.header_mappings_dir = "/Users/tomekzaw/RNOS/react-native-reanimated/packages/react-native-reanimated/Common/cpp"

After this change, the headers were present and in correct tree structure:

Screenshot 2024-10-18 at 20 08 16

However, the headers were still not found by Xcode when building the project.

While reviewing the changes, I found the following diff in project.pbxproj (file is generated by CocoaPods):

HEADER_SEARCH_PATHS = (
	"$(inherited)",
	"${PODS_CONFIGURATION_BUILD_DIR}/ReactCommon/ReactCommon.framework/Headers",
	"${PODS_CONFIGURATION_BUILD_DIR}/ReactCommon/ReactCommon.framework/Headers/react/nativemodule/core",
	"${PODS_CONFIGURATION_BUILD_DIR}/ReactCommon-Samples/ReactCommon_Samples.framework/Headers",
	"${PODS_CONFIGURATION_BUILD_DIR}/ReactCommon-Samples/ReactCommon_Samples.framework/Headers/platform/ios",
	"${PODS_CONFIGURATION_BUILD_DIR}/React-Fabric/React_Fabric.framework/Headers/react/renderer/components/view/platform/cxx",
	"${PODS_CONFIGURATION_BUILD_DIR}/React-NativeModulesApple/React_NativeModulesApple.framework/Headers",
	"${PODS_CONFIGURATION_BUILD_DIR}/React-graphics/React_graphics.framework/Headers",
	"${PODS_CONFIGURATION_BUILD_DIR}/React-graphics/React_graphics.framework/Headers/react/renderer/graphics/platform/ios",
);

It looks like react-native overrides HEADER_SEARCH_PATHS when use_frameworks! :static is used. So I tried doing the same in RNReanimated.podspec:

s.pod_target_xcconfig = {
    "HEADER_SEARCH_PATHS" => '"${PODS_CONFIGURATION_BUILD_DIR}/RNReanimated/RNReanimated.framework/Headers" (...)",
}

Now the headers were correctly found but I got an error about redefinitions:

Screenshot 2024-10-18 at 20 17 25

Then I investigated ReactCommon.podspec inside react-native repository and found several interesting lines of code:

s.header_dir = "ReactCommon" # Use global header_dir for all subspecs for use_frameworks! compatibility

It looks like header_dir must contain header_mappings_dir for the latter to work properly when use_frameworks! is enabled.

if ENV['USE_FRAMEWORKS']
  s.header_mappings_dir     = './'
end

For some reason, s.header_mappings_dir is set to the current directory only if USE_FRAMEWORKS is set.

@tomekzaw tomekzaw added this pull request to the merge queue Oct 18, 2024
Merged via the queue into main with commit 16297eb Oct 18, 2024
17 checks passed
@tomekzaw tomekzaw deleted the @tomekzaw/fix-static-linking branch October 18, 2024 17:49
tomekzaw added a commit that referenced this pull request Oct 21, 2024
Fixes
#6607.

This is more of a workaround to fix issue with missing headers when
building Reanimated with static linkage for iOS. I spent several hours
on trying to make it right but it would work correctly only partially. I
will post my findings here or internally later on and I hope to find a
better solution sometime in the future.

This workaround adds paths to `Common/cpp` and `apple/` directories to
`HEADER_SEARCH_PATHS` – the preprocessor looks for headers in these
locations. I can't use an absolute path (calculated in Ruby) because it
would make the output setup-dependent and thus affect checksums in
`Podfile.lock` which is unwanted as some apps check against checksum
changes on CI.

Tested on fabric-example app with `USE_FRAMEWORKS=static` and without.

---

When installing pods in `fabric-example` with `cd ios && bundle install
&& bundle exec pod install`, the headers are visible in `ios/Pods`
directory:

```
$ find . -name "REAUIKit.h"
./Pods/Headers/Public/RNReanimated/reanimated/apple/REAUIKit.h
./Pods/Headers/Private/RNReanimated/reanimated/apple/REAUIKit.h
```

However, after running `USE_FRAMEWORKS=static bundle exec pod install`,
the headers are no longer present:

```
$ find . -name "REAUIKit.h"
<no output>
```

Because of this, `#import <reanimated/apple/REAUIKit.h>` doesn't work,
but when changed back to `#import <RNReanimated/REAUIKit.h>` it works
fine (jump to file also works in Xcode).

This made me wonder what's the location of `REAUIKit.h`. Obviously, the
file is located in
`react-native-reanimated/packages/react-native-reanimated/apple/reanimated/apple/REAUIKit.h`
(using symlinks as a Development Pod), but the path is not included in
header search paths. Hence, I decided to add
`react-native-reanimated/packages/react-native-reanimated/apple/` to
header search paths. However, this can't be done using absolute paths
because they are likely to be machine-specific (e.g. containing the home
directory name) which also affects checksum in Podfile.lock which is
inconvenient since some setups assume the checksum to be stable (unless
the version of the library is changed) due to security concerns.

A better idea would be to take inspiration from react-native itself.
I've noticed that `ReactCommon` has a similar feature – the headers are
in nested subdirectories (e.g. `react/renderer/core/ShadowNode.h`) and
the imports don't assume flat structure (e.g. it's `#import
<react/renderer/core/ShadowNode.h>` instead of `#import
<ReactCommon/ShadowNode.h>`).

I remembered that frameworks create `FrameworkName.framework`
directories. I finally found `RNReanimated.framework` in Xcode build
folder that you can open from menu bar:

<img width="312" alt="Screenshot 2024-10-18 at 18 04 45"
src="https://github.com/user-attachments/assets/1db95b44-d21c-4e9a-a1d5-19674093094b">

Taking a quick look inside and comparing `ReactCommon` and
`RNReanimated`, I've noticed that `RNReanimated.framework` doesn't
contain any header files (except for umbrella header) while
`ReactCommon` does:

<img width="1178" alt="Screenshot 2024-10-18 at 18 05 33"
src="https://github.com/user-attachments/assets/2633ab64-a85e-4a04-b2b3-97a11e576d6c">

When I commented out all 3 occurrences of `header_mappings_dir` in
RNReanimated.podspec and run the Xcode build (first build lasts until
the first error, if you hit the play button once again then, I assume it
runs the remaining tasks), the headers were finally there but the
directory structure was flattened:

```diff
-ss.header_mappings_dir = "Common/cpp/reanimated"
+#ss.header_mappings_dir = "Common/cpp/reanimated"
```

<img width="1178" alt="Screenshot 2024-10-18 at 18 14 52"
src="https://github.com/user-attachments/assets/c25c1dfe-ce45-45ff-856f-be005907ffe2">

Then I tried passing an absolute path to appropriate directories to see
if this would fix the structure:

```diff
-ss.header_mappings_dir = "Common/cpp/reanimated"
+ss.header_mappings_dir = "/Users/tomekzaw/RNOS/react-native-reanimated/packages/react-native-reanimated/Common/cpp"

-sss.header_mappings_dir = "apple/reanimated"
+sss.header_mappings_dir = "/Users/tomekzaw/RNOS/react-native-reanimated/packages/react-native-reanimated/

-ss.header_mappings_dir = "Common/cpp/worklets"
+ss.header_mappings_dir = "/Users/tomekzaw/RNOS/react-native-reanimated/packages/react-native-reanimated/Common/cpp"
```

Then I investigated `ReactCommon.podspec` inside react-native repository
and found several interesting lines of code:

```rb
s.header_dir = "ReactCommon" # Use global header_dir for all subspecs for use_frameworks! compatibility
```

It looks like `header_dir` must contain `header_mappings_dir` for the
latter to work properly when `use_frameworks!` is enabled.

```rb
if ENV['USE_FRAMEWORKS']
  s.header_mappings_dir     = './'
end
```
For some reason, `s.header_mappings_dir` is set to the current directory
only if `USE_FRAMEWORKS` is set.

...

```
HEADER_SEARCH_PATHS = (
	"$(inherited)",
	"${PODS_CONFIGURATION_BUILD_DIR}/ReactCommon/ReactCommon.framework/Headers",
	"${PODS_CONFIGURATION_BUILD_DIR}/ReactCommon/ReactCommon.framework/Headers/react/nativemodule/core",
	"${PODS_CONFIGURATION_BUILD_DIR}/ReactCommon-Samples/ReactCommon_Samples.framework/Headers",
	"${PODS_CONFIGURATION_BUILD_DIR}/ReactCommon-Samples/ReactCommon_Samples.framework/Headers/platform/ios",
	"${PODS_CONFIGURATION_BUILD_DIR}/React-Fabric/React_Fabric.framework/Headers/react/renderer/components/view/platform/cxx",
	"${PODS_CONFIGURATION_BUILD_DIR}/React-NativeModulesApple/React_NativeModulesApple.framework/Headers",
	"${PODS_CONFIGURATION_BUILD_DIR}/React-graphics/React_graphics.framework/Headers",
	"${PODS_CONFIGURATION_BUILD_DIR}/React-graphics/React_graphics.framework/Headers/react/renderer/graphics/platform/ios",
);
```
@efstathiosntonas
Copy link
Contributor

@tomekzaw hi, quick question:

when pod install after the changes in the podspec, is it necessary to clean derived data/build folder/Pods folder before rebuilding?

@tomekzaw
Copy link
Member Author

@efstathiosntonas It's definitely recommended but not sure if required, I usually run rm -rf Pods Podfile.lock build prior to bundle exec pod install but I hardly ever clean DerivedData folder.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[3.16.0] 'worklets/WorkletRuntime/ReanimatedHermesRuntime.h' file not found
3 participants