Skip to content

Commit 2c6593e

Browse files
committed
[[ Bug 14422 ]] Allow specification of min_os_version fields when building mach-o standalones.
The plist in an app bundle must have a MinOsVersion field which matches the mach-o load command inside the executable. This patch adds a 'min_os_version' key to the deploy array used when using the deploy command. The key's value can either be a string or an array. In the former case, the string is taken to be a version to apply to all architectures in the (fat) binary. In the latter case, the array is expected to be a mapping from architecture string (key) to version (value). The architecture string can be one of: i386, x86_64, armv6, armv7, armv7s, arm64, ppc, ppc64 If the architecture string is empty it is taken to be 'unknown'. The version (value) must be in the X.Y.Z where X, Y, Z are integers. For example: put "6.1.0" into tDeploy["min_os_version"]["i386"] put "8.1.0" into tDeploy["min_os_version"]["armv7"] put "7.1.0" into tDeploy["min_os_version"]["arm64"] When building a standalone, the deploy command will rewrite the min os version load command in each slice based on the architecture of the slice. If a given architecture is not present in the min_os_version array, then it will take the value specified for the 'unknown' (empty key). If neither a given architecture is present nor an 'unknown' entry then the deploy command will not touch the min_os_version load command.
1 parent ff97a45 commit 2c6593e

File tree

3 files changed

+237
-7
lines changed

3 files changed

+237
-7
lines changed

engine/src/deploy.cpp

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,57 @@ extern Boolean InitSSLCrypt(void);
8989

9090
////////////////////////////////////////////////////////////////////////////////
9191

92+
static const char *kMCDeployArchitectureStrings[] =
93+
{
94+
"",
95+
"i386",
96+
"x86-64",
97+
"armv6",
98+
"armv7",
99+
"armv7s",
100+
"arm64",
101+
"ppc",
102+
"ppc64",
103+
nil,
104+
};
105+
106+
static bool MCDeployMapArchitectureString(const MCString& p_string, MCDeployArchitecture& r_architecture)
107+
{
108+
for(uindex_t i = 0; kMCDeployArchitectureStrings[i] != nil; i++)
109+
{
110+
// As 'p_string' is an MCString the '==' operator does a caseless comparison.
111+
if (p_string == kMCDeployArchitectureStrings[i])
112+
{
113+
r_architecture = (MCDeployArchitecture)i;
114+
return true;
115+
}
116+
}
117+
118+
return false;
119+
}
120+
121+
static Exec_stat MCDeployPushMinOSVersion(MCDeployParameters& p_params, MCDeployArchitecture p_arch, const char *p_vers_string)
122+
{
123+
int t_major, t_minor, t_inc;
124+
t_major = t_minor = t_inc = 0;
125+
sscanf(p_vers_string, "%d.%d.%d", &t_major, &t_minor, &t_inc);
126+
127+
if (!MCMemoryResizeArray(p_params . min_os_version_count + 1, p_params . min_os_versions, p_params . min_os_version_count))
128+
return ES_ERROR;
129+
130+
uint32_t t_version;
131+
t_version = (t_major & 0xFFFF) << 16;
132+
t_version |= (t_minor & 0xFF) << 8;
133+
t_version |= (t_inc & 0xFF) << 0;
134+
135+
p_params . min_os_versions[p_params . min_os_version_count - 1] . architecture = p_arch;
136+
p_params . min_os_versions[p_params . min_os_version_count - 1] . version = t_version;
137+
138+
return ES_NORMAL;
139+
}
140+
141+
////////////////////////////////////////////////////////////////////////////////
142+
92143
static bool MCDeployWriteDefinePrologueSection(const MCDeployParameters& p_params, MCDeployCapsuleRef p_capsule)
93144
{
94145
MCCapsulePrologueSection t_prologue;
@@ -531,6 +582,51 @@ Exec_stat MCIdeDeploy::exec(MCExecPoint& ep)
531582
if (t_stat == ES_NORMAL && ep2 . getarray() != NULL)
532583
t_params . version_info = new MCVariableValue(*ep2 . getarray());
533584
}
585+
586+
// The 'min_os_version' is either a string or an array. If it is a string then
587+
// it encodes the version against the 'Unknown' architecture which is interpreted
588+
// by the deploy command to mean all architectures. Otherwise, the keys in the
589+
// array are assumed to be architecture names and each is pushed on the array.
590+
// If multiple entries are present, then the 'unknown' mapping is used for any
591+
// architecture not explicitly specified. The current architecture strings that are
592+
// known are:
593+
// i386, x86_64, armv6, armv7, armv7s, arm64, ppc, ppc64
594+
// The empty string is taken to be 'unknown'.
595+
if (t_stat == ES_NORMAL)
596+
{
597+
t_stat = t_array -> fetch_element(ep2, "min_os_version");
598+
if (t_stat == ES_NORMAL)
599+
{
600+
if (ep2 . getformat() == VF_ARRAY)
601+
{
602+
MCExecPoint ep3(ep2);
603+
MCHashentry *t_entry;
604+
uindex_t t_index;
605+
t_entry = nil;
606+
t_index = 0;
607+
for(;;)
608+
{
609+
if (t_stat == ES_ERROR)
610+
break;
611+
612+
t_entry = ep2 . getarray() -> get_array() -> getnextelement(t_index, t_entry, False, ep);
613+
if (t_entry == nil)
614+
break;
615+
616+
MCDeployArchitecture t_arch;
617+
if (!MCDeployMapArchitectureString(t_entry -> string, t_arch))
618+
continue;
619+
620+
t_stat = t_entry -> value . fetch(ep3);
621+
622+
if (t_stat == ES_NORMAL)
623+
t_stat = MCDeployPushMinOSVersion(t_params, t_arch, ep3 . getcstring());
624+
}
625+
}
626+
else
627+
t_stat = MCDeployPushMinOSVersion(t_params, kMCDeployArchitecture_Unknown, ep2 . getcstring());
628+
}
629+
}
534630

535631
// If platform is iOS and we are not Mac then error
536632
#ifndef _MACOSX
@@ -605,6 +701,7 @@ Exec_stat MCIdeDeploy::exec(MCExecPoint& ep)
605701
delete t_params . engine_ppc;
606702
delete t_params . engine_x86;
607703
delete t_params . version_info;
704+
MCMemoryDeleteArray(t_params . min_os_versions);
608705

609706
if (t_stat == ES_ERROR && t_soft_error)
610707
return ES_NORMAL;

engine/src/deploy.h

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,27 @@ along with LiveCode. If not see <http://www.gnu.org/licenses/>. */
1919

2020
////////////////////////////////////////////////////////////////////////////////
2121

22+
enum MCDeployArchitecture
23+
{
24+
kMCDeployArchitecture_Unknown,
25+
kMCDeployArchitecture_I386,
26+
kMCDeployArchitecture_X86_64,
27+
kMCDeployArchitecture_ARMV6,
28+
kMCDeployArchitecture_ARMV7,
29+
kMCDeployArchitecture_ARMV7S,
30+
kMCDeployArchitecture_ARM64,
31+
kMCDeployArchitecture_PPC,
32+
kMCDeployArchitecture_PPC64,
33+
};
34+
35+
struct MCDeployMinOSVersion
36+
{
37+
// The architecture this version applies to.
38+
MCDeployArchitecture architecture;
39+
// The version word encoded as nibbles XXXX.YY.ZZ for X.Y.Z.
40+
uint32_t version;
41+
};
42+
2243
struct MCDeployParameters
2344
{
2445
// The path to the engine binaries to use. On Windows and Linux this should
@@ -32,6 +53,11 @@ struct MCDeployParameters
3253
// fields.
3354
MCVariableValue *version_info;
3455

56+
// When building for Mac/iOS, you can specify a min os version per arch
57+
// slice.
58+
MCDeployMinOSVersion *min_os_versions;
59+
uint32_t min_os_version_count;
60+
3561
// The root stackfile to be included in the standalone.
3662
char *stackfile;
3763
// The array of auxillary stackfiles to be included in the standalone.

engine/src/deploy_macosx.cpp

Lines changed: 114 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,7 @@ typedef uint32_t cpu_subtype_t;
100100
#define CPU_TYPE_MC98000 ((cpu_type_t) 10)
101101
#define CPU_TYPE_HPPA ((cpu_type_t) 11)
102102
#define CPU_TYPE_ARM ((cpu_type_t) 12)
103+
#define CPU_TYPE_ARM64 (CPU_TYPE_ARM | CPU_ARCH_ABI64)
103104
#define CPU_TYPE_MC88000 ((cpu_type_t) 13)
104105
#define CPU_TYPE_SPARC ((cpu_type_t) 14)
105106
#define CPU_TYPE_I860 ((cpu_type_t) 15)
@@ -189,6 +190,30 @@ typedef uint32_t cpu_subtype_t;
189190
#define CPU_SUBTYPE_POWERPC_7450 ((cpu_subtype_t) 11)
190191
#define CPU_SUBTYPE_POWERPC_970 ((cpu_subtype_t) 100)
191192

193+
/*
194+
* ARM subtypes
195+
*/
196+
#define CPU_SUBTYPE_ARM_ALL ((cpu_subtype_t) 0)
197+
#define CPU_SUBTYPE_ARM_V4T ((cpu_subtype_t) 5)
198+
#define CPU_SUBTYPE_ARM_V6 ((cpu_subtype_t) 6)
199+
#define CPU_SUBTYPE_ARM_V5TEJ ((cpu_subtype_t) 7)
200+
#define CPU_SUBTYPE_ARM_XSCALE ((cpu_subtype_t) 8)
201+
#define CPU_SUBTYPE_ARM_V7 ((cpu_subtype_t) 9)
202+
#define CPU_SUBTYPE_ARM_V7F ((cpu_subtype_t) 10) /* Cortex A9 */
203+
#define CPU_SUBTYPE_ARM_V7S ((cpu_subtype_t) 11) /* Swift */
204+
#define CPU_SUBTYPE_ARM_V7K ((cpu_subtype_t) 12) /* Kirkwood40 */
205+
#define CPU_SUBTYPE_ARM_V6M ((cpu_subtype_t) 14) /* Not meant to be run under xnu */
206+
#define CPU_SUBTYPE_ARM_V7M ((cpu_subtype_t) 15) /* Not meant to be run under xnu */
207+
#define CPU_SUBTYPE_ARM_V7EM ((cpu_subtype_t) 16) /* Not meant to be run under xnu */
208+
209+
#define CPU_SUBTYPE_ARM_V8 ((cpu_subtype_t) 13)
210+
211+
/*
212+
* ARM64 subtypes
213+
*/
214+
#define CPU_SUBTYPE_ARM64_ALL ((cpu_subtype_t) 0)
215+
#define CPU_SUBTYPE_ARM64_V8 ((cpu_subtype_t) 1)
216+
192217
#define FAT_MAGIC 0xcafebabe
193218
#define FAT_CIGAM 0xbebafeca /* NXSwapLong(FAT_MAGIC) */
194219

@@ -862,6 +887,18 @@ struct dyld_info_command
862887
uint32_t export_size;
863888
};
864889

890+
/*
891+
* The version_min_command contains the min OS version on which this
892+
* binary was built to run.
893+
*/
894+
struct version_min_command {
895+
uint32_t cmd; /* LC_VERSION_MIN_MACOSX or
896+
LC_VERSION_MIN_IPHONEOS */
897+
uint32_t cmdsize; /* sizeof(struct min_version_command) */
898+
uint32_t version; /* X.Y.Z is encoded in nibbles xxxx.yy.zz */
899+
uint32_t sdk; /* X.Y.Z is encoded in nibbles xxxx.yy.zz */
900+
};
901+
865902
////////////////////////////////////////////////////////////////////////////////
866903

867904
struct mach_32bit
@@ -963,6 +1000,11 @@ static void swap_relocation_info(bool p_to_network, relocation_info& x)
9631000
MCDeployByteSwapRecord(p_to_network, "ll", &x, sizeof(relocation_info));
9641001
}
9651002

1003+
static void swap_version_min_command(bool p_to_network, version_min_command& x)
1004+
{
1005+
MCDeployByteSwapRecord(p_to_network, "llll", &x, sizeof(version_min_command));
1006+
}
1007+
9661008
static void swap_load_command(bool p_to_network, uint32_t p_type, load_command* x)
9671009
{
9681010
switch(p_type)
@@ -998,7 +1040,12 @@ static void swap_load_command(bool p_to_network, uint32_t p_type, load_command*
9981040
swap_load_command_hdr(p_to_network, *x);
9991041
break;
10001042

1001-
default:
1043+
case LC_VERSION_MIN_MACOSX:
1044+
case LC_VERSION_MIN_IPHONEOS:
1045+
swap_version_min_command(p_to_network, *(version_min_command *)x);
1046+
break;
1047+
1048+
default:
10021049
swap_load_command_hdr(p_to_network, *x);
10031050
break;
10041051
}
@@ -1088,6 +1135,55 @@ static void relocate_function_starts_command(linkedit_data_command *x, int32_t p
10881135

10891136
////////////////////////////////////////////////////////////////////////////////
10901137

1138+
static bool MCDeployToMacOSXFetchMinOSVersion(const MCDeployParameters& p_params, mach_header& p_header, uint32_t& r_version)
1139+
{
1140+
// First work out what DeployArchitecture to look for.
1141+
MCDeployArchitecture t_arch;
1142+
if (p_header . cputype == CPU_TYPE_X86)
1143+
t_arch = kMCDeployArchitecture_I386;
1144+
else if (p_header . cputype == CPU_TYPE_X86_64)
1145+
t_arch = kMCDeployArchitecture_X86_64;
1146+
else if (p_header . cputype == CPU_TYPE_ARM && p_header . cpusubtype == CPU_SUBTYPE_ARM_V6)
1147+
t_arch = kMCDeployArchitecture_ARMV6;
1148+
else if (p_header . cputype == CPU_TYPE_ARM && p_header . cpusubtype == CPU_SUBTYPE_ARM_V7)
1149+
t_arch = kMCDeployArchitecture_ARMV7;
1150+
else if (p_header . cputype == CPU_TYPE_ARM && p_header . cpusubtype == CPU_SUBTYPE_ARM_V7S)
1151+
t_arch = kMCDeployArchitecture_ARMV7S;
1152+
else if (p_header . cputype == CPU_TYPE_ARM64)
1153+
t_arch = kMCDeployArchitecture_ARM64;
1154+
else if (p_header . cputype == CPU_TYPE_POWERPC)
1155+
t_arch = kMCDeployArchitecture_PPC;
1156+
else if (p_header . cputype == CPU_TYPE_POWERPC64)
1157+
t_arch = kMCDeployArchitecture_PPC64;
1158+
1159+
// Search for both the architecture in the mach header and for the 'unknown'
1160+
// architecture. If the real arch is found, then we use that version; otherwise
1161+
// if there is an unknown arch then we use that version. If neither are found we
1162+
// return false which means the caller can do nothing.
1163+
int t_unknown_index, t_found_index;
1164+
t_unknown_index = -1;
1165+
t_found_index = -1;
1166+
for(uindex_t i = 0; i < p_params . min_os_version_count; i++)
1167+
if (p_params . min_os_versions[i] . architecture == t_arch &&
1168+
t_found_index == -1)
1169+
t_found_index = (signed)i;
1170+
else if (p_params . min_os_versions[i] . architecture == kMCDeployArchitecture_Unknown &&
1171+
t_unknown_index == -1)
1172+
t_unknown_index = -1;
1173+
1174+
if (t_found_index == -1 && t_unknown_index == -1)
1175+
return false;
1176+
1177+
if (t_found_index == -1)
1178+
{
1179+
r_version = p_params . min_os_versions[t_unknown_index] . version;
1180+
return true;
1181+
}
1182+
1183+
r_version = p_params . min_os_versions[t_found_index] . version;
1184+
return true;
1185+
}
1186+
10911187
template<typename T> bool MCDeployToMacOSXMainBody(const MCDeployParameters& p_params, bool p_big_endian, MCDeployFileRef p_engine, uint32_t p_engine_offset, uint32_t t_engine_size, uint32_t& x_offset, MCDeployFileRef p_output, mach_header& t_header, load_command **t_commands, uint32_t t_command_count)
10921188
{
10931189
bool t_success;
@@ -1240,7 +1336,7 @@ template<typename T> bool MCDeployToMacOSXMainBody(const MCDeployParameters& p_p
12401336
{
12411337
switch(t_commands[i] -> cmd)
12421338
{
1243-
// Relocate the commands we know about that contain file offset
1339+
// Relocate the commands we know about that contain file offset
12441340
case LC_SEGMENT:
12451341
relocate_segment_command<mach_32bit>((segment_command *)t_commands[i], t_file_delta, t_address_delta);
12461342
break;
@@ -1269,7 +1365,7 @@ template<typename T> bool MCDeployToMacOSXMainBody(const MCDeployParameters& p_p
12691365
relocate_function_starts_command((linkedit_data_command *)t_commands[i], t_file_delta, t_address_delta);
12701366
break;
12711367

1272-
// These commands have no file offsets
1368+
// These commands have no file offsets
12731369
case LC_UUID:
12741370
case LC_THREAD:
12751371
case LC_UNIXTHREAD:
@@ -1278,14 +1374,25 @@ template<typename T> bool MCDeployToMacOSXMainBody(const MCDeployParameters& p_p
12781374
case LC_LOAD_DYLINKER:
12791375
case LC_ENCRYPTION_INFO:
12801376
case LC_ENCRYPTION_INFO_64:
1281-
case LC_VERSION_MIN_MACOSX:
1282-
case LC_VERSION_MIN_IPHONEOS:
12831377
case LC_SOURCE_VERSION:
12841378
case LC_MAIN:
12851379
break;
12861380

1287-
// Any others that are present are an error since we don't know
1288-
// what to do with them.
1381+
// We rewrite the contents of these commands as appropriate to
1382+
// the 'min_os_versions' list in the params.
1383+
case LC_VERSION_MIN_MACOSX:
1384+
case LC_VERSION_MIN_IPHONEOS:
1385+
{
1386+
// Notice that we leave the SDK version alone - this is tied
1387+
// to linkage and so is probably unwise to adjust.
1388+
uint32_t t_version;
1389+
if (MCDeployToMacOSXFetchMinOSVersion(p_params, t_header, t_version))
1390+
((version_min_command *)t_commands[i]) -> version = t_version;
1391+
}
1392+
break;
1393+
1394+
// Any others that are present are an error since we don't know
1395+
// what to do with them.
12891396
default:
12901397
t_success = MCDeployThrow(kMCDeployErrorMacOSXUnknownLoadCommand);
12911398
break;

0 commit comments

Comments
 (0)