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

Add support for modular header search paths, include "legacy" support #7412

Merged
merged 4 commits into from Feb 16, 2018

Conversation

dnkoutso
Copy link
Contributor

@dnkoutso dnkoutso commented Feb 6, 2018

  • Assuming PodA, PodB and PodC in a project.
  • Assuming PodA depends only on PodB but not on PodC

Header search paths for PodA:

before (<= 1.4, a.k.a today)

${PODS_ROOT}/Headers/Private
${PODS_ROOT}/Headers/Public
${PODS_ROOT}/Headers/Private/PodA
${PODS_ROOT}/Headers/Public/PodA
${PODS_ROOT}/Headers/Public/PodB
${PODS_ROOT}/Headers/Public/PodC

Notice that root Public folder but also that PodC is included in the pod's header search paths despite the fact that PodA does not depend on PodC.

after (> 1.5)

${PODS_ROOT}/Headers/Private/PodA
${PODS_ROOT}/Headers/Public/PodA
${PODS_ROOT}/Headers/Public/PodA/PodA
${PODS_ROOT}/Headers/Public/PodB
${PODS_ROOT}/Headers/Public/PodB/PodB

Notice that PodC is correctly not included (due to #7116) but also that the root folder Public is no longer present which would have otherwise allowed consumers to include headers of PodC without directly depending on it in their podspec.

Instead, the root Public folder is "expanded" so users can continue to use <> and "" header imports. Functionally, this is still the same and it shouldn't break anyone upgrading to 1.5.0 but it also makes it more clear by removing the root Public folder.

When using/enabling modular headers there is no "before" state because we never had modular header support in CocoaPods, but PodA header search paths look like this (using static libraries):

${PODS_ROOT}/Headers/Private/PodA
${PODS_ROOT}/Headers/Public/PodA
${PODS_ROOT}/Headers/Public/PodB

As for the file system all public headers are now nested an additional folder further so it looks like this:

Public header symlinks on the file system (notice the extra nested folder):

Pods/Headers/Public/PodA/PodA/PodA.h
Pods/Headers/Public/PodB/PodB/PodB.h
Pods/Headers/Public/PodC/PodC/PodC.h

Private header symlinks on the file system (notice no extra folder as all imports can work with ""):

Pods/Headers/Private/PodA/PodA_Private.h
Pods/Headers/Private/PodB/PodB_Private.h
Pods/Headers/Private/PodC/PodC_Private.h

This means that PodA can now use <> imports of PodB but not use "" imports for PodB private headers (unless maybe USE_HEADERMAPS is set to YES?).

The above only applies HEADER_SEARCH_PATHS which get populated when static libraries are used. When frameworks are used then FRAMEWORK_SEARCH_PATHS are used which continue to work as they are today.

Note that CocoaPods does not also change or interfere with the USE_HEADERMAPS flag, which can cause plenty of dark magic to find headers of targets that are otherwise not part of the header search paths.

closes #7011

@paulb777
Copy link
Member

paulb777 commented Feb 6, 2018

This looks great! How will a podspec choose to "legacy" or "new" functionality?

@dnkoutso
Copy link
Contributor Author

dnkoutso commented Feb 6, 2018

@paulb777
Copy link
Member

paulb777 commented Feb 6, 2018

Isn't that comment only about Swift static libraries? My understanding is that this PR making the imports more restrictive will break Pods that were implicitly depending on the incorrectly exposed headers - like using quotes instead of brackets or importing a private header.

# @param [Array<Pathname>] headers
# The absolute paths of the headers which need to be mapped.
#
# @return [Hash{Pathname => Array<Pathname>}] A hash containing the
# headers folders as the keys and the absolute paths of the
# header files as the values.
#
def header_mappings(headers_sandbox, file_accessor, headers)
def header_mappings(headers_sandbox, file_accessor, headers, visibility_scope = :private)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am thinking on removing visibilty_scope from this method and instead pass in the initializer. Some header stores are explicitly created as private stores but yet this method talks about a public header scope.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

actually this comment is on the wrong file, I meant header_store.rb and yes I changed it there. This is fine to keep here.

@dnkoutso
Copy link
Contributor Author

dnkoutso commented Feb 6, 2018

@paulb777 it is mostly tied to Swift static libs but not 100%, the defines_module? method has three rules:

  1. all pods with Swift must use a module (static lib or not when Swift Static Library support #6966 lands)
  2. If requires frameworks is on then true (in which case FRAMEWORKS_SEARCH_PATHS are used instead)
  3. If the pod author had specified DEFINES_MODULE => 'YES' in their config.

I think "turning this on by default" might break the world. Any additional thoughts or ideas around this?

We could potentially offer a Podfile flag to enable modules everywhere

@paulb777
Copy link
Member

paulb777 commented Feb 6, 2018

I'm worried about (2). I suspect we have pure Objective C podspecs that are sloppy with #import's that would break. I would be fine to fix them, but can't speak for the world.

@dnkoutso dnkoutso force-pushed the strict_header_search_paths branch 2 times, most recently from 68c76b6 to cac5952 Compare February 6, 2018 20:58
@dnkoutso dnkoutso added this to the 1.5.0 milestone Feb 6, 2018
@segiddins
Copy link
Member

@paulb777 pods built as frameworks already dont get the header search paths, since they find headers via the FRAMEWORK_SEARCH_PATHS

@@ -147,11 +147,15 @@ def link_headers
pod_target.build_headers.add_search_path(headers_sandbox, pod_target.platform)
sandbox.public_headers.add_search_path(headers_sandbox, pod_target.platform)

# Private headers will always end up in <Pods/Headers/Private/PodA/*.h
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why the <?

header_mappings(headers_sandbox, file_accessor, file_accessor.headers).each do |namespaced_path, files|
pod_target.build_headers.add_files(namespaced_path, files.reject { |f| f.to_path =~ framework_exp })
end

header_mappings(headers_sandbox, file_accessor, file_accessor.public_headers).each do |namespaced_path, files|
# Public headers on the other hand will be added in <Pods/Headers/Public/<PodA/PodA/*.h
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

<s?

header_mappings(headers_sandbox, file_accessor, file_accessor.public_headers).each do |namespaced_path, files|
# Public headers on the other hand will be added in <Pods/Headers/Public/<PodA/PodA/*.h
# The extra folder is intentional in order for `<>` imports to work.
header_mappings(headers_sandbox, file_accessor, file_accessor.public_headers, true).each do |namespaced_path, files|
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

looks like the 4th parameter should be a symbol rather than a bool?

@@ -571,13 +584,19 @@ def version
# @return [Array<String>] The set of header search paths this target uses.
#
def header_search_paths(include_test_dependent_targets = false)
defines_module = defines_module?
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no need to extract this into a variable, it's cached

@search_paths_cache[key] = search_paths.uniq.flat_map do |entry|
path = "${PODS_ROOT}/#{headers_dir}/#{entry[:path]}"
paths = [path]
paths.push("#{path}/#{entry[:path].basename}") if !use_modular_headers && @visibility_scope == :public
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we may also need to keep doing this for podspecs that define a header mappings dir, since they might be using something like j2objc that will expect things like com/company/foo/bar/header.h to be directly importable, without the module name prefix

@dnkoutso dnkoutso force-pushed the strict_header_search_paths branch 2 times, most recently from 4da688a to 953f391 Compare February 6, 2018 23:01
@dnkoutso
Copy link
Contributor Author

dnkoutso commented Feb 7, 2018

/cc @efirestone please take a look at the top of this PR for all header description.

@segiddins
Copy link
Member

Updated integration specs, also fixed compatibility with pods that define a header_mappings_dir

return @defines_module if defined?(@defines_module)
return @defines_module = true if uses_swift? || requires_frameworks?

@defines_module = non_test_specs.any? { |s| s.consumer(platform).pod_target_xcconfig['DEFINES_MODULE'] == 'YES' }
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we need to decide if we want to hoist this setting up into the Podfile, instead of leaving it in the podspec

@dnkoutso
Copy link
Contributor Author

dnkoutso commented Feb 12, 2018

LGTM, thanks for the fix on header mappings dir, do we want to merge this and continue discussion of defines_module? in #6966?

# For >= 1.5.0 we use modular (stricter) header search paths this means that the integrated target will only be
# able to import public headers using `<>` or `@import` notation, but never import any private headers.
#
# For < 1.5.0 legacy header search paths the same rules apply: Its the wild west.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Its -> It's

@@ -133,13 +137,6 @@ module XCConfig
@xcconfig.to_hash['PODS_TARGET_SRCROOT'].should == '${PODS_ROOT}/../../spec/fixtures/banana-lib'
end

it 'adds the library build headers and public headers search paths to the xcconfig, with quotes' do
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can/should this be converted to a negative test?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like it, I may have toyed with the idea but ultimately decided to remove. Will add back.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

return @defines_module = true if uses_swift? || requires_frameworks?
return @defines_module = true if target_definitions.any? { |td| td.build_pod_as_module?(pod_name) }

@defines_module = non_test_specs.any? { |s| s.consumer(platform).pod_target_xcconfig['DEFINES_MODULE'] == 'YES' }
Copy link
Contributor Author

@dnkoutso dnkoutso Feb 15, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we decided to keep this for now

@segiddins
Copy link
Member

Updated, ready for final review!

@dnkoutso
Copy link
Contributor Author

👍 👍

@efirestone
Copy link
Contributor

A few possible gotchas I see:

  1. By removing ${PODS_ROOT}/Headers/Public/ it means that PodA can no longer import PodC. This is correct, but has the potential to break existing pods, unless legacy support also brings back that import.
  2. You make mention of using "" to import files like "PodA.h", but I don't believe this will work unless Pods is also populating the USER_HEADER_SEARCH_PATHS along with the standard HEADER_SEARCH_PATHS. By including the nested path (like ${PODS_ROOT}/Headers/Public/PodA/PodA) you'll keep the ability to import things like <PodA.h>, but I don't believe "PodA.h" will work. This isn't a change from existing behavior, but I wanted to clarify based on your comments.
  3. We will eventually want to remove the nested folders (like ${PODS_ROOT}/Headers/Public/PodA/PodA) as they're a bad practice and can easily lead to conflicts. For example, if two dependencies have a NSString+Additions.h file, then which one gets imported with <NSString+Additions.h> is dependent on the order of the search paths. Instead we should require an explicit <PodA/NSString+Additions.h>.

@segiddins
Copy link
Member

By removing ${PODS_ROOT}/Headers/Public/ it means that PodA can no longer import PodC. This is correct, but has the potential to break existing pods, unless legacy support also brings back that import.

Legacy support retains the ability to import everything

You make mention of using "" to import files like "PodA.h", but I don't believe this will work unless Pods is also populating the USER_HEADER_SEARCH_PATHS along with the standard HEADER_SEARCH_PATHS. By including the nested path (like ${PODS_ROOT}/Headers/Public/PodA/PodA) you'll keep the ability to import things like <PodA.h>, but I don't believe "PodA.h" will work. This isn't a change from existing behavior, but I wanted to clarify based on your comments.

Correct

We will eventually want to remove the nested folders (like ${PODS_ROOT}/Headers/Public/PodA/PodA) as they're a bad practice and can easily lead to conflicts. For example, if two dependencies have a NSString+Additions.h file, then which one gets imported with <NSString+Additions.h> is dependent on the order of the search paths. Instead we should require an explicit <PodA/NSString+Additions.h>.

The fully namespaced import is required when "modular_headers" are enabled for a pod

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.

Option to set HEADER_SEARCH_PATHS to just "${PODS_ROOT}/Headers/Public"?
4 participants