Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
85 commits
Select commit Hold shift + click to select a range
bc79d43
Give an app extension its build settings, so UIKit links
shai-almog Aug 21, 2026
ee7eb95
Give a brought-in app extension a bundle identity
shai-almog Aug 21, 2026
687ec04
Stop shipping the .ios.appext archive inside the app
shai-almog Aug 21, 2026
115bf16
Resolve extension versions the way the app resolves its own
shai-almog Aug 21, 2026
0cff60f
Stamp the plist the target builds, and only the key's own value
shai-almog Aug 21, 2026
f225368
Read the bundle's identity off the root dict, and fill an empty one
shai-almog Aug 21, 2026
da6420b
Resolve the build settings the plist path is actually written with
shai-almog Aug 21, 2026
2f01b91
Judge a version reference by what it resolves to
shai-almog Aug 21, 2026
d7f1121
Read an identifier's value, not its spelling
shai-almog Aug 21, 2026
b569dbc
Expand a build setting to a fixed point, not one pass
shai-almog Aug 21, 2026
db507a7
Refuse to stamp a plist outside the project directory
shai-almog Aug 21, 2026
886e779
Say at the check why every empty spelling is already empty
shai-almog Aug 21, 2026
cf156d4
Stamp every plist the target may build, in the encoding it was writte…
shai-almog Aug 21, 2026
2b4ae20
Declare the extension's binary, so the IPA is a valid bundle
shai-almog Aug 21, 2026
98c2b5e
Take the development region from the extension, not from us
shai-almog Aug 21, 2026
a628050
Give a brought-in extension a minimum iOS it can ship with
shai-almog Aug 21, 2026
c487df6
Enforce the floor, and close the rest of the parity gaps
shai-almog Aug 21, 2026
79d1cc6
Pick the entitlements by name, not by directory order
shai-almog Aug 21, 2026
bdeffa8
Six review catches on the plist stamper
shai-almog Aug 21, 2026
ff50725
Three more review catches on the stamper
shai-almog Aug 21, 2026
d8144e4
An unknown reference is not an identifier, and grep is not a plist pa…
shai-almog Aug 21, 2026
de24da0
Test the builder here too, not only in the cloud daemon
shai-almog Aug 21, 2026
894b2d2
Expand the archive's own settings in a plist path; read a binary enti…
shai-almog Aug 22, 2026
441081b
Clamp the conditional settings, not just the plain ones
shai-almog Aug 22, 2026
4f527a2
Refuse an extension identifier outside the app, don't just say so
shai-almog Aug 22, 2026
5c674a6
Judge a setting by what it resolves to, before clamping or refusing it
shai-almog Aug 22, 2026
0f4463f
Resolve through Xcode's own settings, and read every entitlements file
shai-almog Aug 22, 2026
e87bb8d
Expand a PRODUCT_NAME chain; ignore conditions this archive never uses
shai-almog Aug 22, 2026
d20771c
A qualified setting overrides the plain one; it does not add to it
shai-almog Aug 22, 2026
52a2f05
Match a condition against the SDK and arch this archive really uses
shai-almog Aug 22, 2026
6d2099e
Resolve with the archive's own context, and stop assuming an SDK version
shai-almog Aug 22, 2026
6e4f152
An unwildcarded sdk condition matches exactly, as Xcode matches it
shai-almog Aug 22, 2026
5288b0e
Ask the selected Xcode for its SDK, and fill the context first
shai-almog Aug 22, 2026
02dfe48
Take the SDK from Xcode, and repair only this archive's settings
shai-almog Aug 23, 2026
da061b5
Three review catches: modifiers, the shared base target, and a cycle
shai-almog Aug 23, 2026
a76dc86
Rank an exact condition above a wildcard; take the arch from the build
shai-almog Aug 23, 2026
dd5d465
Rank wildcards by width, refuse a foreign variant, clamp an empty target
shai-almog Aug 23, 2026
8f79057
The archive's variant is the archive's to declare, and plist paths re…
shai-almog Aug 23, 2026
e6d0a15
Give repairs the whole context, and resolve a qualifier where it lives
shai-almog Aug 23, 2026
c587b75
Resolve conditionals before expanding, and three smaller corrections
shai-almog Aug 23, 2026
3c6c724
Three review catches: padding, helper settings, and variant chains
shai-almog Aug 23, 2026
153c897
Flatten before resolving, and pick the variant list for the archive
shai-almog Aug 23, 2026
b90cb2d
Mirror: one settings reader, and device families from the project type
shai-almog Aug 23, 2026
4638802
Mirror: each Info.plist is stamped in its own candidate's context
shai-almog Aug 23, 2026
ec48ae4
Mirror: the containing app's own plist is never stamped
shai-almog Aug 23, 2026
3090361
Mirror: supply $(PROJECT_NAME), keep a wildcard a wildcard
shai-almog Aug 23, 2026
0c88b83
Mirror: every qualifier group reaches the candidate's context
shai-almog Aug 23, 2026
3cf35f9
Mirror: one plist named by two conditions is stamped in both
shai-almog Aug 23, 2026
e59712b
Empty is a value, and / is not a developer directory
shai-almog Aug 23, 2026
746a931
Merge master, and drop the version helpers it brought a second copy of
shai-almog Aug 24, 2026
1537f7f
Three identifier and floor corrections from review
shai-almog Aug 24, 2026
5a3c8d5
Stamp CFBundleDisplayName, the last key an upload was finding for us
shai-almog Aug 24, 2026
afa8172
Five review findings: ordering, specificity, scope, variants, agreement
shai-almog Aug 24, 2026
f3d11b1
Judge each plist against the identifier its own configuration gets
shai-almog Aug 24, 2026
8498632
The base identifier is the base plist's, not the archive's
shai-almog Aug 24, 2026
ae3ca05
Resolve a referenced target, and keep the host's device family
shai-almog Aug 24, 2026
cbbeec6
An explicit setting governs, and a referenced family is resolved
shai-almog Aug 24, 2026
94e3755
Blank is not a statement, and a family helper has its own context
shai-almog Aug 24, 2026
4c6b3ae
Do not freeze a family reference, and drop an identifier that comes t…
shai-almog Aug 24, 2026
5af83db
Keep the variant, resolve qualifiers in context, compare families as …
shai-almog Aug 24, 2026
f0e4cfd
An identifier written through Xcode's own settings is not a blank one
shai-almog Aug 24, 2026
3f00773
A matching wildcard variant, and an override that reaches only its own
shai-almog Aug 24, 2026
3f8b201
Keep the variant while resolving plists, and let the base inherit
shai-almog Aug 24, 2026
eb43cdc
Coverage, not specificity, decides whether an override silences a plist
shai-almog Aug 24, 2026
10a48cc
See a qualified-only target identifier, and pin the no-null-context c…
shai-almog Aug 24, 2026
24d3adc
A plist path can vary where its key does not, and repairs go per key
shai-almog Aug 24, 2026
735959b
Enumerate the SDKs the archive names, and refuse to invent one
shai-almog Aug 24, 2026
25ef617
Only settings Xcode always defines count as provided
shai-almog Aug 24, 2026
5e5f2f2
A null context means "cannot tell", not a crash
shai-almog Aug 24, 2026
8902110
Enumerate plist paths one variant at a time
shai-almog Aug 24, 2026
efd8c32
Always defined is not the same as never empty
shai-almog Aug 24, 2026
b202e2c
Enumerate the other dimensions, and narrow a wildcard to what matches
shai-almog Aug 24, 2026
49a2676
Enumerate every dimension the archive names, and inherit the project'…
shai-almog Aug 24, 2026
eb1a4c3
A path is not part of a bundle identifier
shai-almog Aug 24, 2026
8185fd2
Constrain the enumeration, name the variant, and require the host head
shai-almog Aug 24, 2026
10f34f3
Filter configurations through the condition, and read ARCHS itself
shai-almog Aug 24, 2026
243b12d
Repair identifiers in every configuration, and recompute ARCHS per co…
shai-almog Aug 24, 2026
90ca99f
A shared plist still needs a pass per context, and ARCHS resolves first
shai-almog Aug 24, 2026
64a3db9
The namespace is decided before the first unknown, and by every helper
shai-almog Aug 24, 2026
4b240b3
Drop only what builds the identifier, and expand a wildcard architecture
shai-almog Aug 24, 2026
1462510
The platform is knowable, and the outermost override is what goes
shai-almog Aug 24, 2026
37546b7
Judge the identifier that governs each build, and drop the winner
shai-almog Aug 24, 2026
ee25baf
Model EFFECTIVE_PLATFORM_NAME, which an identifier may well be built …
shai-almog Aug 24, 2026
fcabb76
Drop the consumer stranded by removing its helper
shai-almog Aug 24, 2026
c91069c
Judge an identifier by what Xcode will actually build it into
shai-almog Aug 24, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -1041,7 +1041,11 @@ static java.util.List<String> injectedPlistStringArray(BuildRequest request, Str

/// Where the value belonging to {@code key} begins -- just past its {@code </key>} -- or -1
/// when the fragment does not carry the key.
private static int injectedValueAt(String inject, String key) {
/// Shared with IPhoneBuilder's app-extension Info.plist stamping, which has to find a
/// key's own value in a real plist for the same reason the comment on
/// {@link #injectedPlistString} gives: the next `<string>` after a key is very often some
/// other key's.
static int injectedValueAt(String inject, String key) {
int at = 0;
while (true) {
int content = contentAfterOpenTag(inject, "key", at);
Expand All @@ -1064,7 +1068,7 @@ private static int injectedValueAt(String inject, String key) {
/// The {@code <} of the next real element at or after {@code from}, or -1 when what follows is
/// text or nothing. Whitespace, comments and CDATA sit between a key and its value in real
/// fragments and none of them is the value.
private static int nextElementAt(String inject, int from) {
static int nextElementAt(String inject, int from) {
int i = from;
while (i < inject.length()) {
if (Character.isWhitespace(inject.charAt(i))) {
Expand All @@ -1085,7 +1089,7 @@ private static int nextElementAt(String inject, int from) {
}

/// The element name at an opening tag, lowercased. Empty for an end tag, which is not one.
private static String tagAt(String inject, int element) {
static String tagAt(String inject, int element) {
StringBuilder tag = new StringBuilder();
for (int j = element + 1; j < inject.length()
&& Character.isLetterOrDigit(inject.charAt(j)); j++) {
Expand Down Expand Up @@ -1169,7 +1173,7 @@ private static int contentAfterOpenTag(String inject, String element, int from)
///
/// Returns `at` when it is already outside both, the position just past the enclosing
/// construct when it is not, and -1 when that construct never ends.
private static int skipMarkupBefore(String inject, int at, int from) {
static int skipMarkupBefore(String inject, int at, int from) {
int cdata = inject.indexOf(CDATA_OPEN, from);
int comment = inject.indexOf(COMMENT_OPEN, from);
boolean cdataFirst = cdata >= 0 && (comment < 0 || cdata < comment);
Expand Down Expand Up @@ -1211,7 +1215,7 @@ private static int nextMarkup(String inject, String tag, int from) {
}

/// The end tag that closes an element, skipping over CDATA sections and comments.
private static int closeOfElement(String inject, int from, String closeTag) {
static int closeOfElement(String inject, int from, String closeTag) {
// `</key >` closes the same element as `</key>`, so the tag is matched as a pattern rather
// than as literal text -- the same reason the opening tags are.
java.util.regex.Matcher m = java.util.regex.Pattern
Expand Down Expand Up @@ -1295,11 +1299,19 @@ private static String stripComments(String value) {
/// entity decoding applies as before. The assembled value is trimmed, matching what this did
/// before CDATA was understood at all.
static String plistStringContent(String raw) {
String exact = plistStringContentExact(raw);
return exact == null ? null : exact.trim();
}

/// The same content WITHOUT the trim, for a caller that has to see the value a plist parser
/// would: <string> 5.4 </string> and <string><![CDATA[ 5.4 ]]></string> both carry padding
/// that Apple compares and this method must not throw away.
static String plistStringContentExact(String raw) {
if (raw == null) {
return null;
}
if (raw.indexOf(CDATA_OPEN) < 0) {
return decodeXmlEntities(stripComments(raw).trim());
return decodeXmlEntities(stripComments(raw));
}
StringBuilder out = new StringBuilder(raw.length());
int i = 0;
Expand All @@ -1320,7 +1332,7 @@ static String plistStringContent(String raw) {
out.append(raw, body, end);
i = end + CDATA_CLOSE.length();
}
return out.toString().trim();
return out.toString();
}

/// Turns the five predefined XML entities back into their characters.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
/*
* Copyright (c) 2026, Codename One and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Codename One designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Codename One through http://www.codenameone.com/ if you
* need additional information or have any questions.
*/
package com.codename1.builders;

import org.junit.Test;

import java.util.Map;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;

public class AppExtensionBuildSettingsTest {

/**
* The block is written the way it appears in a pbxproj: tab-indented, one
* {@code KEY = VALUE;} per line.
*/
private static final String BLOCK = "CLANG_ANALYZER_NONNULL = YES;\n"
+ "\t\t\t\tCLANG_CXX_LANGUAGE_STANDARD = \"gnu++14\";\n"
+ "\t\t\t\tCLANG_ENABLE_MODULES = YES;\n"
+ "\t\t\t\tCLANG_ENABLE_OBJC_ARC = YES;\n"
+ "\t\t\t\tCLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;";

@Test
public void valueLosesItsTrailingSemicolon() {
Map<String, String> settings = IPhoneBuilder.parseXcodeBuildSettings(BLOCK);
// A value of ";" is what CLANG_ENABLE_MODULES used to get, and Xcode reads that
// as "off": no -fmodules, no clang autolinking, and an extension importing UIKit
// reaches ld with Foundation alone and fails on _OBJC_CLASS_$_UIView.
assertEquals("YES", settings.get("CLANG_ENABLE_MODULES"));
assertEquals("YES", settings.get("CLANG_ENABLE_OBJC_ARC"));
assertEquals("YES", settings.get("CLANG_ANALYZER_NONNULL"));
assertEquals("YES_AGGRESSIVE", settings.get("CLANG_WARN_UNGUARDED_AVAILABILITY"));
}

@Test
public void quotedValueIsUnwrappedBeforeItBecomesARubyLiteral() {
Map<String, String> settings = IPhoneBuilder.parseXcodeBuildSettings(BLOCK);
// Kept quotes would be emitted as e.build_settings['...'] = ""gnu++14"", which
// is a Ruby syntax error that takes the whole project fixup script with it.
assertEquals("gnu++14", settings.get("CLANG_CXX_LANGUAGE_STANDARD"));
}

@Test
public void blankAndMalformedLinesAreSkipped() {
Map<String, String> settings = IPhoneBuilder.parseXcodeBuildSettings(
"\n \nCLANG_ENABLE_MODULES = YES;\nnot a setting\n");
assertEquals(1, settings.size());
assertEquals("YES", settings.get("CLANG_ENABLE_MODULES"));
}

@Test
public void paddingIsStrippedFromArchiveSettings() throws Exception {
java.io.File dist = java.nio.file.Files.createTempDirectory("appext").toFile();
java.io.File extension = new java.io.File(dist, "WalletUIExtension");
assertTrue(extension.mkdirs());
java.io.FileWriter w = new java.io.FileWriter(
new java.io.File(extension, "buildSettings.properties"));
w.write("PRODUCT_BUNDLE_IDENTIFIER=com.example.app.Ext \n");
w.close();

// Properties keeps the trailing space and Xcode does not. Kept, preflight validated
// "com.example.app.Ext" while the target was handed "com.example.app.Ext " -- an
// identifier no profile matches, from two readers of one file disagreeing.
assertEquals("com.example.app.Ext", IPhoneBuilder.appExtensionBuildSettings(extension)
.get("PRODUCT_BUNDLE_IDENTIFIER"));
}

@Test
public void extensionDeviceFamiliesFollowTheApp() {
// The translator gives the app target "1" for iphone and "2" for anything else that is
// not "ios"; an extension pinned to "1,2" beside an iPhone-only app is an upload
// rejection for an embedded bundle its container does not support.
assertEquals("1", IPhoneBuilder.embeddedExtensionDeviceFamily("iphone"));
assertEquals("2", IPhoneBuilder.embeddedExtensionDeviceFamily("ipad"));
assertEquals("1,2", IPhoneBuilder.embeddedExtensionDeviceFamily("ios"));
assertEquals("1,2", IPhoneBuilder.embeddedExtensionDeviceFamily(null));
}
}
Loading
Loading