blob: 122e21f5fa1efcc4d86862ba1bed4ad25a46848c [file] [log] [blame]
//===----------------------------------------------------------------------===//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2017 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//
const char *usage =
" swift-stdlib-tool --print [options...]\n"
" Find and print the Swift libraries required by an app.\n"
"\n"
" swift-stdlib-tool --copy [options...]\n"
" Copy the Swift libraries into an app bundle, and optionally sign them.\n"
"\n"
" --verbose\n"
" Print progress.\n"
" --verbose --verbose\n"
" Print debugging details.\n"
" --help\n"
" Print usage.\n"
"\n"
" Options for lookup:\n"
" --scan-executable <path>\n"
" Scan the executable at <path> for references to Swift libraries.\n"
" This option may be set multiple times.\n"
" --scan-folder <path>\n"
" Scan any executables inside <path> for references to Swift libraries.\n"
" This option may be set multiple times.\n"
" --platform <macosx|iphoneos|iphonesimulator>\n"
" Use the Swift libraries for <platform>.\n"
" --source-libraries <path>\n"
" Search <path> for Swift libraries.\n"
" The default is /path/to/swift-stdlib-tool/../../lib/swift/<platform>/\n"
"\n"
" Options for copying and signing:\n"
" --destination <path>\n"
" Copy Swift libraries into <path>.\n"
" --unsigned-destination <path>\n"
" Copy Swift libraries into <path> without signing them.\n"
" --sign <identity>\n"
" Sign copied Swift libraries using <identity>.\n"
" --keychain <keychain>\n"
" Search <keychain> for the code signing identity.\n"
" --Xcodesign <option>\n"
" Pass <option> to the codesign tool.\n"
" --strip-bitcode\n"
" Remove embedded bitcode from libraries copied to --destination.\n"
" Libraries copied to --unsigned-destination are unmodified.\n"
"\n"
" Options for libraries copied as resources\n"
" --resource-library <library>\n"
" Copy <library> and its dependencies as resources without signing\n"
" them. These copies are in addition to any libraries copied as a result\n"
" of the --scan-executable option.\n"
" Any library in the Swift library search path can be specified for\n"
" <library>.\n"
" This option may be set multiple times.\n"
" --resource-destination <path>\n"
" The <path> to copy Swift resource libraries to.\n"
"\n"
;
#include <sys/types.h>
#include <sys/uio.h>
#include <sys/stat.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <fcntl.h>
#include <os/overflow.h>
#include <mach-o/fat.h>
#include <mach-o/dyld.h>
#include <mach-o/loader.h>
#include <libkern/OSByteOrder.h>
#include <algorithm>
using namespace std;
#include <Foundation/Foundation.h>
#pragma clang diagnostic ignored "-Wgcc-compat"
#ifndef CPU_TYPE_ARM64
# define CPU_TYPE_ARM64 ((cpu_type_t) (CPU_TYPE_ARM | CPU_ARCH_ABI64))
#endif
static int Verbose = 0;
#ifdef __OPTIMIZE__
#define INLINE __attribute__((always_inline))
#else
#define INLINE
#endif
//
// This abstraction layer is for use with file formats that have 64-bit/32-bit and Big-Endian/Little-Endian variants
//
// For example: to make a utility that handles 32-bit little enidan files use: Pointer32<LittleEndian>
//
//
// get16() read a 16-bit number from an E endian struct
// set16() write a 16-bit number to an E endian struct
// get32() read a 32-bit number from an E endian struct
// set32() write a 32-bit number to an E endian struct
// get64() read a 64-bit number from an E endian struct
// set64() write a 64-bit number to an E endian struct
//
// getBits() read a bit field from an E endian struct (bitCount=number of bits in field, firstBit=bit index of field)
// setBits() write a bit field to an E endian struct (bitCount=number of bits in field, firstBit=bit index of field)
//
// getBitsRaw() read a bit field from a struct with native endianness
// setBitsRaw() write a bit field from a struct with native endianness
//
class BigEndian
{
public:
static uint16_t get16(const uint16_t& from) INLINE { return OSReadBigInt16(&from, 0); }
static void set16(uint16_t& into, uint16_t value) INLINE { OSWriteBigInt16(&into, 0, value); }
static uint32_t get32(const uint32_t& from) INLINE { return OSReadBigInt32(&from, 0); }
static void set32(uint32_t& into, uint32_t value) INLINE { OSWriteBigInt32(&into, 0, value); }
static uint64_t get64(const uint64_t& from) INLINE { return OSReadBigInt64(&from, 0); }
static void set64(uint64_t& into, uint64_t value) INLINE { OSWriteBigInt64(&into, 0, value); }
static uint32_t getBits(const uint32_t& from,
uint8_t firstBit, uint8_t bitCount) INLINE { return getBitsRaw(get32(from), firstBit, bitCount); }
static void setBits(uint32_t& into, uint32_t value,
uint8_t firstBit, uint8_t bitCount) INLINE { uint32_t temp = get32(into); setBitsRaw(temp, value, firstBit, bitCount); set32(into, temp); }
static uint32_t getBitsRaw(const uint32_t& from,
uint8_t firstBit, uint8_t bitCount) INLINE { return ((from >> (32-firstBit-bitCount)) & ((1<<bitCount)-1)); }
static void setBitsRaw(uint32_t& into, uint32_t value,
uint8_t firstBit, uint8_t bitCount) INLINE { uint32_t temp = into;
const uint32_t mask = ((1<<bitCount)-1);
temp &= ~(mask << (32-firstBit-bitCount));
temp |= ((value & mask) << (32-firstBit-bitCount));
into = temp; }
enum { little_endian = 0 };
};
class LittleEndian
{
public:
static uint16_t get16(const uint16_t& from) INLINE { return OSReadLittleInt16(&from, 0); }
static void set16(uint16_t& into, uint16_t value) INLINE { OSWriteLittleInt16(&into, 0, value); }
static uint32_t get32(const uint32_t& from) INLINE { return OSReadLittleInt32(&from, 0); }
static void set32(uint32_t& into, uint32_t value) INLINE { OSWriteLittleInt32(&into, 0, value); }
static uint64_t get64(const uint64_t& from) INLINE { return OSReadLittleInt64(&from, 0); }
static void set64(uint64_t& into, uint64_t value) INLINE { OSWriteLittleInt64(&into, 0, value); }
static uint32_t getBits(const uint32_t& from,
uint8_t firstBit, uint8_t bitCount) INLINE { return getBitsRaw(get32(from), firstBit, bitCount); }
static void setBits(uint32_t& into, uint32_t value,
uint8_t firstBit, uint8_t bitCount) INLINE { uint32_t temp = get32(into); setBitsRaw(temp, value, firstBit, bitCount); set32(into, temp); }
static uint32_t getBitsRaw(const uint32_t& from,
uint8_t firstBit, uint8_t bitCount) INLINE { return ((from >> firstBit) & ((1<<bitCount)-1)); }
static void setBitsRaw(uint32_t& into, uint32_t value,
uint8_t firstBit, uint8_t bitCount) INLINE { uint32_t temp = into;
const uint32_t mask = ((1<<bitCount)-1);
temp &= ~(mask << firstBit);
temp |= ((value & mask) << firstBit);
into = temp; }
enum { little_endian = 1 };
};
#if __BIG_ENDIAN__
typedef BigEndian CurrentEndian;
typedef LittleEndian OtherEndian;
#elif __LITTLE_ENDIAN__
typedef LittleEndian CurrentEndian;
typedef BigEndian OtherEndian;
#else
#error unknown endianness
#endif
template <typename _E>
class Pointer32
{
public:
typedef uint32_t uint_t;
typedef int32_t sint_t;
typedef _E E;
static uint64_t getP(const uint_t& from) INLINE { return _E::get32(from); }
static void setP(uint_t& into, uint64_t value) INLINE { _E::set32(into, value); }
};
template <typename _E>
class Pointer64
{
public:
typedef uint64_t uint_t;
typedef int64_t sint_t;
typedef _E E;
static uint64_t getP(const uint_t& from) INLINE { return _E::get64(from); }
static void setP(uint_t& into, uint64_t value) INLINE { _E::set64(into, value); }
};
//
// mach-o file header
//
template <typename P> struct macho_header_content {};
template <> struct macho_header_content<Pointer32<BigEndian> > { mach_header fields; };
template <> struct macho_header_content<Pointer64<BigEndian> > { mach_header_64 fields; };
template <> struct macho_header_content<Pointer32<LittleEndian> > { mach_header fields; };
template <> struct macho_header_content<Pointer64<LittleEndian> > { mach_header_64 fields; };
template <typename P>
class macho_header {
public:
uint32_t magic() const INLINE { return E::get32(header.fields.magic); }
void set_magic(uint32_t value) INLINE { E::set32(header.fields.magic, value); }
uint32_t cputype() const INLINE { return E::get32(header.fields.cputype); }
void set_cputype(uint32_t value) INLINE { E::set32((uint32_t&)header.fields.cputype, value); }
uint32_t cpusubtype() const INLINE { return E::get32(header.fields.cpusubtype); }
void set_cpusubtype(uint32_t value) INLINE { E::set32((uint32_t&)header.fields.cpusubtype, value); }
uint32_t filetype() const INLINE { return E::get32(header.fields.filetype); }
void set_filetype(uint32_t value) INLINE { E::set32(header.fields.filetype, value); }
uint32_t ncmds() const INLINE { return E::get32(header.fields.ncmds); }
void set_ncmds(uint32_t value) INLINE { E::set32(header.fields.ncmds, value); }
uint32_t sizeofcmds() const INLINE { return E::get32(header.fields.sizeofcmds); }
void set_sizeofcmds(uint32_t value) INLINE { E::set32(header.fields.sizeofcmds, value); }
uint32_t flags() const INLINE { return E::get32(header.fields.flags); }
void set_flags(uint32_t value) INLINE { E::set32(header.fields.flags, value); }
uint32_t reserved() const INLINE { return E::get32(header.fields.reserved); }
void set_reserved(uint32_t value) INLINE { E::set32(header.fields.reserved, value); }
typedef typename P::E E;
private:
macho_header_content<P> header;
};
//
// mach-o load command
//
template <typename P>
class macho_load_command {
public:
uint32_t cmd() const INLINE { return E::get32(command.cmd); }
void set_cmd(uint32_t value) INLINE { E::set32(command.cmd, value); }
uint32_t cmdsize() const INLINE { return E::get32(command.cmdsize); }
void set_cmdsize(uint32_t value) INLINE { E::set32(command.cmdsize, value); }
typedef typename P::E E;
private:
load_command command;
};
//
// mach-o uuid load command
//
template <typename P>
class macho_uuid_command {
public:
uint32_t cmd() const INLINE { return E::get32(fields.cmd); }
void set_cmd(uint32_t value) INLINE { E::set32(fields.cmd, value); }
uint32_t cmdsize() const INLINE { return E::get32(fields.cmdsize); }
void set_cmdsize(uint32_t value) INLINE { E::set32(fields.cmdsize, value); }
const uint8_t* uuid() const INLINE { return fields.uuid; }
void set_uuid(uint8_t value[16]) INLINE { memcpy(&fields.uuid, value, 16); }
typedef typename P::E E;
private:
uuid_command fields;
};
//
// mach-o dylib load command
//
template <typename P>
class macho_dylib_command {
public:
uint32_t cmd() const INLINE { return E::get32(fields.cmd); }
void set_cmd(uint32_t value) INLINE { E::set32(fields.cmd, value); }
uint32_t cmdsize() const INLINE { return E::get32(fields.cmdsize); }
void set_cmdsize(uint32_t value) INLINE { E::set32(fields.cmdsize, value); }
uint32_t name_offset() const INLINE { return E::get32(fields.dylib.name.offset); }
void set_name_offset(uint32_t value) INLINE { E::set32(fields.dylib.name.offset, value); }
uint32_t timestamp() const INLINE { return E::get32(fields.dylib.timestamp); }
void set_timestamp(uint32_t value) INLINE { E::set32(fields.dylib.timestamp, value); }
uint32_t current_version() const INLINE { return E::get32(fields.dylib.current_version); }
void set_current_version(uint32_t value) INLINE { E::set32(fields.dylib.current_version, value); }
uint32_t compatibility_version() const INLINE { return E::get32(fields.dylib.compatibility_version); }
void set_compatibility_version(uint32_t value) INLINE { E::set32(fields.dylib.compatibility_version, value); }
const char* name() const INLINE { return (const char*)&fields + name_offset(); }
void set_name_offset() INLINE { set_name_offset(sizeof(fields)); }
typedef typename P::E E;
private:
dylib_command fields;
};
// Print everything to standard output.
// Mixing stdout and stderr looks bad when the output is reprinted by Xcode.
void printUsage()
{
fprintf(stdout, "%s", usage);
}
void fail(const char *msg, ...)
__attribute__((format(printf, 1, 2))) __attribute__((noreturn))
{
va_list args;
va_start(args, msg);
char *msg2;
asprintf(&msg2, "*** error: %s\n", msg);
vfprintf(stdout, msg2, args);
exit(1);
}
void fail_errno(const char *msg, ...)
__attribute__((format(printf, 1, 2))) __attribute__((noreturn))
{
va_list args;
va_start(args, msg);
char *msg2;
asprintf(&msg2, "*** error: %s: %s\n", msg, strerror(errno));
vfprintf(stdout, msg2, args);
exit(1);
}
void fail_usage(const char *msg, ...)
__attribute__((format(printf, 1, 2))) __attribute__((noreturn))
{
va_list args;
va_start(args, msg);
char *msg2;
asprintf(&msg2, "*** error: %s\n\n", msg);
vfprintf(stdout, msg2, args);
printUsage();
exit(1);
}
void log_vn(int verbosity, const char *msg, va_list args)
__attribute__((format(printf, 2, 0)))
{
if (verbosity <= Verbose) {
char *msg2;
asprintf(&msg2, "%s\n", msg);
vfprintf(stdout, msg2, args);
free(msg2);
}
}
int log_v(const char *msg, ...)
__attribute__((format(printf, 1, 2)))
{
va_list args;
va_start(args, msg);
log_vn(1, msg, args);
return -1;
}
int log_vv(const char *msg, ...)
__attribute__((format(printf, 1, 2)))
{
va_list args;
va_start(args, msg);
log_vn(2, msg, args);
return -1;
}
ssize_t pread_all(int fd, void *buf, size_t count, off_t offset)
{
size_t total = 0;
while (total < count) {
ssize_t readed = pread(fd, (void *)((char *)buf+total),
count-total, offset+total);
if (readed > 0) total += readed; // got data
else if (readed == 0) return total; // EOF: done
else if (readed == -1 && errno != EINTR) return -1;
// error but not EINTR: fail
}
return total;
}
template <typename T>
int parse_macho(int fd, uint32_t offset, uint32_t size,
void (^dylibVisitor)(NSString *path),
void (^uuidVisitor)(NSUUID *uuid))
{
ssize_t readed;
macho_header<T> mh;
if (size < sizeof(mh)) return log_vv("file is too small");
readed = pread_all(fd, &mh, sizeof(mh), offset);
if (readed != sizeof(mh)) return log_vv("pread failed");
uint32_t sizeofcmds = mh.sizeofcmds();
size -= sizeof(mh);
offset += sizeof(mh);
if (size < sizeofcmds) return log_vv("file is badly formed");
uint8_t *cmdp = (uint8_t *)malloc(sizeofcmds);
if (!cmdp) return log_vv("malloc(sizeofcmds) failed");
readed = pread_all(fd, cmdp, sizeofcmds, offset);
if (readed == sizeofcmds) {
uint8_t *cmds = cmdp;
for (uint32_t c = 0; c < mh.ncmds(); c++) {
macho_load_command<T> *cmd;
if (size < sizeof(*cmd)) return log_vv("file is badly formed");
cmd = (macho_load_command<T>*) cmds;
if (size < cmd->cmdsize()) return log_vv("file is badly formed");
cmds += cmd->cmdsize();
size -= cmd->cmdsize();
if (dylibVisitor &&
(cmd->cmd() == LC_LOAD_DYLIB ||
cmd->cmd() == LC_LOAD_WEAK_DYLIB ||
cmd->cmd() == LC_LAZY_LOAD_DYLIB))
{
macho_dylib_command<T>* dylib = (macho_dylib_command<T>*)cmd;
if (dylib->cmdsize() < dylib->name_offset()) continue;
char *name = (char *)dylib + dylib->name_offset();
size_t name_len =
strnlen(name, dylib->cmdsize() - dylib->name_offset());
log_vv(" loads %.*s", (int)name_len, name);
#define PREPREFIX "@rpath/"
#define PREFIX PREPREFIX "libswift"
if (0 == strncmp(name, PREFIX, strlen(PREFIX))) {
NSString *nsname =
[[[[NSString alloc] initWithBytes:name length:name_len
encoding:NSUTF8StringEncoding] autorelease]
substringFromIndex:strlen(PREPREFIX)];
if (nsname) dylibVisitor(nsname);
}
}
else if (uuidVisitor && cmd->cmd() == LC_UUID) {
macho_uuid_command<T>* uuid_cmd = (macho_uuid_command<T>*)cmd;
if (uuid_cmd->cmdsize() < sizeof(uuid_command)) continue;
CFUUIDBytes uuid_bytes = *(CFUUIDBytes *)uuid_cmd->uuid();
NSUUID *uuid = (NSUUID *)
CFUUIDCreateFromUUIDBytes(nil, uuid_bytes);
uuidVisitor(uuid);
[uuid release];
}
}
}
free(cmdp);
return 0;
}
int parse_macho(int fd, uint32_t offset, uint32_t size,
void (^dylibVisitor)(NSString *path),
void (^uuidVisitor)(NSUUID *uuid))
{
uint32_t magic;
if (size < sizeof(magic)) return log_vv("file is too small");
ssize_t readed = pread_all(fd, &magic, sizeof(magic), offset);
if (readed != sizeof(magic)) return log_vv("pread failed");
switch (magic) {
case MH_MAGIC_64:
return parse_macho<Pointer64<CurrentEndian>>(fd, offset, size,
dylibVisitor, uuidVisitor);
case MH_MAGIC:
return parse_macho<Pointer32<CurrentEndian>>(fd, offset, size,
dylibVisitor, uuidVisitor);
case MH_CIGAM_64:
return parse_macho<Pointer64<OtherEndian>>(fd, offset, size,
dylibVisitor, uuidVisitor);
case MH_CIGAM:
return parse_macho<Pointer32<OtherEndian>>(fd, offset, size,
dylibVisitor, uuidVisitor);
default:
return log_vv("file is not mach-o");
}
}
int parse_fat(int fd, off_t fsize, char *buffer, size_t size,
void (^dylibVisitor)(NSString *path),
void (^uuidVisitor)(NSUUID *uuid))
{
uint32_t magic;
if (size < sizeof(magic)) {
return log_vv("file is too small");
}
magic = *(uint32_t *)buffer;
if (magic == FAT_MAGIC || magic == FAT_CIGAM) {
struct fat_header *fh;
uint32_t fat_magic, fat_nfat_arch;
struct fat_arch *archs;
uint32_t i;
if (size < sizeof(struct fat_header)) {
return log_vv("file is too small");
}
fh = (struct fat_header *)buffer;
fat_magic = OSSwapBigToHostInt32(fh->magic);
fat_nfat_arch = OSSwapBigToHostInt32(fh->nfat_arch);
size_t fat_arch_size;
// fat_nfat_arch * sizeof(struct fat_arch) + sizeof(struct fat_header)
if (os_mul_and_add_overflow(fat_nfat_arch, sizeof(struct fat_arch),
sizeof(struct fat_header), &fat_arch_size))
{
return log_vv("too many fat archs\n");
}
if (size < fat_arch_size) {
return log_vv("file is too small");
}
archs = (struct fat_arch *)(buffer + sizeof(struct fat_header));
/* Special case hidden CPU_TYPE_ARM64 */
size_t fat_arch_plus_one_size;
if (os_add_overflow(fat_arch_size, sizeof(struct fat_arch),
&fat_arch_plus_one_size))
{
return log_vv("too many fat archs\n");
}
if (size >= fat_arch_plus_one_size) {
if (fat_nfat_arch > 0
&& OSSwapBigToHostInt32(archs[fat_nfat_arch].cputype) == CPU_TYPE_ARM64) {
fat_nfat_arch++;
}
}
/* End special case hidden CPU_TYPE_ARM64 */
for (i=0; i < fat_nfat_arch; i++) {
int ret;
uint32_t arch_cputype, arch_cpusubtype, arch_offset, arch_size, arch_align;
arch_cputype = OSSwapBigToHostInt32(archs[i].cputype);
arch_cpusubtype = OSSwapBigToHostInt32(archs[i].cpusubtype);
arch_offset = OSSwapBigToHostInt32(archs[i].offset);
arch_size = OSSwapBigToHostInt32(archs[i].size);
arch_align = OSSwapBigToHostInt32(archs[i].align);
/* Check that slice data is after all fat headers and archs */
if (arch_offset < fat_arch_size) {
return log_vv("file is badly formed");
}
/* Check that the slice ends before the file does */
if (arch_offset > fsize) {
return log_vv("file is badly formed");
}
if (arch_size > fsize) {
return log_vv("file is badly formed");
}
if (arch_offset > (fsize - arch_size)) {
return log_vv("file is badly formed");
}
ret = parse_macho(fd, arch_offset, arch_size,
dylibVisitor, uuidVisitor);
if (ret != 0) {
return ret;
}
}
return 0;
} else {
/* Not a fat file */
return parse_macho(fd, 0, fsize, dylibVisitor, uuidVisitor);
}
}
void process(NSString *path, void(^dylibVisitor)(NSString *),
void (^uuidVisitor)(NSUUID *))
{
log_vv("Scanning %s...", path.fileSystemRepresentation);
int fd = open(path.fileSystemRepresentation, O_RDONLY);
if (fd < 0) log_vv("%s: open failed: %s",
path.fileSystemRepresentation, strerror(errno));
struct stat st;
if (fstat(fd, &st) < 0) {
log_vv("%s: stat failed: %s",
path.fileSystemRepresentation, strerror(errno));
} else {
const int len = 4096;
char buf[len];
ssize_t readed = pread_all(fd, buf, len, 0);
if (readed != len) {
log_vv("%s: pread failed: %s",
path.fileSystemRepresentation, strerror(errno));
} else {
parse_fat(fd, st.st_size, buf, len, dylibVisitor, uuidVisitor);
}
}
close(fd);
}
@implementation NSString (sst)
-(NSString *)sst_stringByAppendingPathComponents:(NSArray *)components
{
NSString *result = self;
@autoreleasepool {
for (NSString *component in components) {
result = [result stringByAppendingPathComponent:component];
}
[result retain];
}
return [result autorelease];
}
@end
@implementation NSTask (sst)
-(NSString *)sst_command {
NSMutableString *command = [self.launchPath mutableCopy];
for (NSString *arg in self.arguments) {
[command appendFormat:@" '%@'", arg];
}
return command;
}
@end
bool operator <= (const struct timespec &lhs, const struct timespec &rhs)
{
if (lhs.tv_sec == rhs.tv_sec) return lhs.tv_nsec <= rhs.tv_nsec;
return lhs.tv_sec <= rhs.tv_sec;
}
// This executable's own path.
NSString *self_executable = []() -> NSString * {
uint32_t len = 0;
_NSGetExecutablePath(nil, &len);
char buf[len];
_NSGetExecutablePath(buf, &len);
return [[NSString alloc] initWithUTF8String:buf];
}();
// This executable's own xctoolchain path.
NSString *self_toolchain = []() -> NSString * {
@autoreleasepool {
NSString *result = self_executable;
// Remove the executable name.
result = result.stringByDeletingLastPathComponent;
// Remove trailing /usr/bin, if any
if ([result.lastPathComponent isEqualToString:@"bin"]) {
result = result.stringByDeletingLastPathComponent;
}
if ([result.lastPathComponent isEqualToString:@"usr"]) {
result = result.stringByDeletingLastPathComponent;
}
return [result retain];
}
}();
// Runs a tool with `xcrun`.
// Returns NSTask.terminationStatus.
// Prints the tool's command line if we are verbose.
// Prints the tool's stdout and stderr if terminationStatus is non-zero
// or if we are very verbose.
typedef void (^XcrunToolBlock)(NSData *stdOutData, NSData *stdErrorData, int err);
int xcrunToolCommand(NSArray *commandAndArguments, XcrunToolBlock block = 0)
{
@autoreleasepool {
NSTask *task = [[[NSTask alloc] init] autorelease];
task.launchPath = @"/usr/bin/xcrun";
// Tell xcrun to search our toolchain first.
NSMutableArray *arguments =
[[commandAndArguments mutableCopy] autorelease];
[arguments insertObject:@"--toolchain" atIndex:0];
[arguments insertObject:self_toolchain atIndex:1];
// Tell xcrun to print its command if we are very verbose.
if (Verbose > 1) {
[arguments insertObject:@"--log" atIndex:2];
}
task.arguments = arguments;
NSPipe *outPipe = [NSPipe pipe];
NSPipe *errPipe = [NSPipe pipe];
task.standardOutput = outPipe;
task.standardError = errPipe;
log_v(" %s", task.sst_command.fileSystemRepresentation);
[task launch];
// Read stdout and stderr in parallel, then wait for the task
// to exit. Anything else risks deadlock if the task fills
// one of the output buffers.
__block NSData *stdErrData = nil;
dispatch_semaphore_t gotStdErr = dispatch_semaphore_create(0);
dispatch_queue_t concurrentQueue =
dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(concurrentQueue, ^{
stdErrData =
[[errPipe.fileHandleForReading readDataToEndOfFile] retain];
dispatch_semaphore_signal(gotStdErr);
});
NSData *stdOutData = [outPipe.fileHandleForReading readDataToEndOfFile];
dispatch_semaphore_wait(gotStdErr, DISPATCH_TIME_FOREVER);
dispatch_release(gotStdErr);
[stdErrData autorelease];
[task waitUntilExit];
// Task is finished and we have its stdout and stderr output.
// Print its stdout and stderr if it failed or we are verbose.
// (Print nothing by default because codesign is noisy.)
int err = task.terminationStatus;
if (err || Verbose > 1) {
fwrite(stdErrData.bytes, stdErrData.length, 1, stdout);
fwrite(stdOutData.bytes, stdOutData.length, 1, stdout);
}
if (block) { block(stdOutData, stdErrData, err); }
return err;
}
}
void copyAndStripBitcode(NSString *src, NSString *dst)
{
// -r removes bitcode
int err = xcrunToolCommand(@[@"bitcode_strip", src, @"-r", @"-o", dst]);
// Fail if bitcode_strip failed.
if (err) {
fail("Couldn't copy and strip bitcode %s to %s: bitcode_strip failed "
"with exit code %d",
src.fileSystemRepresentation, dst.fileSystemRepresentation, err);
}
}
void
copyFile(NSFileManager *fm, NSString *src, NSString *dst, bool stripBitcode)
{
if (stripBitcode) {
copyAndStripBitcode(src, dst);
} else {
NSError *nserr;
if (![fm copyItemAtPath:src toPath:dst error:&nserr]) {
fail("Couldn't copy %s to %s: %s",
src.fileSystemRepresentation,
dst.fileSystemRepresentation,
nserr.localizedFailureReason.UTF8String);
}
}
}
void copyLibraries(NSString *src_dir, NSString *dst_dir,
NSMutableDictionary *libs, bool stripBitcode)
{
NSFileManager *fm = NSFileManager.defaultManager;
[fm createDirectoryAtPath: dst_dir withIntermediateDirectories:YES
attributes:nil error:nil];
for (NSString *lib in libs) @autoreleasepool {
NSString *src = [src_dir stringByAppendingPathComponent:lib];
NSString *dst = [dst_dir stringByAppendingPathComponent:lib];
// Compare UUIDs of src and dst and don't copy if they're the same.
// Do not use mod times for this task: the dst copy gets code-signed
// and bitcode-stripped so it can look newer than it really is.
NSSet *srcUUIDs = libs[lib];
NSMutableSet *dstUUIDs = [NSMutableSet set];
process(dst, nil, ^(NSUUID *uuid) {
[dstUUIDs addObject:uuid];
});
log_vv("Source UUIDs %s: %s", src.fileSystemRepresentation,
srcUUIDs.description.UTF8String);
log_vv("Destination UUIDs %s: %s", dst.fileSystemRepresentation,
dstUUIDs.description.UTF8String);
if ([srcUUIDs isEqualToSet:dstUUIDs]) {
log_v("%s is up to date at %s",
lib.fileSystemRepresentation, dst.fileSystemRepresentation);
continue;
}
// Perform the copy.
log_v("Copying %s from %s to %s",
lib.fileSystemRepresentation,
src_dir.fileSystemRepresentation,
dst_dir.fileSystemRepresentation);
[fm removeItemAtPath:dst error:nil]; // fixme report this err?
copyFile(fm, src.stringByResolvingSymlinksInPath, dst, stripBitcode);
}
}
NSString *relative_path(NSString *base, NSString *suffix_name)
{
NSDictionary *env = NSProcessInfo.processInfo.environment;
NSString *suffix = env[suffix_name];
if (base && suffix) {
return [base stringByAppendingPathComponent:suffix];
}
return nil;
}
void add_relative_path(NSMutableArray *paths,
NSString *base, NSString *suffix_name)
{
NSString *path = relative_path(base, suffix_name);
if (path) [paths addObject:path];
}
NSData *query_code_signature(NSString *file) {
__block NSData *d = 0;
@autoreleasepool {
NSArray *command = @[ @"codesign", @"-r-", @"--display", file];
log_v("Probing signature of %s", file.fileSystemRepresentation);
int err = xcrunToolCommand(command,
^(NSData *stdOutData, NSData *stdErrData, int err) {
if (!err) { d = [stdOutData retain]; }
});
if (err) { return 0; }
}
return [d autorelease];
}
int main(int argc, const char *argv[])
{
@autoreleasepool {
NSFileManager *fm = [NSFileManager defaultManager];
// Exxecutables to scan for Swift references.
// --scan-executable
NSMutableArray *executables = [NSMutableArray array];
// Directories to scan for more executables.
// --scan-folder
NSMutableArray *embedDirs = [NSMutableArray array];
// Platform name.
// --platform
// or the last path component of --source-libraries
NSString *platform = nil;
// Copy source.
// --source-libraries
// or /path/to/swift-stdlib-tool/../../lib/swift/<--platform>
NSString *src_dir = nil;
// Copy destinations, signed and unsigned.
// --destination and --unsigned-destination
NSString *dst_dir = nil;
NSString *unsigned_dst_dir = nil;
// Resource copy destination.
// --resource-destination
NSString *resource_dst_dir = nil;
// Resource libraries.
// --resource-library
NSMutableArray *resourceLibraries = [NSMutableArray new];
// Code signing options.
NSString *ident = nil;
NSString *keychain = nil;
NSMutableArray *otherCodesignFlags = [NSMutableArray array];
// Read arguments
bool print = false;
bool copy = false;
bool stripBitcode = false;
for (int i = 1; i < argc; i++) {
if (0 == strcmp(argv[i], "--print")) print = true;
if (0 == strcmp(argv[i], "--copy")) copy = true;
if (0 == strcmp(argv[i], "--verbose")) Verbose++;
if (0 == strcmp(argv[i], "--help")) {
printUsage();
exit(0);
}
if (0 == strcmp(argv[i], "--scan-executable")) {
[executables addObject:[NSString stringWithUTF8String:argv[++i]]];
}
if (0 == strcmp(argv[i], "--scan-folder")) {
[embedDirs addObject:[NSString stringWithUTF8String:argv[++i]]];
}
if (0 == strcmp(argv[i], "--source-libraries")) {
src_dir = [NSString stringWithUTF8String:argv[++i]];
}
if (0 == strcmp(argv[i], "--platform")) {
platform = [NSString stringWithUTF8String:argv[++i]];
}
if (0 == strcmp(argv[i], "--destination")) {
dst_dir = [NSString stringWithUTF8String:argv[++i]];
}
if (0 == strcmp(argv[i], "--unsigned-destination")) {
unsigned_dst_dir = [NSString stringWithUTF8String:argv[++i]];
}
if (0 == strcmp(argv[i], "--sign")) {
ident = [NSString stringWithUTF8String:argv[++i]];
}
if (0 == strcmp(argv[i], "--keychain")) {
keychain = [NSString stringWithUTF8String:argv[++i]];
}
if (0 == strcmp(argv[i], "--Xcodesign")) {
[otherCodesignFlags addObject:
[NSString stringWithUTF8String:argv[++i]]];
}
if (0 == strcmp(argv[i], "--strip-bitcode")) {
stripBitcode = true;
}
if (0 == strcmp(argv[i], "--resource-destination")) {
resource_dst_dir = [NSString stringWithUTF8String:argv[++i]];
}
if (0 == strcmp(argv[i], "--resource-library")) {
[resourceLibraries addObject:[NSString stringWithUTF8String:argv[++i]]];
}
}
// Fix up src_dir and platform values.
if (!src_dir && !platform) {
// Neither src_dir nor platform is set. Die.
fail_usage("At least one of --source-libraries and --platform "
"must be set.");
}
else if (!src_dir) {
// platform is set but src_dir is not.
// Use platform to set src_dir relative to us.
src_dir = [[[self_executable stringByDeletingLastPathComponent]
stringByDeletingLastPathComponent]
sst_stringByAppendingPathComponents:
@[ @"lib", @"swift", platform ]];
} else if (!platform) {
// src_dir is set but platform is not.
// Pick platform from src_dir's name.
platform = src_dir.lastPathComponent;
}
// Add the platform to unsigned_dst_dir if it is not already present.
if (unsigned_dst_dir) {
NSString *unsigned_platform = unsigned_dst_dir.lastPathComponent;
if (![platform isEqualToString:unsigned_platform]) {
unsigned_dst_dir =
[unsigned_dst_dir stringByAppendingPathComponent:platform];
}
}
// If the user specifies --strip-bitcode but not --sign, this
// will cause the dylibs to get copied, stripped, but not resigned.
// This will cause apps to fail to launch because the code signature
// is invalid. In this case, ignore --strip-bitcode.
if (stripBitcode && !ident) {
stripBitcode = false;
}
// Collect executables from the --scan-folder locations.
for (NSString *embedDir in embedDirs) @autoreleasepool {
NSDirectoryEnumerator *dir = [fm enumeratorAtPath:embedDir];
for (NSString *p in dir) @autoreleasepool {
BOOL isDir;
NSString *p2 = [embedDir stringByAppendingPathComponent:p];
if ([fm fileExistsAtPath:p2 isDirectory:&isDir] &&
!isDir &&
[fm isExecutableFileAtPath:p2])
{
[executables addObject:p2];
} else {
log_vv("%s is not an executable file",
p2.fileSystemRepresentation);
}
}
}
// Collect Swift library names from the input files.
// If the library does not exist in src_dir then assume the user wrote
// their own library named libswift* and is handling it elsewhere.
NSMutableDictionary *swiftLibs = [NSMutableDictionary new];
for (NSString *path in executables) @autoreleasepool {
process(path,
^(NSString *linkedLib) {
@autoreleasepool {
NSString *linkedSrc =
[src_dir stringByAppendingPathComponent:linkedLib];
if ([fm fileExistsAtPath:linkedSrc]) {
swiftLibs[linkedLib] = [NSMutableSet set];
}
}
},
nil);
}
// Collect more Swift library names from the Swift libraries themselves.
// Also collect the Swift libraries' UUIDs.
NSMutableArray *worklist = [swiftLibs.allKeys mutableCopy];
while (worklist.count) @autoreleasepool {
NSString *lib = [worklist lastObject];
[worklist removeLastObject];
NSString *path = [src_dir stringByAppendingPathComponent:lib];
process(path,
^(NSString *linkedLib) {
@autoreleasepool {
NSString *linkedSrc =
[src_dir stringByAppendingPathComponent:linkedLib];
if (!swiftLibs[linkedLib] &&
[fm fileExistsAtPath:linkedSrc])
{
swiftLibs[linkedLib] = [NSMutableSet set];
[worklist addObject:linkedLib];
}
}
},
^(NSUUID *uuid) {
NSMutableSet *uuids = swiftLibs[lib];
[uuids addObject:uuid];
});
}
[worklist release];
// Collect all the Swift libraries that the user requested
// with --resource-library.
NSMutableDictionary *swiftLibsForResources = [NSMutableDictionary new];
for (NSString *lib in resourceLibraries) @autoreleasepool {
NSString *libSrc = [src_dir stringByAppendingPathComponent:lib];
if ([fm fileExistsAtPath:libSrc]) {
swiftLibsForResources[lib] = [NSMutableSet set];
}
}
// Collect dependencies of --resource-library libs.
worklist = [swiftLibsForResources.allKeys mutableCopy];
while (worklist.count) @autoreleasepool {
NSString *lib = [worklist lastObject];
[worklist removeLastObject];
NSString *path = [src_dir stringByAppendingPathComponent:lib];
process(path,
^(NSString *linkedLib) {
NSString *linkedSrc =
[src_dir stringByAppendingPathComponent:linkedLib];
if (!swiftLibsForResources[linkedLib] &&
[fm fileExistsAtPath:linkedSrc])
{
swiftLibsForResources[linkedLib] = [NSMutableSet set];
[worklist addObject:linkedLib];
}
},
^(NSUUID *uuid) {
NSMutableSet *uuids = swiftLibsForResources[lib];
[uuids addObject:uuid];
});
}
[worklist release];
// Print the Swift libraries (full path to toolchain's copy)
if (print) {
for (NSString *lib in swiftLibs) {
printf("%s\n", [[src_dir stringByAppendingPathComponent:lib]
fileSystemRepresentation]);
}
}
// Copy the Swift libraries to $build_dir/$frameworks
// and $build_dir/$unsigned_frameworks
if (copy) {
copyLibraries(src_dir, dst_dir, swiftLibs, stripBitcode);
if (unsigned_dst_dir) {
// Never strip bitcode from the unsigned libraries.
// Their existing signatures must be preserved.
copyLibraries(src_dir, unsigned_dst_dir, swiftLibs, false);
}
if (resource_dst_dir) {
// Never strip bitcode from resources libraries, for
// the same reason as the libraries copied to
// unsigned_dst_dir.
copyLibraries(src_dir, resource_dst_dir, swiftLibsForResources, false);
}
}
// Codesign the Swift libraries in $build_dir/$frameworks
// but not the libraries in $build_dir/$unsigned_frameworks.
if (ident) {
// Swift libraries that are up-to-date get codesigned anyway
// (in case options changed or a previous build was incomplete).
// We do employ an optimization, however, if resigning the dylib
// results in getting the same signing identity and credentials
// then we keep the original file to optimize for delta updates
// to the device.
__block bool signedOne = false;
NSLock *signingLock = [NSLock new];
[swiftLibs enumerateKeysAndObjectsWithOptions: NSEnumerationConcurrent usingBlock: ^(id key, id value, BOOL *stop) {
// Work around authentication UI problems
// by signing one synchronously and then signing the rest.
[signingLock lock];
if (signedOne) {
// First signer is complete. Proceed concurrently.
[signingLock unlock];
} else {
// We are the first signer. Hold the lock until we finish.
}
NSString *lib = key;
NSString *dst = [dst_dir stringByAppendingPathComponent:lib];
// Get the code signature, and copy the dylib to the side
// to preserve it in case it does not change. We can use
// this to avoid unnecessary copies during delta installs
// to devices.
NSData *oldSignatureData = query_code_signature(dst);
const char *tmpFilePath = 0;
if (oldSignatureData) {
// Make a copy of the existing file, with permissions and
// mtime preserved.
NSString *tmpFile = [dst stringByAppendingPathExtension:@"original"];
tmpFilePath = tmpFile.fileSystemRepresentation;
xcrunToolCommand(@[@"cp", @"-p", dst, tmpFile]);
}
// Proceed with (re-)codesigning.
log_v("Codesigning %s at %s", lib.fileSystemRepresentation, dst_dir.fileSystemRepresentation);
// Build the codesign invocation.
NSMutableArray *commandAndArguments =
[NSMutableArray arrayWithObjects:
@"codesign",
@"--force", @"--sign", ident, @"--verbose", nil];
if (keychain) {
[commandAndArguments addObject:@"--keychain"];
[commandAndArguments addObject:keychain];
}
// Other codesign flags come later
// so they can override the default flags.
[commandAndArguments addObjectsFromArray:otherCodesignFlags];
[commandAndArguments addObject:dst];
int err = xcrunToolCommand(commandAndArguments);
// Fail if codesign failed.
if (err) {
// Clean up any temporary files.
if (tmpFilePath)
unlink(tmpFilePath);
fail("Couldn't codesign %s: codesign failed with "
"exit code %d", dst.fileSystemRepresentation, err);
}
// If we have an existing code signature data, query the new one and compare
// it with the code signature of the file before we re-signed it.
// If they are the same, use the original file instead. This preserves
// the contents of the file and mtime for use with delta installs.
if (oldSignatureData) {
NSData *newSignatureData = query_code_signature(dst);
#if 0
// For Debugging.
fprintf(stdout, "oldSignature (%lu bytes)\n", (unsigned long)oldSignatureData.length);
fwrite(oldSignatureData.bytes, oldSignatureData.length, 1, stdout);
fprintf(stdout, "\nnewSignature (%lu bytes)\n", (unsigned long)newSignatureData.length);
fwrite(newSignatureData.bytes, newSignatureData.length, 1, stdout);
fprintf(stdout, "\n");
fflush(stdout);
#endif
unsigned newLength = newSignatureData.length;
if (newLength == oldSignatureData.length &&
memcmp(newSignatureData.bytes, oldSignatureData.bytes, newLength) == 0) {
log_v("Code signature of %s is unchanged; keeping original", lib.fileSystemRepresentation);
// The two signatures match. Unlink the new file, and re-link the old file.
const char *filePath = dst.fileSystemRepresentation;
unlink(filePath);
link(tmpFilePath, filePath);
}
}
// Clean up any temporary files.
if (tmpFilePath) {
unlink(tmpFilePath);
}
if (!signedOne) {
// We are the first signer. Allow the others to proceed now.
signedOne = true;
[signingLock unlock];
}
}];
[signingLock release];
}
}
exit(0);
}