[PR #44] [CLOSED] Add Framework subspec for building the pod as a framework. #406

Closed
opened 2026-05-05 12:15:05 -06:00 by gitea-mirror · 0 comments
Owner

📋 Pull Request Information

Original PR: https://github.com/jmcnamara/libxlsxwriter/pull/44
Author: @lrossi
Created: 2/3/2016
Status: Closed

Base: masterHead: cocoapods-1.0.0


📝 Commits (1)

  • 6631db0 Add Framework subspec for building the pod as a framework.

📊 Changes

1 file changed (+31 additions, -5 deletions)

View changed files

📝 libxlsxwriter.podspec (+31 -5)

📄 Description

Adding support for CocoaPods' use_frameworks!

Ok, here we are: sorry this took longer than I expected and almost drove me crazy 😜

The quest for building as a framework

So, CocoaPods 1.0.0 is almost there (currently in its second beta—actually it seems while I was writing the third beta came out, see The bottom line), and it fixes the main problem (#13) that was preventing this pod from being built as a framework.

Full of hope I installed the CocoaPods beta and tried to include the pod with the use_frameworks! directive. Build fails with error:

01

Hmm, so it seems even though the include directory structure is now preserved in the framework (I was able to verify that), the compiler is still puzzled about where to find the headers. Since the correct structure exists on disk, I solved this error by adding a line in the Podspec:

s.pod_target_xcconfig   = { 'USER_HEADER_SEARCH_PATHS' => '${PODS_ROOT}/libxlsxwriter/include' }

Now a new problem arose in the umbrella header generated by CocoaPods:

02

The error is a bit misleading since it basically means that with the current Podspec the xlsxwriter.h header doesn't get copied in the framework: that file is inside the include directory, but with these lines:

s.header_dir            = "xlsxwriter"
s.header_mappings_dir   = "include/xlsxwriter"

we are just preserving the xlsxwriter directory that is a subdirectory of include, effectively leaving out the xlsxwriter.h header.

NOTE: The non-modular header error most likely refers to the fact that the compiler is able to access the header since it is in its header paths, but the header lies outside of the module (= framework) and this is something it simply can't approve.

My first naive attempt was to preserve the whole include directory, like this:

s.header_mappings_dir   = "include"

This removes the non-modular error, however it leads to another error in libxlsxwriter's app.h when building the iOS target that includes the pod:

03

I think this is because at this point (i.e. when building the iOS app and not the pod), the compiler relies on the headers that are bundled within the framework, that now have this structure:

04

while when going back to:

s.header_mappings_dir   = "include/xlsxwriter"

the structure is like this:

05

Even though this latter structure leaves out the xlsxwriter.h header, for some reason it allows all the other includes such as #include "xlsxwriter/workbook.h" to succeed (possibly because xlsxwriter gets recognized as the name of the framework).

So now it comes my, ahem, “brillant” idea: moving the xlsxwriter.h header inside of the xlsxwriter directory. This is accomplished with these lines in the Podspec:

s.prepare_command       = <<-CMD
                            mv include/xlsxwriter.h include/xlsxwriter/xlsxwriter_module.h
                          CMD

I renamed the file to xlsxwriter_module.h to further reduce the probability of clashes with existing headers inside the xlsxwriter, since for the purpose of building the framework the actual file name doesn't matter (it automatically gets included in the generated umbrella header). This is kind of an hack that I don't like very much, but you know what... It works:

06

All that glitters is not gold

But wait a minute... Didn't we just break the old-fashioned way of building the pod as a static library? Of course we did, since now when building the pod the good ol' way, instead of importing it with the comforting (and standard):

#import <libxlsxwriter/xlsxwriter.h>

we must use the crazy:

#import <libxlsxwriter/xlsxwriter/xlsxwriter_module.h>

Even relaxing my name clashing obsession, it's still:

#import <libxlsxwriter/xlsxwriter/xlsxwriter.h>

This means: to the best of my knowledge, there is no easy way of writing a plain Podspec that works both with and without use_frameworks! without touching the main code.

So what?

A new beginning

In these cases, it seems to be an accepted practice to define multiple subspecs addressing specific configuration needs. For example, the Chameleon pod has a ChameleonFramework/Swift subspec so that to add the pod to an Objective-C project you do:

pod 'ChameleonFramework'

while with a Swift project you do:

pod 'ChameleonFramework/Swift'

NOTE: If you are not familiar with subspecs, the good thing is they can be easily defined in the same Podspec file as the main spec.

Subspecs seem the way to go, however there is one last caveat: they can't contain prepare_command statements. Thus this is what I ended up with:

# On the one hand, all the headers need to be inside the xlsxwriter directory for the pod to be built as a
# module (i.e. framework). On the other hand, the xlsxwriter.h header needs to be at the top level for it
# to be easily imported when building the pod as a static library. That being said, since we can't use
# prepare_command in a subspec, we do the copy here and then exclude the unwanted file in each subspec.

s.prepare_command       = <<-CMD
                          cp include/xlsxwriter.h include/xlsxwriter/xlsxwriter_module.h
                        CMD


s.default_subspecs = 'Default'

s.subspec 'Default' do |ss|
  ss.source_files           = "src/**/*.c", "third_party/**/{zip.c,ioapi.c}", "include/**/*.h"

  # Building as a static library: we don't need the "module" header inside the xlsxwriter directory
  # (even though leaving it there wouldn't harm). 
  ss.exclude_files          = "include/xlsxwriter/xlsxwriter_module.h"
end

s.subspec 'Framework' do |ss|
  ss.source_files           = "src/**/*.c", "third_party/**/{zip.c,ioapi.c}", "include/**/*.h"
  ss.pod_target_xcconfig    = { 'USER_HEADER_SEARCH_PATHS' => '${PODS_ROOT}/libxlsxwriter/include' }

  # Building as a framework: we don't want to have an header outside of the xlsxwriter directory, since
  # it would end outside of the framework.
  ss.exclude_files          = "include/xlsxwriter.h"
end

NOTE: If you're wondering why source_files is repeated in both the subspecs: a parent common subspec defining the source_files could theoretically be created and then included as a dependency in both the Default and Framework subspecs, however it seems that subspec can't exclude files added by a parent spec. Sigh.

This excerpt from the Podspec means there are now two subspecs:

  1. The Default subspec is marked as default by setting default_subspecs, thus it can be imported in a project with the usual pod 'libxlsxwriter' in the Podfile. It is used to build the pod as a static library (exactly as it was before).
  2. The Framework subspec can be imported in a project with pod 'libxlsxwriter/Framework' in the Podfile. It is used to build the pod as a framework (i.e. when the Podfile contains the use_frameworks! directive).

NOTE: Why choosing libxlsxwriter/Framework instead of libxlsxwriter/Swift? Because the use_frameworks! directive can also be used with an Objective-C project, and the proposed solution also works in that case (since there is nothing Swift-specific at the moment). If one day the library will include Swift source files (for instace, to expose C macros that are not automatically translated in Swift and/or to offer a more Swifty syntax), a new Swift sub-subspec could be created with the Framework subspec as a dependency.

In a libxlsxwriter user's shoes

Good news is a libxlsxwriter user doesn't need to be aware of all this machinery. In order to import the pod as a static library, one can simply add this to the Podfile (as usual):

pod 'libxlsxwriter'

and then import the pod in project's source files like this (as usual):

#import <libxlsxwriter/xlsxwriter.h>

On the other hand, in order to import the pod as a framework, one can add this to the Podfile:

use_frameworks!

pod 'libxlsxwriter/Framework'

and then import the pod in project's source files in Swift:

import xlsxwriter

or in Objective-C:

@import xlsxwriter;

The bottom line

First of all, thanks for reading this far. I tried to explain the whole process so that someone can possibly suggests any way to improve it. This is the best solution I came up with so far, but it would be great to get rid of some of the machinery involved, if possible. Otherwise, the current solution is working and I'm not seeing major side effects (with my own eyes, at least). I have tested the current solution with my example projects in a dedicated branch.

In any case, I would recommend waiting for the final CocoaPods 1.0.0 release before merging this PR.

NOTE: A new (third) beta of CocoaPods 1.0.0 came out while I was writing this. I have tested it and this solution still works. However, since the change log mentions an issue with header files and frameworks has been fixed, I'll repeat the previous attempts to see if the whole process can be simplified.

Credit where credit is due

Many thanks to @atomiix for spotting the original issue and for providing a patch in the meantime.


🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.

## 📋 Pull Request Information **Original PR:** https://github.com/jmcnamara/libxlsxwriter/pull/44 **Author:** [@lrossi](https://github.com/lrossi) **Created:** 2/3/2016 **Status:** ❌ Closed **Base:** `master` ← **Head:** `cocoapods-1.0.0` --- ### 📝 Commits (1) - [`6631db0`](https://github.com/jmcnamara/libxlsxwriter/commit/6631db08ea86df01e93d44e9393b85b8689feabb) Add Framework subspec for building the pod as a framework. ### 📊 Changes **1 file changed** (+31 additions, -5 deletions) <details> <summary>View changed files</summary> 📝 `libxlsxwriter.podspec` (+31 -5) </details> ### 📄 Description # Adding support for CocoaPods' `use_frameworks!` Ok, here we are: sorry this took longer than I expected and almost drove me crazy 😜 ## The quest for building as a framework So, CocoaPods 1.0.0 is almost there (currently in its <s>second beta</s>—actually it seems while I was writing the third beta came out, see [The bottom line](#bottom-line)), and it fixes the main problem (#13) that was preventing this pod from being built as a framework. Full of hope I installed the CocoaPods beta and tried to include the pod with the `use_frameworks!` directive. Build fails with error: ![01](https://cloud.githubusercontent.com/assets/2843411/12783471/1871c48c-ca82-11e5-8375-4dce1530a970.png) Hmm, so it seems even though the include directory structure is now preserved in the framework (I was able to verify that), the compiler is still puzzled about where to find the headers. Since the correct structure exists on disk, I solved this error by adding a line in the Podspec: ``` ruby s.pod_target_xcconfig = { 'USER_HEADER_SEARCH_PATHS' => '${PODS_ROOT}/libxlsxwriter/include' } ``` Now a new problem arose in the umbrella header generated by CocoaPods: ![02](https://cloud.githubusercontent.com/assets/2843411/12783477/1eab03f4-ca82-11e5-8016-e454c057cc43.png) The error is a bit misleading since it basically means that with the current Podspec the `xlsxwriter.h` header doesn't get copied in the framework: that file is inside the `include` directory, but with these lines: ``` ruby s.header_dir = "xlsxwriter" s.header_mappings_dir = "include/xlsxwriter" ``` we are just preserving the `xlsxwriter` directory that is a subdirectory of `include`, effectively leaving out the `xlsxwriter.h` header. > **NOTE:** The `non-modular header` error most likely refers to the fact that the compiler is able to access the header since it is in its header paths, but the header lies outside of the module (= framework) and this is something it simply [can't approve](https://forums.developer.apple.com/thread/23568). My first naive attempt was to preserve the whole `include` directory, like this: ``` ruby s.header_mappings_dir = "include" ``` This removes the `non-modular` error, however it leads to another error in libxlsxwriter's `app.h` when building the iOS target that includes the pod: ![03](https://cloud.githubusercontent.com/assets/2843411/12783483/24b8b160-ca82-11e5-9658-209a241110c4.png) I think this is because at this point (i.e. when building the iOS app and not the pod), the compiler relies on the headers that are bundled within the framework, that now have this structure: <img width="250" alt="04" src="https://cloud.githubusercontent.com/assets/2843411/12783489/314694ec-ca82-11e5-9545-b63302885af9.png"> while when going back to: ``` ruby s.header_mappings_dir = "include/xlsxwriter" ``` the structure is like this: <img width="237" alt="05" src="https://cloud.githubusercontent.com/assets/2843411/12783491/3786091e-ca82-11e5-8b9c-c3700d9f33ee.png"> Even though this latter structure leaves out the `xlsxwriter.h` header, for some reason it allows all the other includes such as `#include "xlsxwriter/workbook.h"` to succeed (possibly because `xlsxwriter` gets recognized as the name of the framework). So now it comes my, ahem, “brillant” idea: moving the `xlsxwriter.h` header _inside_ of the `xlsxwriter` directory. This is accomplished with these lines in the Podspec: ``` ruby s.prepare_command = <<-CMD mv include/xlsxwriter.h include/xlsxwriter/xlsxwriter_module.h CMD ``` I renamed the file to `xlsxwriter_module.h` to further reduce the probability of clashes with existing headers inside the `xlsxwriter`, since for the purpose of building the framework the actual file name doesn't matter (it automatically gets included in the generated umbrella header). This is kind of an hack that I don't like very much, but you know what... It works: ![06](https://cloud.githubusercontent.com/assets/2843411/12783495/3d90c6be-ca82-11e5-9008-fee7e491abd3.png) ## All that glitters is not gold But wait a minute... Didn't we just break the old-fashioned way of building the pod as a static library? _Of course we did_, since now when building the pod the good ol' way, instead of importing it with the comforting (and standard): ``` objc #import <libxlsxwriter/xlsxwriter.h> ``` we must use the crazy: ``` objc #import <libxlsxwriter/xlsxwriter/xlsxwriter_module.h> ``` Even relaxing my name clashing obsession, it's still: ``` objc #import <libxlsxwriter/xlsxwriter/xlsxwriter.h> ``` This means: to the best of my knowledge, there is no easy way of writing a plain Podspec that works both with and without `use_frameworks!` without touching the main code. So what? ## A new beginning In these cases, it seems to be an accepted practice to define multiple subspecs addressing specific configuration needs. For example, the [Chameleon](https://github.com/ViccAlexander/Chameleon/) pod has a `ChameleonFramework/Swift` subspec so that to add the pod to an Objective-C project you do: ``` ruby pod 'ChameleonFramework' ``` while with a Swift project you do: ``` pod 'ChameleonFramework/Swift' ``` > **NOTE:** If you are not familiar with [subspecs](https://guides.cocoapods.org/syntax/podspec.html#subspec), the good thing is they can be easily defined in the same Podspec file as the main spec. Subspecs seem the way to go, however there is one last caveat: they can't contain `prepare_command` statements. Thus this is what I ended up with: ``` ruby # On the one hand, all the headers need to be inside the xlsxwriter directory for the pod to be built as a # module (i.e. framework). On the other hand, the xlsxwriter.h header needs to be at the top level for it # to be easily imported when building the pod as a static library. That being said, since we can't use # prepare_command in a subspec, we do the copy here and then exclude the unwanted file in each subspec. s.prepare_command = <<-CMD cp include/xlsxwriter.h include/xlsxwriter/xlsxwriter_module.h CMD s.default_subspecs = 'Default' s.subspec 'Default' do |ss| ss.source_files = "src/**/*.c", "third_party/**/{zip.c,ioapi.c}", "include/**/*.h" # Building as a static library: we don't need the "module" header inside the xlsxwriter directory # (even though leaving it there wouldn't harm). ss.exclude_files = "include/xlsxwriter/xlsxwriter_module.h" end s.subspec 'Framework' do |ss| ss.source_files = "src/**/*.c", "third_party/**/{zip.c,ioapi.c}", "include/**/*.h" ss.pod_target_xcconfig = { 'USER_HEADER_SEARCH_PATHS' => '${PODS_ROOT}/libxlsxwriter/include' } # Building as a framework: we don't want to have an header outside of the xlsxwriter directory, since # it would end outside of the framework. ss.exclude_files = "include/xlsxwriter.h" end ``` > **NOTE:** If you're wondering why `source_files` is repeated in both the subspecs: a parent common subspec defining the `source_files` could theoretically be created and then included as a dependency in both the `Default` and `Framework` subspecs, however it seems that subspec can't exclude files added by a parent spec. Sigh. This excerpt from the Podspec means there are now two subspecs: 1. The `Default` subspec is marked as default by setting `default_subspecs`, thus it can be imported in a project with the usual `pod 'libxlsxwriter'` in the Podfile. It is used to build the pod as a static library (exactly as it was before). 2. The `Framework` subspec can be imported in a project with `pod 'libxlsxwriter/Framework'` in the Podfile. It is used to build the pod as a framework (i.e. when the Podfile contains the `use_frameworks!` directive). > **NOTE:** Why choosing `libxlsxwriter/Framework` instead of `libxlsxwriter/Swift`? Because the `use_frameworks!` directive can also be used with an Objective-C project, and the proposed solution also works in that case (since there is nothing Swift-specific at the moment). If one day the library will include Swift source files (for instace, to expose C macros that are not automatically translated in Swift and/or to offer a more Swifty syntax), a new `Swift` sub-subspec could be created with the `Framework` subspec as a dependency. ## In a libxlsxwriter user's shoes Good news is a libxlsxwriter user doesn't need to be aware of all this machinery. In order to import the pod as a static library, one can simply add this to the Podfile (as usual): ``` ruby pod 'libxlsxwriter' ``` and then import the pod in project's source files like this (as usual): ``` objc #import <libxlsxwriter/xlsxwriter.h> ``` On the other hand, in order to import the pod as a framework, one can add this to the Podfile: ``` ruby use_frameworks! pod 'libxlsxwriter/Framework' ``` and then import the pod in project's source files in Swift: ``` swift import xlsxwriter ``` or in Objective-C: ``` objc @import xlsxwriter; ``` ## <a name="bottom-line"></a>The bottom line First of all, thanks for reading this far. I tried to explain the whole process so that someone can possibly suggests any way to improve it. This is the best solution I came up with so far, but it would be great to get rid of some of the machinery involved, if possible. Otherwise, the current solution is working and I'm not seeing major side effects (with my own eyes, at least). I have tested the current solution with my example projects in a [dedicated branch](https://github.com/lrossi/libxlsxwriterCocoaExamples/tree/framework-pod). In any case, I would recommend waiting for the final CocoaPods 1.0.0 release before merging this PR. > **NOTE:** A new (third) beta of CocoaPods 1.0.0 came out while I was writing this. I have tested it and this solution still works. However, since the change log mentions an issue with header files and frameworks has been fixed, I'll repeat the previous attempts to see if the whole process can be simplified. ## Credit where credit is due Many thanks to @atomiix for spotting the original issue and for providing a patch in the meantime. --- <sub>🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.</sub>
gitea-mirror 2026-05-05 12:15:05 -06:00
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference: github-starred/libxlsxwriter#406
No description provided.