Skip to content

Commit ffff638

Browse files
authored
Fix how public headers are collected from PodSpec (#840)
* Enumate source headers as public header in case if no public_headers_files is defined. Add tests. Add test cleanup * Enumerate platform-dependant source_files from pod spec
1 parent 5d3375c commit ffff638

6 files changed

Lines changed: 333 additions & 6 deletions

File tree

server/src/main/java/com/defold/extender/services/cocoapods/PodBuildSpec.java

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -345,8 +345,16 @@ void addPodIncludePaths(String pattern) {
345345
}
346346

347347
void collectSourceFilesFromSpec(PodSpec spec) {
348-
for (String pattern : spec.publicHeadersPatterns) {
349-
this.publicHeaders.addAll(PodUtils.listFilesGlob(this.dir, pattern));
348+
// if no public_header_files specified treat source headers as public
349+
if (!spec.publicHeadersPatterns.isEmpty()) {
350+
for (String pattern : spec.publicHeadersPatterns) {
351+
this.publicHeaders.addAll(PodUtils.listFilesGlob(this.dir, pattern));
352+
}
353+
} else {
354+
for (String pattern : spec.sourceFilesPatterns) {
355+
List<File> src = PodUtils.listFilesGlob(this.dir, pattern);
356+
this.publicHeaders.addAll(src.stream().filter(f -> { return PodUtils.isHeaderFile(f.getName()); }).toList());
357+
}
350358
}
351359

352360
Iterator<String> it = spec.sourceFilesPatterns.iterator();

server/src/main/java/com/defold/extender/services/cocoapods/PodSpecParser.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,7 @@ public static PodSpec createPodSpec(JSONObject specJson, PodUtils.Platform selec
141141
// find source and header files
142142
// https://guides.cocoapods.org/syntax/podspec.html#source_files
143143
spec.sourceFilesPatterns.addAll(getAsList(specJson, "source_files"));
144+
if (platformSettings != null) spec.sourceFilesPatterns.addAll(getAsList(platformSettings, "source_files"));
144145

145146
return spec;
146147
}

server/src/test/java/com/defold/extender/services/cocoapods/PodBuildSpecTest.java

Lines changed: 210 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,12 @@
99
import java.nio.file.Path;
1010
import java.nio.file.StandardCopyOption;
1111
import java.util.HashMap;
12+
import java.util.List;
1213
import java.util.Map;
1314

1415
import javax.naming.InvalidNameException;
1516

17+
import org.junit.jupiter.api.AfterEach;
1618
import org.junit.jupiter.api.BeforeEach;
1719
import org.junit.jupiter.api.Test;
1820
import org.junit.jupiter.api.TestInfo;
@@ -42,7 +44,25 @@ public void setUp(TestInfo testInfo) throws IOException, InvalidNameException {
4244
this.buildDir.mkdir();
4345
this.podsDir = new File(this.workingDir, "pods");
4446
this.podsDir.mkdir();
45-
this.workingDir.deleteOnExit();
47+
}
48+
49+
@AfterEach
50+
public void tearDown() throws IOException {
51+
if (this.workingDir != null && this.workingDir.exists()) {
52+
deleteRecursively(this.workingDir.toPath());
53+
}
54+
}
55+
56+
private void deleteRecursively(Path path) throws IOException {
57+
Files.walk(path)
58+
.sorted((a, b) -> b.compareTo(a)) // reverse order to delete children first
59+
.forEach(p -> {
60+
try {
61+
Files.delete(p);
62+
} catch (IOException e) {
63+
// ignore cleanup errors
64+
}
65+
});
4666
}
4767

4868
@Test
@@ -107,9 +127,9 @@ public void testCompilationFlags() throws IOException, ExtenderException {
107127
public void testNestedPodSpecName() throws IOException, ExtenderException {
108128
String jsonSpec = Files.readString(Path.of("test-data/pod_specs/YandexMobileMetrica.json"));
109129
XCConfigParser parser = new XCConfigParser(this.buildDir, this.podsDir, PodUtils.Platform.IPHONEOS, "Debug", "arm64");
110-
File sentryTargetFolder = Path.of(this.podsDir.toString(), "Target Support Files", "YandexMobileMetrica").toFile();
111-
sentryTargetFolder.mkdirs();
112-
Files.copy(Path.of("test-data/xcconfigs/YandexMobileMetrica.xcconfig"), Path.of(sentryTargetFolder.toString(), "YandexMobileMetrica.debug.xcconfig"), StandardCopyOption.REPLACE_EXISTING);
130+
File metricaTargetFolder = Path.of(this.podsDir.toString(), "Target Support Files", "YandexMobileMetrica").toFile();
131+
metricaTargetFolder.mkdirs();
132+
Files.copy(Path.of("test-data/xcconfigs/YandexMobileMetrica.xcconfig"), Path.of(metricaTargetFolder.toString(), "YandexMobileMetrica.debug.xcconfig"), StandardCopyOption.REPLACE_EXISTING);
113133

114134
PodSpec podSpec = PodSpecParser.createPodSpec(PodSpecParser.parseJson(jsonSpec), PodUtils.Platform.IPHONEOS, null);
115135
CocoaPodsServiceBuildState cocoapodsState = new CocoaPodsServiceBuildState();
@@ -128,4 +148,190 @@ public void testNestedPodSpecName() throws IOException, ExtenderException {
128148
PodBuildSpec buildSpec = new PodBuildSpec(args, podSpec);
129149
assertEquals(buildSpec.name, "YandexMobileMetrica");
130150
}
151+
152+
@Test
153+
public void testPublicHeaderPatternMatch() throws IOException, ExtenderException {
154+
String jsonSpec = Files.readString(Path.of("test-data/pod_specs/Sentry.json"));
155+
XCConfigParser parser = new XCConfigParser(this.buildDir, this.podsDir, PodUtils.Platform.IPHONEOS, "Debug", "arm64");
156+
File sentryTargetFolder = Path.of(this.podsDir.toString(), "Target Support Files", "Sentry").toFile();
157+
sentryTargetFolder.mkdirs();
158+
Files.copy(Path.of("test-data/xcconfigs/Sentry.xcconfig"), Path.of(sentryTargetFolder.toString(), "Sentry.debug.xcconfig"), StandardCopyOption.REPLACE_EXISTING);
159+
160+
// Files matching public_header_files pattern: Sources/Sentry/Public/*.h
161+
List<String> publicHeaderFiles = List.of(
162+
"Sources/Sentry/Public/SentrySDK.h",
163+
"Sources/Sentry/Public/SentryClient.h",
164+
"Sources/Sentry/Public/SentryOptions.h"
165+
);
166+
167+
// Files NOT matching the pattern (should not be included in publicHeaders)
168+
List<String> otherFiles = List.of(
169+
"Sources/Sentry/Private/SentryPrivate.h",
170+
"Sources/Sentry/SentryInternal.h",
171+
"Sources/Sentry/Public/SentrySDK.m",
172+
"Sources/SentryCrash/SentryCrash.h"
173+
);
174+
175+
// Create all files
176+
Path sentrySourcesDir = Path.of(this.podsDir.toString(), "Sentry");
177+
for (String filePath : publicHeaderFiles) {
178+
Path fullPath = sentrySourcesDir.resolve(filePath);
179+
Files.createDirectories(fullPath.getParent());
180+
Files.createFile(fullPath);
181+
}
182+
for (String filePath : otherFiles) {
183+
Path fullPath = sentrySourcesDir.resolve(filePath);
184+
Files.createDirectories(fullPath.getParent());
185+
Files.createFile(fullPath);
186+
}
187+
188+
PodSpec podSpec = PodSpecParser.createPodSpec(PodSpecParser.parseJson(jsonSpec), PodUtils.Platform.IPHONEOS, null);
189+
CocoaPodsServiceBuildState cocoapodsState = new CocoaPodsServiceBuildState();
190+
cocoapodsState.workingDir = this.workingDir;
191+
cocoapodsState.podsDir = this.podsDir;
192+
cocoapodsState.selectedPlatform = PodUtils.Platform.IPHONEOS;
193+
194+
CreateBuildSpecArgs args = new CreateBuildSpecArgs.Builder()
195+
.setConfigParser(parser)
196+
.setCocoapodsBuildState(cocoapodsState)
197+
.setJobContext(this.jobContext)
198+
.build();
199+
args.buildDir = this.buildDir;
200+
args.configuration = "Debug";
201+
202+
PodBuildSpec buildSpec = new PodBuildSpec(args, podSpec);
203+
buildSpec.addSubSpec(podSpec.getSubspec("Core"));
204+
205+
// Verify only the public_header_files pattern matches are included
206+
for (String headerFile : publicHeaderFiles) {
207+
Path expectedPath = sentrySourcesDir.resolve(headerFile);
208+
assertTrue(
209+
buildSpec.publicHeaders.stream().anyMatch(h -> h.getAbsolutePath().equals(expectedPath.toString())),
210+
"publicHeaders should contain: " + expectedPath
211+
);
212+
}
213+
214+
assertEquals(publicHeaderFiles.size(), buildSpec.publicHeaders.size(),
215+
"publicHeaders should contain exactly " + publicHeaderFiles.size() + " files");
216+
}
217+
218+
@Test
219+
public void testNoPublicHeaderPattern() throws IOException, ExtenderException {
220+
String jsonSpec = Files.readString(Path.of("test-data/pod_specs/AppAuth.json"));
221+
XCConfigParser parser = new XCConfigParser(this.buildDir, this.podsDir, PodUtils.Platform.IPHONEOS, "Debug", "arm64");
222+
File appAuthTargetFolder = Path.of(this.podsDir.toString(), "Target Support Files", "AppAuth").toFile();
223+
appAuthTargetFolder.mkdirs();
224+
Files.copy(Path.of("test-data/xcconfigs/AppAuth.xcconfig"), Path.of(appAuthTargetFolder.toString(), "AppAuth.debug.xcconfig"), StandardCopyOption.REPLACE_EXISTING);
225+
226+
List<String> tmpFiles = List.of(
227+
"AppAuth.h",
228+
"AppAuth/iOS/OIDAuthState+IOS.h",
229+
"AppAuth/iOS/OIDAuthState+IOS.m",
230+
"AppAuth/iOS/OIDAuthorizationService+IOS.h",
231+
"AppAuth/iOS/OIDAuthorizationService+IOS.m",
232+
"AppAuth/iOS/OIDExternalUserAgentCatalyst.h",
233+
"AppAuth/iOS/OIDExternalUserAgentCatalyst.m",
234+
"AppAuth/iOS/OIDExternalUserAgentIOS.h",
235+
"AppAuth/iOS/OIDExternalUserAgentIOS.m",
236+
"AppAuth/iOS/OIDExternalUserAgentIOSCustomBrowser.h",
237+
"AppAuth/iOS/OIDExternalUserAgentIOSCustomBrowser.m",
238+
"AppAuthCore.h",
239+
"AppAuthCore/OIDAuthState.h",
240+
"AppAuthCore/OIDAuthState.m",
241+
"AppAuthCore/OIDAuthStateChangeDelegate.h",
242+
"AppAuthCore/OIDAuthStateErrorDelegate.h",
243+
"AppAuthCore/OIDAuthorizationRequest.h",
244+
"AppAuthCore/OIDAuthorizationRequest.m",
245+
"AppAuthCore/OIDAuthorizationResponse.h",
246+
"AppAuthCore/OIDAuthorizationResponse.m",
247+
"AppAuthCore/OIDAuthorizationService.h",
248+
"AppAuthCore/OIDAuthorizationService.m",
249+
"AppAuthCore/OIDClientMetadataParameters.h",
250+
"AppAuthCore/OIDClientMetadataParameters.m",
251+
"AppAuthCore/OIDDefines.h",
252+
"AppAuthCore/OIDEndSessionRequest.h",
253+
"AppAuthCore/OIDEndSessionRequest.m",
254+
"AppAuthCore/OIDEndSessionResponse.h",
255+
"AppAuthCore/OIDEndSessionResponse.m",
256+
"AppAuthCore/OIDError.h",
257+
"AppAuthCore/OIDError.m",
258+
"AppAuthCore/OIDErrorUtilities.h",
259+
"AppAuthCore/OIDErrorUtilities.m",
260+
"AppAuthCore/OIDExternalUserAgent.h",
261+
"AppAuthCore/OIDExternalUserAgentRequest.h",
262+
"AppAuthCore/OIDExternalUserAgentSession.h",
263+
"AppAuthCore/OIDFieldMapping.h",
264+
"AppAuthCore/OIDFieldMapping.m",
265+
"AppAuthCore/OIDGrantTypes.h",
266+
"AppAuthCore/OIDGrantTypes.m",
267+
"AppAuthCore/OIDIDToken.h",
268+
"AppAuthCore/OIDIDToken.m",
269+
"AppAuthCore/OIDRegistrationRequest.h",
270+
"AppAuthCore/OIDRegistrationRequest.m",
271+
"AppAuthCore/OIDRegistrationResponse.h",
272+
"AppAuthCore/OIDRegistrationResponse.m",
273+
"AppAuthCore/OIDResponseTypes.h",
274+
"AppAuthCore/OIDResponseTypes.m",
275+
"AppAuthCore/OIDScopeUtilities.h",
276+
"AppAuthCore/OIDScopeUtilities.m",
277+
"AppAuthCore/OIDScopes.h",
278+
"AppAuthCore/OIDScopes.m",
279+
"AppAuthCore/OIDServiceConfiguration.h",
280+
"AppAuthCore/OIDServiceConfiguration.m",
281+
"AppAuthCore/OIDServiceDiscovery.h",
282+
"AppAuthCore/OIDServiceDiscovery.m",
283+
"AppAuthCore/OIDTokenRequest.h",
284+
"AppAuthCore/OIDTokenRequest.m",
285+
"AppAuthCore/OIDTokenResponse.h",
286+
"AppAuthCore/OIDTokenResponse.m",
287+
"AppAuthCore/OIDTokenUtilities.h",
288+
"AppAuthCore/OIDTokenUtilities.m",
289+
"AppAuthCore/OIDURLQueryComponent.h",
290+
"AppAuthCore/OIDURLQueryComponent.m",
291+
"AppAuthCore/OIDURLSessionProvider.h",
292+
"AppAuthCore/OIDURLSessionProvider.m",
293+
"AppAuthCore/Resources/PrivacyInfo.xcprivacy"
294+
);
295+
296+
// Create empty files at enumerated paths
297+
Path appAuthSourcesDir = Path.of(this.podsDir.toString(), "AppAuth", "Sources");
298+
for (String filePath : tmpFiles) {
299+
Path fullPath = appAuthSourcesDir.resolve(filePath);
300+
Files.createDirectories(fullPath.getParent());
301+
Files.createFile(fullPath);
302+
}
303+
304+
305+
PodSpec podSpec = PodSpecParser.createPodSpec(PodSpecParser.parseJson(jsonSpec), PodUtils.Platform.IPHONEOS, null);
306+
CocoaPodsServiceBuildState cocoapodsState = new CocoaPodsServiceBuildState();
307+
cocoapodsState.workingDir = this.workingDir;
308+
cocoapodsState.podsDir = this.podsDir;
309+
cocoapodsState.selectedPlatform = PodUtils.Platform.IPHONEOS;
310+
311+
CreateBuildSpecArgs args = new CreateBuildSpecArgs.Builder()
312+
.setConfigParser(parser)
313+
.setCocoapodsBuildState(cocoapodsState)
314+
.setJobContext(this.jobContext)
315+
.build();
316+
args.buildDir = this.buildDir;
317+
args.configuration = "Debug";
318+
319+
PodBuildSpec buildSpec = new PodBuildSpec(args, podSpec);
320+
buildSpec.addSubSpec(podSpec.getSubspec("Core"));
321+
buildSpec.addSubSpec(podSpec.getSubspec("ExternalUserAgent"));
322+
List<String> expectedHeaders = tmpFiles.stream()
323+
.filter(f -> f.endsWith(".h"))
324+
.toList();
325+
326+
for (String headerFile : expectedHeaders) {
327+
Path expectedPath = appAuthSourcesDir.resolve(headerFile);
328+
assertTrue(
329+
buildSpec.publicHeaders.stream().anyMatch(h -> h.getAbsolutePath().equals(expectedPath.toString())),
330+
"publicHeaders should contain: " + expectedPath
331+
);
332+
}
333+
334+
assertEquals(expectedHeaders.size(), buildSpec.publicHeaders.size(),
335+
"publicHeaders should contain exactly " + expectedHeaders.size() + " files");
336+
}
131337
}

server/src/test/java/com/defold/extender/services/cocoapods/PodSpecParserTest.java

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,4 +103,20 @@ public void testDefaultSubspecs() throws ExtenderException, IOException {
103103
};
104104
assertArrayEquals(expectedValues, podSpec.defaultSubspecs.toArray());
105105
}
106+
107+
@Test
108+
public void testSourceFlesFromPlatform() throws IOException, ExtenderException {
109+
String jsonSpec = Files.readString(Path.of("test-data/pod_specs/AppAuth.json"));
110+
PodSpec podSpec = PodSpecParser.createPodSpec(PodSpecParser.parseJson(jsonSpec), PodUtils.Platform.IPHONEOS, null);
111+
112+
PodSpec subSpec = podSpec.getSubspec("ExternalUserAgent");
113+
String[] expectedPatterns = new String[]{
114+
"Sources/AppAuth.h",
115+
"Sources/AppAuth/*.h",
116+
"Sources/AppAuth/*.m",
117+
"Sources/AppAuth/iOS/**/*.h",
118+
"Sources/AppAuth/iOS/**/*.m"
119+
};
120+
assertArrayEquals(expectedPatterns, subSpec.sourceFilesPatterns.toArray());
121+
}
106122
}
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
{
2+
"name": "AppAuth",
3+
"version": "2.0.0",
4+
"summary": "AppAuth for iOS and macOS is a client SDK for communicating with OAuth 2.0 and OpenID Connect providers.",
5+
"description": "AppAuth for iOS and macOS is a client SDK for communicating with [OAuth 2.0]\n(https://tools.ietf.org/html/rfc6749) and [OpenID Connect]\n(http://openid.net/specs/openid-connect-core-1_0.html) providers. It strives to\ndirectly map the requests and responses of those specifications, while following\nthe idiomatic style of the implementation language. In addition to mapping the\nraw protocol flows, convenience methods are available to assist with common\ntasks like performing an action with fresh tokens.\n\nIt follows the OAuth 2.0 for Native Apps best current practice\n([RFC 8252](https://tools.ietf.org/html/rfc8252)).",
6+
"homepage": "https://openid.github.io/AppAuth-iOS",
7+
"license": "Apache License, Version 2.0",
8+
"authors": {
9+
"William Denniss": "wdenniss@google.com",
10+
"Steven E Wright": "stevewright@google.com",
11+
"Julien Bodet": "julien.bodet92@gmail.com"
12+
},
13+
"platforms": {
14+
"ios": "12.0",
15+
"osx": "10.12",
16+
"watchos": "2.0",
17+
"tvos": "9.0"
18+
},
19+
"source": {
20+
"git": "https://github.com/openid/AppAuth-iOS.git",
21+
"tag": "2.0.0"
22+
},
23+
"requires_arc": true,
24+
"pod_target_xcconfig": {
25+
"DEFINES_MODULE": "YES"
26+
},
27+
"default_subspecs": [
28+
"Core",
29+
"ExternalUserAgent"
30+
],
31+
"subspecs": [
32+
{
33+
"name": "Core",
34+
"source_files": [
35+
"Sources/AppAuthCore.h",
36+
"Sources/AppAuthCore/*.{h,m}"
37+
],
38+
"resource_bundles": {
39+
"AppAuthCore_Privacy": [
40+
"Sources/AppAuthCore/Resources/PrivacyInfo.xcprivacy"
41+
]
42+
}
43+
},
44+
{
45+
"name": "ExternalUserAgent",
46+
"dependencies": {
47+
"AppAuth/Core": [
48+
49+
]
50+
},
51+
"source_files": [
52+
"Sources/AppAuth.h",
53+
"Sources/AppAuth/*.{h,m}"
54+
],
55+
"ios": {
56+
"source_files": "Sources/AppAuth/iOS/**/*.{h,m}",
57+
"frameworks": "SafariServices",
58+
"weak_frameworks": "AuthenticationServices"
59+
},
60+
"platforms": {
61+
"ios": "12.0",
62+
"osx": "10.12"
63+
},
64+
"osx": {
65+
"source_files": "Sources/AppAuth/macOS/**/*.{h,m}",
66+
"weak_frameworks": "AuthenticationServices"
67+
}
68+
},
69+
{
70+
"name": "TV",
71+
"source_files": [
72+
"Sources/AppAuthTV.h",
73+
"Sources/AppAuthTV/*.{h,m}"
74+
],
75+
"dependencies": {
76+
"AppAuth/Core": [
77+
78+
]
79+
}
80+
}
81+
]
82+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO
2+
CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/AppAuth
3+
DEFINES_MODULE = YES
4+
GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1
5+
OTHER_LDFLAGS = $(inherited) -framework "SafariServices" -weak_framework "AuthenticationServices"
6+
PODS_BUILD_DIR = ${BUILD_DIR}
7+
PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)
8+
PODS_DEVELOPMENT_LANGUAGE = ${DEVELOPMENT_LANGUAGE}
9+
PODS_ROOT = ${SRCROOT}
10+
PODS_TARGET_SRCROOT = ${PODS_ROOT}/AppAuth
11+
PODS_XCFRAMEWORKS_BUILD_DIR = $(PODS_CONFIGURATION_BUILD_DIR)/XCFrameworkIntermediates
12+
PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier}
13+
SKIP_INSTALL = YES
14+
USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES

0 commit comments

Comments
 (0)