mirror of
https://github.com/touchHLE/touchHLE.git
synced 2026-01-31 01:25:24 +01:00
Support building Objective C in TestApp
Supports building Objective-C code inside TestApp by using the common3.0-sdk. Change-Id: I684e2892f750c03818dd2bc35f0d108e61611e15
This commit is contained in:
committed by
hikari_no_yume
parent
d9b0991b1a
commit
9666ff6746
5
.github/workflows/touchHLE_release.yml
vendored
5
.github/workflows/touchHLE_release.yml
vendored
@@ -57,6 +57,8 @@ jobs:
|
|||||||
- if: ${{ steps.cache-llvm.outputs.cache-hit != 'true' }}
|
- if: ${{ steps.cache-llvm.outputs.cache-hit != 'true' }}
|
||||||
name: Extract LLVM
|
name: Extract LLVM
|
||||||
run: tar -xf clang+llvm-12.0.0-x86_64-apple-darwin.tar.xz && mkdir tests/llvm && mv clang+llvm-12.0.0-x86_64-apple-darwin/* tests/llvm
|
run: tar -xf clang+llvm-12.0.0-x86_64-apple-darwin.tar.xz && mkdir tests/llvm && mv clang+llvm-12.0.0-x86_64-apple-darwin/* tests/llvm
|
||||||
|
- name: Get copy of common-3.0-sdk
|
||||||
|
run: cd ${{ github.workspace }}/tests && curl -L https://github.com/touchHLE/common-3.0-sdk/releases/latest/download/common-3.0.sdk-macOS-universal.tar.gz | tar xzv
|
||||||
- name: Install x86_64 toolchain
|
- name: Install x86_64 toolchain
|
||||||
run: rustup target add x86_64-apple-darwin
|
run: rustup target add x86_64-apple-darwin
|
||||||
- name: Test
|
- name: Test
|
||||||
@@ -227,6 +229,9 @@ jobs:
|
|||||||
- if: ${{ steps.cache-llvm.outputs.cache-hit != 'true' }}
|
- if: ${{ steps.cache-llvm.outputs.cache-hit != 'true' }}
|
||||||
name: Extract LLVM
|
name: Extract LLVM
|
||||||
run: 7z -otests/llvm x LLVM-12.0.1-win64.exe
|
run: 7z -otests/llvm x LLVM-12.0.1-win64.exe
|
||||||
|
# Windows seems to break the stdout pipe for some reason
|
||||||
|
- name: Get copy of common-3.0-sdk
|
||||||
|
run: cd ${{ github.workspace }}/tests && curl -L --output ${{ runner.temp }}\sdk.tar.gz https://github.com/touchHLE/common-3.0-sdk/releases/latest/download/common-3.0.sdk-windows-x86_64.tar.gz && tar xzvf ${{ runner.temp }}\sdk.tar.gz
|
||||||
- name: Test
|
- name: Test
|
||||||
run: cargo test
|
run: cargo test
|
||||||
env:
|
env:
|
||||||
|
|||||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -13,8 +13,6 @@ touchHLE_wallpaper.*
|
|||||||
*.mode1v3
|
*.mode1v3
|
||||||
!default.mode1v3
|
!default.mode1v3
|
||||||
|
|
||||||
tests/TestApp_source/build
|
|
||||||
|
|
||||||
# Android
|
# Android
|
||||||
|
|
||||||
*.iml
|
*.iml
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ The developer documentation can be found in [the `dev-docs` directory](dev-docs/
|
|||||||
|
|
||||||
Please run [`dev-scripts/format.sh`](dev-scripts/format.sh) and [`dev-scripts/lint.sh`](dev-scripts/lint.sh) on your changes before committing.
|
Please run [`dev-scripts/format.sh`](dev-scripts/format.sh) and [`dev-scripts/lint.sh`](dev-scripts/lint.sh) on your changes before committing.
|
||||||
|
|
||||||
You should also run `cargo test`. [Building the integration tests requires downloading LLVM](tests/README.md), so it's understandable if you want to skip them (`cargo test -- --skip run_test_app`) and let the GitHub Actions CI catch any issues when you submit your pull request. Alternatively, you can download a pre-built version of the integration tests app (TestApp) from GitHub Actions CI and run it in touchHLE.
|
You should also run `cargo test`. [Building the integration tests requires downloading LLVM and a custom toolchain](tests/README.md), so it's understandable if you want to skip them (`cargo test -- --skip run_test_app`) and let the GitHub Actions CI catch any issues when you submit your pull request. Alternatively, you can download a pre-built version of the integration tests app (TestApp) from GitHub Actions CI and run it in touchHLE.
|
||||||
|
|
||||||
If you're going to open a pull request with non-trivial changes, please talk to us first so we can figure out if we're likely to accept them. It would be a shame if your effort had to be wasted.
|
If you're going to open a pull request with non-trivial changes, please talk to us first so we can figure out if we're likely to accept them. It would be a shame if your effort had to be wasted.
|
||||||
|
|
||||||
|
|||||||
@@ -21,7 +21,9 @@ touchHLE will print the basic registers (r0-r13, SP, LR, PC) and a basic stack t
|
|||||||
|
|
||||||
## Dumping classes/selectors/function symbols from binaries
|
## Dumping classes/selectors/function symbols from binaries
|
||||||
The `--dump=linking-info` flag dumps information about the classes, selectors, and lazy symbols (functions) that are requested by the binary, and how touchHLE is handling them. This is output to the file specified by `--dump-file=` (which defaults to the {running directory}/DUMP.txt.
|
The `--dump=linking-info` flag dumps information about the classes, selectors, and lazy symbols (functions) that are requested by the binary, and how touchHLE is handling them. This is output to the file specified by `--dump-file=` (which defaults to the {running directory}/DUMP.txt.
|
||||||
|
|
||||||
The most useful application for this is determining which classes/selectors/functions that (might) be needed by an application are not implemented by touchHLE. This can be checked with `dev-scripts/log_unimplemented.sh [name of app to check]` (make sure `jq` is installed!).
|
The most useful application for this is determining which classes/selectors/functions that (might) be needed by an application are not implemented by touchHLE. This can be checked with `dev-scripts/log_unimplemented.sh [name of app to check]` (make sure `jq` is installed!).
|
||||||
|
|
||||||
The schemas for the JSON are described in `ObjC::dump_classes` (in `src/objc/classes.rs`), `ObjC::dump_selectors` (in `src/objc/selectors.rs`), and `Dyld::dump_lazy_symbols` (in `src/dyld.rs`).
|
The schemas for the JSON are described in `ObjC::dump_classes` (in `src/objc/classes.rs`), `ObjC::dump_selectors` (in `src/objc/selectors.rs`), and `Dyld::dump_lazy_symbols` (in `src/dyld.rs`).
|
||||||
|
|
||||||
### GDB Remote Serial Protocol server
|
### GDB Remote Serial Protocol server
|
||||||
|
|||||||
5
tests/.gitignore
vendored
5
tests/.gitignore
vendored
@@ -1,2 +1,7 @@
|
|||||||
/llvm
|
/llvm
|
||||||
/TestApp.app/TestApp
|
/TestApp.app/TestApp
|
||||||
|
/TestApp_source/build
|
||||||
|
/libc_stub/libSystem.dylib
|
||||||
|
/libc_stub/libc_stub.c
|
||||||
|
/SYMBOLS.txt
|
||||||
|
/common-3.0.sdk
|
||||||
|
|||||||
@@ -6,23 +6,37 @@ This directory contains integration tests written in Objective-C. They're compil
|
|||||||
Building
|
Building
|
||||||
--------
|
--------
|
||||||
|
|
||||||
### Setup
|
### Compiler Setup
|
||||||
|
|
||||||
Upstream LLVM is needed for building the ARMv6 test binary. 32-bit iOS support is broken in version 13 onwards, so 12.0.1 is the newest supported version you can use. Downloads:
|
Clang is required to build the TestApp binary, and should be placed (or symlinked) at `tests/TestApp_build/llvm/bin/clang`. While modern versions of clang may work fine, only clang 12 (listed below) is tested and used in CI.
|
||||||
|
|
||||||
* [LLVM 12.0.1 Windows x64 release binaries](https://github.com/llvm/llvm-project/releases/download/llvmorg-12.0.1/LLVM-12.0.1-win64.exe) (extract it with 7-zip)
|
* [LLVM 12.0.1 Windows x64 release binaries](https://github.com/llvm/llvm-project/releases/download/llvmorg-12.0.1/LLVM-12.0.1-win64.exe) (extract it with 7-zip)
|
||||||
* [LLVM 12.0.0 macOS x64 release binaries](https://github.com/llvm/llvm-project/releases/download/llvmorg-12.0.0/clang+llvm-12.0.0-x86_64-apple-darwin.tar.xz) (extract it with `tar -xf`)
|
* [LLVM 12.0.0 macOS x64 release binaries](https://github.com/llvm/llvm-project/releases/download/llvmorg-12.0.0/clang+llvm-12.0.0-x86_64-apple-darwin.tar.xz) (extract it with `tar -xf`)
|
||||||
* [Other versions](https://github.com/llvm/llvm-project/releases/tag/llvmorg-12.0.0) (though you might need to build LLVM yourself, sorry :c)
|
* [Other versions](https://github.com/llvm/llvm-project/releases/tag/llvmorg-12.0.0) (though you might need to build LLVM yourself, sorry :c)
|
||||||
|
|
||||||
Extract LLVM to `tests/llvm`, so that e.g. `tests/TestApp_build/llvm/bin/clang` (with `.exe` suffix, on Windows) is the path to Clang. `cargo test` (via `integration.rs`) will do the rest.
|
Extract LLVM to `tests/llvm`, so that `tests/TestApp_build/llvm/bin/clang` (with `.exe` suffix, on Windows) is the path to Clang.
|
||||||
|
|
||||||
### Why
|
### Linker setup
|
||||||
|
|
||||||
32-bit iOS is an awkward platform to target. There's no way Apple's official tools still support it, and nobody wants to have to install an old version of an OS X in a VM. Also, this is a cross-platform emulation project, but Apple's tools require you to own a Mac. Using LLVM lets us avoid a dependency on this legacy, proprietary software that only runs on one platform, in favour of somewhat less legacy (July 2021), cross-platform software with convenient release builds available.
|
A [custom SDK](https://github.com/touchHLE/common-3.0-sdk) with headers and a multiplatform version of Apple's `ld` is required to build the TestApp binary. To install it, download the latest release (or follow the instructions inside the repository to compile it), then extract/place/symlink the resultant directories as shown below. (On Windows, you may find it easier to use the precompiled binaries, since compiling requires mingw).
|
||||||
|
|
||||||
### Particulars
|
The overall structure of the tests directory should look like the following:
|
||||||
|
```
|
||||||
Upstream LLVM provides a compiler (Clang) and linker (LLD) that can target 32-bit iOS, but not platform headers or libraries, so some tricks are needed. See the comments in `integration.rs` and `main.c`. Some additional notes:
|
- tests
|
||||||
|
- integration.rs
|
||||||
- The resulting binary is probably not actually compatible iPhone OS 2. It uses `LC_MAIN` rather than `LC_UNIX_THREAD`. It might work on iOS 6? I haven't tested it.
|
- llvm
|
||||||
- LLD crashes if you try to compile Objective-C rather than C code. It might be expecting an Objective-C system library.
|
- bin
|
||||||
|
- clang
|
||||||
|
- common-3.0.sdk
|
||||||
|
- usr
|
||||||
|
- lib
|
||||||
|
- (sdk libraries)
|
||||||
|
- include
|
||||||
|
- (sdk headers)
|
||||||
|
- bin
|
||||||
|
- ld(.exe)
|
||||||
|
- lipo(.exe)
|
||||||
|
- TestApp_source
|
||||||
|
- TestApp.app
|
||||||
|
- libc_stub
|
||||||
|
```
|
||||||
|
|||||||
19
tests/TestApp_source/SyncTester.h
Normal file
19
tests/TestApp_source/SyncTester.h
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
/*
|
||||||
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
|
*/
|
||||||
|
//
|
||||||
|
// SyncTester.h
|
||||||
|
// TestApp
|
||||||
|
//
|
||||||
|
#import "system_headers_objc.h"
|
||||||
|
|
||||||
|
@interface SyncTester : NSObject {
|
||||||
|
}
|
||||||
|
@property(nonatomic) int counter;
|
||||||
|
@property(nonatomic) BOOL test_ok;
|
||||||
|
- (void)recursiveSyncEnter;
|
||||||
|
- (BOOL)holdAndCheckCounter;
|
||||||
|
- (void)tryModifyCounter;
|
||||||
|
@end
|
||||||
52
tests/TestApp_source/SyncTester.m
Normal file
52
tests/TestApp_source/SyncTester.m
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
/*
|
||||||
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
|
*/
|
||||||
|
//
|
||||||
|
// SyncTester.m
|
||||||
|
// TestApp
|
||||||
|
//
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
|
#import "SyncTester.h"
|
||||||
|
#import "system_headers_objc.h"
|
||||||
|
|
||||||
|
@implementation SyncTester : NSObject {
|
||||||
|
}
|
||||||
|
- (SyncTester *)init {
|
||||||
|
self = [super init];
|
||||||
|
self.counter = 0;
|
||||||
|
self.test_ok = false;
|
||||||
|
return self;
|
||||||
|
}
|
||||||
|
- (BOOL)holdAndCheckCounter {
|
||||||
|
@synchronized(self) {
|
||||||
|
self.counter = 0;
|
||||||
|
for (int i = 0; i < 10; i++) {
|
||||||
|
usleep(1000);
|
||||||
|
}
|
||||||
|
return self.counter == 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
- (void)tryModifyCounter {
|
||||||
|
@synchronized(self) {
|
||||||
|
self.counter = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
- (void)recursiveSyncEnterWithCount:(int)count {
|
||||||
|
if (count <= 0) {
|
||||||
|
self.counter++;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
@synchronized(self) {
|
||||||
|
[self recursiveSyncEnterWithCount:(count - 1)];
|
||||||
|
[self recursiveSyncEnterWithCount:(count - 1)];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
- (void)recursiveSyncEnter {
|
||||||
|
self.counter = 0;
|
||||||
|
[self recursiveSyncEnterWithCount:4];
|
||||||
|
self.test_ok = self.counter == 16;
|
||||||
|
}
|
||||||
|
@end
|
||||||
@@ -13,334 +13,35 @@
|
|||||||
// For convenience, let's just include the other source files.
|
// For convenience, let's just include the other source files.
|
||||||
|
|
||||||
#include "CGAffineTransform.c"
|
#include "CGAffineTransform.c"
|
||||||
|
#import "SyncTester.h"
|
||||||
|
|
||||||
// === Declarations ===
|
// === Declarations ===
|
||||||
|
|
||||||
// We don't have any system headers for iPhone OS, so we must declare everything
|
// We don't have any system headers for iPhone OS, so we must declare everything
|
||||||
// ourselves rather than #include'ing.
|
// ourselves rather than #include'ing.
|
||||||
|
|
||||||
// <stddef.h>
|
#include <CoreFoundation/CFBase.h>
|
||||||
#define NULL ((void *)0)
|
#include <CoreFoundation/CFDictionary.h>
|
||||||
typedef unsigned long size_t;
|
#include <CoreFoundation/CFNumber.h>
|
||||||
typedef int wchar_t;
|
#include <CoreFoundation/CFString.h>
|
||||||
|
#include <CoreFoundation/CFURL.h>
|
||||||
// <errno.h>
|
#include <arpa/inet.h>
|
||||||
int *__error(void);
|
#include <dirent.h>
|
||||||
#define errno (*__error())
|
#include <errno.h>
|
||||||
|
#include <fcntl.h>
|
||||||
// <stdarg.h>
|
#include <fenv.h>
|
||||||
typedef __builtin_va_list va_list;
|
#include <locale.h>
|
||||||
#define va_start(a, b) __builtin_va_start(a, b)
|
#include <math.h>
|
||||||
#define va_arg(a, b) __builtin_va_arg(a, b)
|
#include <pthread.h>
|
||||||
#define va_end(a) __builtin_va_end(a)
|
#include <semaphore.h>
|
||||||
|
#include <setjmp.h>
|
||||||
// <stdio.h>
|
#include <stdarg.h>
|
||||||
typedef struct FILE FILE;
|
#include <stddef.h>
|
||||||
FILE *fopen(const char *, const char *);
|
#include <stdio.h>
|
||||||
int fclose(FILE *);
|
#include <stdlib.h>
|
||||||
int sscanf(const char *, const char *, ...);
|
#include <string.h>
|
||||||
int fscanf(FILE *, const char *, ...);
|
#include <unistd.h>
|
||||||
int fgetc(FILE *);
|
#include <wchar.h>
|
||||||
int printf(const char *, ...);
|
|
||||||
int vsnprintf(char *, size_t, const char *, va_list);
|
|
||||||
int swprintf(wchar_t *, size_t, const wchar_t *, ...);
|
|
||||||
size_t fwrite(const void *, size_t, size_t, FILE *);
|
|
||||||
size_t fread(void *, size_t, size_t, FILE *);
|
|
||||||
int getc(FILE *);
|
|
||||||
int ungetc(int, FILE *);
|
|
||||||
|
|
||||||
// <stdlib.h>
|
|
||||||
#define EXIT_SUCCESS 0
|
|
||||||
#define EXIT_FAILURE 1
|
|
||||||
void exit(int);
|
|
||||||
void free(void *);
|
|
||||||
void *malloc(size_t);
|
|
||||||
void qsort(void *, size_t, size_t, int (*)(const void *, const void *));
|
|
||||||
void *realloc(void *, size_t);
|
|
||||||
double atof(const char *);
|
|
||||||
float strtof(const char *, char **);
|
|
||||||
long strtol(const char *, char **, int);
|
|
||||||
unsigned long strtoul(const char *, char **, int);
|
|
||||||
char *realpath(const char *, char *);
|
|
||||||
size_t mbstowcs(wchar_t *, const char *, size_t);
|
|
||||||
size_t wcstombs(char *, const wchar_t *, size_t);
|
|
||||||
|
|
||||||
// <string.h>
|
|
||||||
void *memset(void *, int, size_t);
|
|
||||||
void memset_pattern4(void *, const void *, size_t);
|
|
||||||
void memset_pattern8(void *, const void *, size_t);
|
|
||||||
void memset_pattern16(void *, const void *, size_t);
|
|
||||||
int memcmp(const void *, const void *, size_t);
|
|
||||||
void *memcpy(void *, const void *, size_t);
|
|
||||||
void *memmove(void *, const void *, size_t);
|
|
||||||
int strcmp(const char *, const char *);
|
|
||||||
char *strncpy(char *, const char *, size_t);
|
|
||||||
char *strncat(char *, const char *, size_t);
|
|
||||||
size_t strlcpy(char *, const char *, size_t);
|
|
||||||
char *strchr(const char *s, int c);
|
|
||||||
char *strrchr(const char *s, int c);
|
|
||||||
size_t strlen(const char *);
|
|
||||||
int strncmp(const char *, const char *, size_t);
|
|
||||||
size_t strcspn(const char *, const char *);
|
|
||||||
char *strdup(const char *);
|
|
||||||
|
|
||||||
// <unistd.h>
|
|
||||||
typedef unsigned int __uint32_t;
|
|
||||||
typedef __uint32_t useconds_t;
|
|
||||||
int chdir(const char *);
|
|
||||||
char *getcwd(char *, size_t);
|
|
||||||
int usleep(useconds_t);
|
|
||||||
|
|
||||||
// <fcntl.h>
|
|
||||||
#define O_RDONLY 0x00000000
|
|
||||||
#define O_WRONLY 0x00000001
|
|
||||||
#define O_RDWR 0x00000002
|
|
||||||
#define O_CREAT 0x00000200
|
|
||||||
|
|
||||||
int open(const char *, int, ...);
|
|
||||||
int close(int);
|
|
||||||
|
|
||||||
// <pthread.h>
|
|
||||||
typedef struct opaque_pthread_t opaque_pthread_t;
|
|
||||||
typedef struct opaque_pthread_t *__pthread_t;
|
|
||||||
typedef __pthread_t pthread_t;
|
|
||||||
|
|
||||||
typedef struct opaque_pthread_attr_t opaque_pthread_attr_t;
|
|
||||||
typedef struct opaque_pthread_attr_t *__pthread_attr_t;
|
|
||||||
typedef __pthread_attr_t pthread_attr_t;
|
|
||||||
|
|
||||||
struct _opaque_pthread_mutex_t {
|
|
||||||
long __sig;
|
|
||||||
char __opaque[40];
|
|
||||||
};
|
|
||||||
typedef struct _opaque_pthread_mutex_t __pthread_mutex_t;
|
|
||||||
typedef __pthread_mutex_t pthread_mutex_t;
|
|
||||||
|
|
||||||
typedef struct opaque_pthread_mutexattr_t opaque_pthread_mutexattr_t;
|
|
||||||
typedef struct opaque_pthread_mutexattr_t *__pthread_mutexattr_t;
|
|
||||||
typedef __pthread_mutexattr_t pthread_mutexattr_t;
|
|
||||||
|
|
||||||
typedef struct opaque_pthread_cond_t opaque_pthread_cond_t;
|
|
||||||
typedef struct opaque_pthread_cond_t *__pthread_cond_t;
|
|
||||||
typedef __pthread_cond_t pthread_cond_t;
|
|
||||||
|
|
||||||
typedef struct opaque_pthread_condattr_t opaque_pthread_condattr_t;
|
|
||||||
typedef struct opaque_pthread_condattr_t *__pthread_condattr_t;
|
|
||||||
typedef __pthread_condattr_t pthread_condattr_t;
|
|
||||||
|
|
||||||
int pthread_create(pthread_t *, const pthread_attr_t *, void *(*)(void *),
|
|
||||||
void *);
|
|
||||||
int pthread_join(pthread_t thread, void **value_ptr);
|
|
||||||
|
|
||||||
int pthread_cond_init(pthread_cond_t *, const pthread_condattr_t *);
|
|
||||||
int pthread_cond_signal(pthread_cond_t *);
|
|
||||||
int pthread_cond_broadcast(pthread_cond_t *);
|
|
||||||
int pthread_cond_wait(pthread_cond_t *, pthread_mutex_t *);
|
|
||||||
|
|
||||||
int pthread_mutex_init(pthread_mutex_t *, const pthread_mutexattr_t *);
|
|
||||||
int pthread_mutex_lock(pthread_mutex_t *);
|
|
||||||
int pthread_mutex_unlock(pthread_mutex_t *);
|
|
||||||
|
|
||||||
// <semaphore.h>
|
|
||||||
#define SEM_FAILED ((sem_t *)-1)
|
|
||||||
typedef int sem_t;
|
|
||||||
int sem_close(sem_t *);
|
|
||||||
sem_t *sem_open(const char *, int, ...);
|
|
||||||
int sem_post(sem_t *);
|
|
||||||
int sem_trywait(sem_t *);
|
|
||||||
int sem_unlink(const char *);
|
|
||||||
int sem_wait(sem_t *);
|
|
||||||
|
|
||||||
// <locale.h>
|
|
||||||
#define LC_ALL 0
|
|
||||||
#define LC_COLLATE 1
|
|
||||||
#define LC_CTYPE 2
|
|
||||||
#define LC_MONETARY 3
|
|
||||||
#define LC_NUMERIC 4
|
|
||||||
#define LC_TIME 5
|
|
||||||
#define LC_MESSAGES 6
|
|
||||||
char *setlocale(int category, const char *locale);
|
|
||||||
|
|
||||||
#ifdef DEFINE_ME_WHEN_BUILDING_ON_MACOS
|
|
||||||
typedef long _register_t; // 64-bit definition
|
|
||||||
#else
|
|
||||||
typedef int _register_t;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// <setjmp.h>
|
|
||||||
#define _JBLEN (10 + 16 + 2)
|
|
||||||
typedef _register_t jmp_buf[_JBLEN];
|
|
||||||
int setjmp(jmp_buf env);
|
|
||||||
void longjmp(jmp_buf env, int val);
|
|
||||||
|
|
||||||
// <ctype.h>
|
|
||||||
int __maskrune(wchar_t, unsigned long);
|
|
||||||
|
|
||||||
// <dirent.h>
|
|
||||||
typedef struct {
|
|
||||||
int _unused;
|
|
||||||
} DIR;
|
|
||||||
struct dirent {
|
|
||||||
char _unused[21]; // TODO
|
|
||||||
char d_name[1024];
|
|
||||||
};
|
|
||||||
DIR *opendir(const char *);
|
|
||||||
struct dirent *readdir(DIR *);
|
|
||||||
int closedir(DIR *);
|
|
||||||
int scandir(const char *, struct dirent ***, int (*)(struct dirent *),
|
|
||||||
int (*)(const void *, const void *));
|
|
||||||
|
|
||||||
// <wchar.h>
|
|
||||||
int swscanf(const wchar_t *, const wchar_t *, ...);
|
|
||||||
|
|
||||||
// <math.h>
|
|
||||||
long int lrint(double);
|
|
||||||
long int lrintf(float);
|
|
||||||
double ldexp(double, int);
|
|
||||||
float ldexpf(float, int);
|
|
||||||
float frexpf(float, int *);
|
|
||||||
double frexp(double, int *);
|
|
||||||
double fabs(double);
|
|
||||||
|
|
||||||
// <fenv.h>
|
|
||||||
#define FE_TONEAREST 0x00000000
|
|
||||||
#define FE_TOWARDZERO 0x00C00000
|
|
||||||
int fegetround(void);
|
|
||||||
int fesetround(int);
|
|
||||||
|
|
||||||
// <inet.h>
|
|
||||||
typedef unsigned int socklen_t;
|
|
||||||
typedef unsigned int in_addr_t;
|
|
||||||
struct in_addr {
|
|
||||||
in_addr_t s_addr;
|
|
||||||
};
|
|
||||||
in_addr_t inet_addr(const char *);
|
|
||||||
const char *inet_ntop(int, const void *, char *, socklen_t);
|
|
||||||
int inet_pton(int, const char *, void *);
|
|
||||||
|
|
||||||
// `CFBase.h`
|
|
||||||
|
|
||||||
typedef unsigned char Boolean;
|
|
||||||
typedef const void *CFTypeRef;
|
|
||||||
typedef const struct _CFAllocator *CFAllocatorRef;
|
|
||||||
typedef unsigned int CFStringEncoding;
|
|
||||||
typedef unsigned long CFHashCode;
|
|
||||||
typedef signed long CFIndex;
|
|
||||||
typedef struct {
|
|
||||||
CFIndex location;
|
|
||||||
CFIndex length;
|
|
||||||
} CFRange;
|
|
||||||
typedef unsigned long CFOptionFlags;
|
|
||||||
typedef const struct _CFDictionary *CFDictionaryRef;
|
|
||||||
typedef const struct _CFString *CFStringRef;
|
|
||||||
typedef const struct _CFString *CFMutableStringRef;
|
|
||||||
typedef const struct _CFURL *CFURLRef;
|
|
||||||
|
|
||||||
CFTypeRef CFRetain(CFTypeRef cf);
|
|
||||||
void CFRelease(CFTypeRef cf);
|
|
||||||
Boolean CFEqual(CFTypeRef cf1, CFTypeRef cf2);
|
|
||||||
CFHashCode CFHash(CFTypeRef cf);
|
|
||||||
|
|
||||||
enum {
|
|
||||||
kCFCompareLessThan = -1,
|
|
||||||
kCFCompareEqualTo = 0,
|
|
||||||
kCFCompareGreaterThan = 1
|
|
||||||
};
|
|
||||||
|
|
||||||
// `CFData.h`
|
|
||||||
|
|
||||||
typedef const struct _CFData *CFDataRef;
|
|
||||||
|
|
||||||
CFDataRef CFDataCreate(CFAllocatorRef, const char *, CFIndex);
|
|
||||||
const unsigned char *CFDataGetBytePtr(CFDataRef);
|
|
||||||
|
|
||||||
// `CFString.h`
|
|
||||||
|
|
||||||
enum { kCFStringEncodingASCII = 0x600 };
|
|
||||||
|
|
||||||
typedef int CFComparisonResult;
|
|
||||||
typedef unsigned int CFStringCompareFlags;
|
|
||||||
|
|
||||||
void CFStringAppendFormat(CFMutableStringRef s, CFDictionaryRef fo,
|
|
||||||
CFStringRef format, ...);
|
|
||||||
CFMutableStringRef CFStringCreateMutable(CFAllocatorRef alloc, CFIndex max_len);
|
|
||||||
CFStringRef CFStringCreateWithCString(CFAllocatorRef alloc, const char *cStr,
|
|
||||||
CFStringEncoding encoding);
|
|
||||||
CFComparisonResult CFStringCompare(CFStringRef a, CFStringRef b,
|
|
||||||
CFStringCompareFlags flags);
|
|
||||||
CFRange CFStringFind(CFStringRef theString, CFStringRef stringToFind,
|
|
||||||
CFOptionFlags compareOptions);
|
|
||||||
|
|
||||||
// `CFDictionary.h`
|
|
||||||
|
|
||||||
typedef const struct _CFDictionary *CFMutableDictionaryRef;
|
|
||||||
|
|
||||||
typedef const void *(*CFDictionaryRetainCallBack)(CFAllocatorRef alloc,
|
|
||||||
const void *value);
|
|
||||||
typedef void (*CFDictionaryReleaseCallBack)(CFAllocatorRef alloc,
|
|
||||||
const void *val);
|
|
||||||
typedef CFStringRef (*CFDictionaryCopyDescriptionCallBack)(const void *val);
|
|
||||||
typedef Boolean (*CFDictionaryEqualCallBack)(const void *val1,
|
|
||||||
const void *val2);
|
|
||||||
typedef CFHashCode (*CFDictionaryHashCallBack)(const void *val);
|
|
||||||
|
|
||||||
typedef struct {
|
|
||||||
CFIndex version;
|
|
||||||
CFDictionaryRetainCallBack retain;
|
|
||||||
CFDictionaryReleaseCallBack release;
|
|
||||||
CFDictionaryCopyDescriptionCallBack copyDescription;
|
|
||||||
CFDictionaryEqualCallBack equal;
|
|
||||||
CFDictionaryHashCallBack hash;
|
|
||||||
} CFDictionaryKeyCallBacks;
|
|
||||||
|
|
||||||
typedef struct {
|
|
||||||
CFIndex version;
|
|
||||||
CFDictionaryRetainCallBack retain;
|
|
||||||
CFDictionaryReleaseCallBack release;
|
|
||||||
CFDictionaryCopyDescriptionCallBack copyDescription;
|
|
||||||
CFDictionaryEqualCallBack equal;
|
|
||||||
} CFDictionaryValueCallBacks;
|
|
||||||
|
|
||||||
CFMutableDictionaryRef
|
|
||||||
CFDictionaryCreateMutable(CFAllocatorRef allocator, CFIndex capacity,
|
|
||||||
const CFDictionaryKeyCallBacks *keyCallBacks,
|
|
||||||
const CFDictionaryValueCallBacks *valueCallBacks);
|
|
||||||
void CFDictionaryAddValue(CFMutableDictionaryRef dict, const void *key,
|
|
||||||
const void *value);
|
|
||||||
void CFDictionarySetValue(CFMutableDictionaryRef dict, const void *key,
|
|
||||||
const void *value);
|
|
||||||
void CFDictionaryRemoveValue(CFMutableDictionaryRef dict, const void *key);
|
|
||||||
void CFDictionaryRemoveAllValues(CFMutableDictionaryRef dict);
|
|
||||||
const void *CFDictionaryGetValue(CFDictionaryRef dict, const void *key);
|
|
||||||
CFIndex CFDictionaryGetCount(CFDictionaryRef dict);
|
|
||||||
void CFDictionaryGetKeysAndValues(CFDictionaryRef dict, const void **keys,
|
|
||||||
const void **values);
|
|
||||||
|
|
||||||
// `CFURL.h`
|
|
||||||
|
|
||||||
CFURLRef CFURLCreateFromFileSystemRepresentation(CFAllocatorRef allocator,
|
|
||||||
const char *buffer,
|
|
||||||
CFIndex bufLen,
|
|
||||||
Boolean isDirectory);
|
|
||||||
CFStringRef CFURLCopyFileSystemPath(CFURLRef anURL, CFIndex pathStyle);
|
|
||||||
|
|
||||||
CFURLRef CFURLCreateCopyAppendingPathComponent(CFAllocatorRef allocator,
|
|
||||||
CFURLRef url,
|
|
||||||
CFStringRef pathComponent,
|
|
||||||
Boolean isDirectory);
|
|
||||||
CFURLRef CFURLCreateCopyDeletingLastPathComponent(CFAllocatorRef allocator,
|
|
||||||
CFURLRef url);
|
|
||||||
|
|
||||||
CFURLRef CFURLCreateWithBytes(CFAllocatorRef, const char *, CFIndex,
|
|
||||||
CFStringEncoding, CFURLRef);
|
|
||||||
Boolean CFURLHasDirectoryPath(CFURLRef url);
|
|
||||||
|
|
||||||
// `CFNumber.h`
|
|
||||||
|
|
||||||
typedef const struct _CFNumber *CFNumberRef;
|
|
||||||
typedef int CFNumberType;
|
|
||||||
CFNumberRef CFNumberCreate(CFAllocatorRef, CFNumberType, const void *);
|
|
||||||
CFComparisonResult CFNumberCompare(CFNumberRef, CFNumberRef, void *);
|
|
||||||
|
|
||||||
// `CGDataProvider.h`
|
// `CGDataProvider.h`
|
||||||
|
|
||||||
@@ -2903,9 +2604,9 @@ int test_inet_pton() {
|
|||||||
int test_case_CFURL(const char *basePathCStr, const char *urlPathCStr,
|
int test_case_CFURL(const char *basePathCStr, const char *urlPathCStr,
|
||||||
const char *fileNameCStr,
|
const char *fileNameCStr,
|
||||||
const char *expectedAppendedCStr) {
|
const char *expectedAppendedCStr) {
|
||||||
CFURLRef url = CFURLCreateFromFileSystemRepresentation(NULL, urlPathCStr,
|
CFURLRef url = CFURLCreateFromFileSystemRepresentation(
|
||||||
strlen(urlPathCStr),
|
NULL, (uint8_t *)urlPathCStr, strlen(urlPathCStr),
|
||||||
1 // isDirectory
|
1 // isDirectory
|
||||||
);
|
);
|
||||||
if (url == NULL) {
|
if (url == NULL) {
|
||||||
return -1;
|
return -1;
|
||||||
@@ -3029,31 +2730,6 @@ int test_CFNumberCompare_simple() {
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
#define INT8_MAX 127
|
|
||||||
#define INT16_MAX 32767
|
|
||||||
#define INT32_MAX 2147483647
|
|
||||||
#define INT64_MAX 9223372036854775807LL
|
|
||||||
|
|
||||||
#define INT8_MIN -128
|
|
||||||
#define INT16_MIN -32768
|
|
||||||
|
|
||||||
// Note: those cannot expressed as literals
|
|
||||||
#define INT32_MIN (-INT32_MAX - 1)
|
|
||||||
#define INT64_MIN (-INT64_MAX - 1)
|
|
||||||
|
|
||||||
#define UINT8_MAX 255
|
|
||||||
#define UINT16_MAX 65535
|
|
||||||
#define UINT32_MAX 4294967295U
|
|
||||||
#define UINT64_MAX 18446744073709551615ULL
|
|
||||||
|
|
||||||
#define HUGE_VALF 1e50f
|
|
||||||
#define INFINITY HUGE_VALF
|
|
||||||
|
|
||||||
typedef signed char int8_t;
|
|
||||||
typedef short int16_t;
|
|
||||||
typedef int int32_t;
|
|
||||||
typedef long long int int64_t;
|
|
||||||
|
|
||||||
#ifndef kCFNumberSInt8Type
|
#ifndef kCFNumberSInt8Type
|
||||||
#define kCFNumberSInt8Type 1
|
#define kCFNumberSInt8Type 1
|
||||||
#define kCFNumberSInt16Type 2
|
#define kCFNumberSInt16Type 2
|
||||||
@@ -3346,6 +3022,42 @@ int test_memset_pattern() {
|
|||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
typedef struct {
|
||||||
|
SyncTester *tester;
|
||||||
|
BOOL res;
|
||||||
|
} sync_test_arg;
|
||||||
|
|
||||||
|
void *modify(sync_test_arg *arg) {
|
||||||
|
SyncTester *tester = arg->tester;
|
||||||
|
arg->res = [tester holdAndCheckCounter];
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
void *try_modify(SyncTester *tester) {
|
||||||
|
[tester tryModifyCounter];
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
int test_synchronized() {
|
||||||
|
SyncTester *sync_test = [SyncTester new];
|
||||||
|
sync_test_arg *arg = malloc(sizeof(sync_test_arg));
|
||||||
|
memset(arg, 0, sizeof(sync_test_arg));
|
||||||
|
arg->tester = sync_test;
|
||||||
|
pthread_t locking_thread;
|
||||||
|
pthread_create(&locking_thread, NULL, (void *(*)(void *)) & modify, arg);
|
||||||
|
pthread_t blocked_threads[10];
|
||||||
|
for (int i = 0; i < 10; i++) {
|
||||||
|
pthread_create(blocked_threads + i, NULL, (void *(*)(void *)) & try_modify,
|
||||||
|
sync_test);
|
||||||
|
}
|
||||||
|
if (pthread_join(locking_thread, NULL))
|
||||||
|
return -1;
|
||||||
|
if (!arg->res)
|
||||||
|
return -1;
|
||||||
|
[sync_test recursiveSyncEnter];
|
||||||
|
if (!sync_test.test_ok)
|
||||||
|
return -1;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
bool test_case_CFURLHasDirectoryPath(const char *str) {
|
bool test_case_CFURLHasDirectoryPath(const char *str) {
|
||||||
CFURLRef url = CFURLCreateWithBytes(NULL, str, strlen(str),
|
CFURLRef url = CFURLCreateWithBytes(NULL, str, strlen(str),
|
||||||
@@ -3446,14 +3158,10 @@ struct {
|
|||||||
FUNC_DEF(test_CGGeometry),
|
FUNC_DEF(test_CGGeometry),
|
||||||
FUNC_DEF(test_CFURLHasDirectoryPath),
|
FUNC_DEF(test_CFURLHasDirectoryPath),
|
||||||
FUNC_DEF(test_CGImage_JPEG),
|
FUNC_DEF(test_CGImage_JPEG),
|
||||||
|
FUNC_DEF(test_synchronized)
|
||||||
};
|
};
|
||||||
// clang-format on
|
// clang-format on
|
||||||
|
|
||||||
// Because no libc is linked into this executable, there is no libc entry point
|
|
||||||
// to call main. Instead, integration.rs tells Clang to set the _main symbol
|
|
||||||
// as the entry point. (It has to be _main because a C compiler will throw
|
|
||||||
// away stuff not called by main().) Since this is the true entry point, there's
|
|
||||||
// no argc or argv and we must call exit() ourselves.
|
|
||||||
int main() {
|
int main() {
|
||||||
int tests_run = 0;
|
int tests_run = 0;
|
||||||
int tests_passed = 0;
|
int tests_passed = 0;
|
||||||
25
tests/TestApp_source/system_headers_objc.h
Normal file
25
tests/TestApp_source/system_headers_objc.h
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
/*
|
||||||
|
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||||
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||||
|
*/
|
||||||
|
// Objective C headers we don't have open-source equivalents for.
|
||||||
|
#ifndef TOUCHHLE_OBJC_SYSTEM_H
|
||||||
|
#define TOUCHHLE_OBJC_SYSTEM_H
|
||||||
|
// Objective-C base:
|
||||||
|
typedef signed char BOOL;
|
||||||
|
#define false 0
|
||||||
|
#define true 1
|
||||||
|
typedef struct objc_selector *SEL;
|
||||||
|
typedef struct objc_class *Class;
|
||||||
|
typedef struct objc_object {
|
||||||
|
Class isa;
|
||||||
|
} *id;
|
||||||
|
id objc_msgSend(id, SEL, ...);
|
||||||
|
@interface NSObject {
|
||||||
|
Class isa;
|
||||||
|
}
|
||||||
|
+ (id)new;
|
||||||
|
- (id)init;
|
||||||
|
@end
|
||||||
|
#endif // TOUCHHLE_OBJC_SYSTEM_H
|
||||||
@@ -1,7 +1,9 @@
|
|||||||
use std::env;
|
use std::env;
|
||||||
use std::env::current_dir;
|
use std::env::current_dir;
|
||||||
use std::error::Error;
|
use std::error::Error;
|
||||||
use std::io::Write;
|
use std::ffi::{OsStr, OsString};
|
||||||
|
use std::fs::File;
|
||||||
|
use std::io::{BufRead, BufReader, BufWriter, Write};
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
use std::process::Command;
|
use std::process::Command;
|
||||||
|
|
||||||
@@ -26,99 +28,230 @@ fn find_subsequence(haystack: &[u8], needle: &[u8]) -> Option<usize> {
|
|||||||
.position(|window| window == needle)
|
.position(|window| window == needle)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn build_test_app(tests_dir: &Path, test_app_path: &Path) -> Result<(), Box<dyn Error>> {
|
/// Makes a path object, and checks that it exists.
|
||||||
let clang_path = tests_dir
|
fn make_path_and_check(
|
||||||
.join("llvm")
|
tests_dir: &Path,
|
||||||
.join("bin")
|
path: &[&str],
|
||||||
.join(format!("clang{}", env::consts::EXE_SUFFIX));
|
object: &str,
|
||||||
|
is_executable: bool,
|
||||||
|
) -> PathBuf {
|
||||||
|
let mut buf = tests_dir.to_path_buf();
|
||||||
|
if is_executable {
|
||||||
|
for part in &path[..(path.len() - 1)] {
|
||||||
|
buf.push(part);
|
||||||
|
}
|
||||||
|
buf.push(format!(
|
||||||
|
"{}{}",
|
||||||
|
path.last().unwrap(),
|
||||||
|
env::consts::EXE_SUFFIX
|
||||||
|
));
|
||||||
|
println!("{}", buf.iter().last().unwrap().display())
|
||||||
|
} else {
|
||||||
|
for part in path {
|
||||||
|
buf.push(part);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if !clang_path.exists() {
|
if !buf.exists() {
|
||||||
panic!(
|
panic!(
|
||||||
"Couldn't find Clang at {}. Please see {} for more details.",
|
"Couldn't find {} at {}. Please see {} for more details.",
|
||||||
clang_path.display(),
|
object,
|
||||||
|
buf.display(),
|
||||||
tests_dir.join("README.md").display()
|
tests_dir.join("README.md").display()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
buf
|
||||||
|
}
|
||||||
|
|
||||||
let test_bin_path = test_app_path.join("TestApp");
|
fn generate_libc_stub<'a, F: std::io::Write, S: AsRef<str>, I: Iterator<Item = S>>(
|
||||||
|
output: &mut F,
|
||||||
eprintln!("Building {} for iPhone OS 2...", test_bin_path.display());
|
symbols: &mut I,
|
||||||
|
) -> Result<(), Box<dyn Error>> {
|
||||||
let mut cmd = Command::new(clang_path);
|
for symbol in symbols {
|
||||||
|
// Need to strip off leading underscore
|
||||||
let output = cmd
|
let symbol = &symbol.as_ref().trim();
|
||||||
// Use upstream LLVM linker (not system linker)
|
assert!(
|
||||||
.arg("-fuse-ld=lld")
|
symbol.chars().nth(0).unwrap() == '_',
|
||||||
// On macOS only, Clang tries to use flags that ld64.lld doesn't
|
"symbol {} does not start with '_'",
|
||||||
// support. Perhaps it's confused and thinks it's invoking Apple's ld64?
|
symbol
|
||||||
// Telling it not to use newer flags like this seems to avoid this, but
|
);
|
||||||
// I suspect there may be a better fix.
|
writeln!(output, "void {}() {{}}", &symbol[1..])?;
|
||||||
.arg("-mlinker-version=0")
|
}
|
||||||
// Target iPhone OS 2
|
|
||||||
.args(["-target", "armv6-apple-ios2"])
|
|
||||||
// We don't have a libc to link against, don't try
|
|
||||||
.arg("-nostdlib")
|
|
||||||
// If enabled, the stack protection causes a null pointer crash in some
|
|
||||||
// functions. This is probably because ___stack_chk_guard isn't linked.
|
|
||||||
.arg("-fno-stack-protector")
|
|
||||||
// Pass four args to the linker:
|
|
||||||
// `-e _main` sets the mangled C main() function as the entry point
|
|
||||||
// (normally the libc provides an entry point calling main(), but we
|
|
||||||
// have no libc)
|
|
||||||
// `-undefined dynamic_lookup` makes the linker tolerate undefined
|
|
||||||
// references, falling back to dynamic linking instead. This is needed
|
|
||||||
// because we have no system libraries/frameworks for it to link to.
|
|
||||||
.arg("-Wl,-e,_main,-undefined,dynamic_lookup")
|
|
||||||
// Avoid warnings about "magnitude of floating-point constant too large"
|
|
||||||
.arg("-Wno-literal-range")
|
|
||||||
// Input
|
|
||||||
.arg(tests_dir.join("TestApp_source").join("main.c"))
|
|
||||||
// Write the output to the bundle.
|
|
||||||
.arg("-o")
|
|
||||||
.arg(&test_bin_path)
|
|
||||||
.output()
|
|
||||||
.expect("failed to execute Clang process");
|
|
||||||
|
|
||||||
std::io::stdout().write_all(&output.stdout).unwrap();
|
|
||||||
std::io::stderr().write_all(&output.stderr).unwrap();
|
|
||||||
|
|
||||||
assert!(output.status.success());
|
|
||||||
|
|
||||||
eprintln!("Built successfully.");
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
fn build_object<I: Iterator<Item = P>, P: AsRef<OsStr>>(
|
||||||
fn run_test_app() -> Result<(), Box<dyn Error>> {
|
tests_dir: &Path,
|
||||||
let tests_dir = current_dir()?.join("tests");
|
output_name: &Path,
|
||||||
|
sources: I,
|
||||||
|
extra_compile_args: &[&str],
|
||||||
|
) -> Result<(), Box<dyn Error>> {
|
||||||
|
let clang_path = make_path_and_check(tests_dir, &["llvm", "bin", "clang"], "Clang", true);
|
||||||
|
|
||||||
let test_app_path = tests_dir.join("TestApp.app");
|
let bin_path = make_path_and_check(
|
||||||
|
tests_dir,
|
||||||
|
&["common-3.0.sdk", "usr", "bin"],
|
||||||
|
"binary directory",
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
|
||||||
build_test_app(&tests_dir, &test_app_path)?;
|
let sdk_path = make_path_and_check(tests_dir, &["common-3.0.sdk"], "SDK sysroot", false);
|
||||||
|
|
||||||
|
let mut linker_arg = OsString::from("-B");
|
||||||
|
linker_arg.push(bin_path);
|
||||||
|
let mut sdk_arg = OsString::from("--sysroot=");
|
||||||
|
sdk_arg.push(sdk_path);
|
||||||
|
|
||||||
|
eprintln!("Building {} for iPhone OS 3...", output_name.display());
|
||||||
|
std::io::stderr().flush().unwrap();
|
||||||
|
let mut cmd = Command::new(clang_path);
|
||||||
|
let output = cmd
|
||||||
|
// Uncomment for verbose output (useful for debugging search path
|
||||||
|
// issues)
|
||||||
|
// .arg("-v")
|
||||||
|
// Uncomment for verbose linker output
|
||||||
|
// .arg("-Wl,-v")
|
||||||
|
// Target iPhone OS 2
|
||||||
|
.arg("--target=arm-apple-ios")
|
||||||
|
.arg("-miphoneos-version-min=2.0")
|
||||||
|
.args(["-arch", "armv7"])
|
||||||
|
// If enabled, the stack protection causes a null pointer crash in some
|
||||||
|
// functions. This is probably because ___stack_chk_guard isn't linked.
|
||||||
|
.arg("-fno-stack-protector")
|
||||||
|
.arg("-DPRODUCT_iPhone")
|
||||||
|
.arg(linker_arg)
|
||||||
|
.arg(sdk_arg)
|
||||||
|
.args(extra_compile_args)
|
||||||
|
// Input files.
|
||||||
|
.args(sources)
|
||||||
|
// Write the output to the bundle.
|
||||||
|
.arg("-o")
|
||||||
|
.arg(output_name)
|
||||||
|
.output()
|
||||||
|
.expect("failed to execute Clang process");
|
||||||
|
eprintln!("Running {:?}", cmd);
|
||||||
|
std::io::stdout().write_all(&output.stdout).unwrap();
|
||||||
|
std::io::stderr().write_all(&output.stderr).unwrap();
|
||||||
|
assert!(output.status.success());
|
||||||
|
eprintln!("Built successfully.");
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Note that source files are looked for in the path
|
||||||
|
// "{tests_dir}/{test_app_name}_source"
|
||||||
|
// and binaries are output as
|
||||||
|
// "{tests_dir}/{test_app_name}.app/{test_app_name}".
|
||||||
|
fn run_test_app(
|
||||||
|
tests_dir: &Path,
|
||||||
|
test_app_name: &str,
|
||||||
|
sources: &[&Path],
|
||||||
|
extra_compile_args: &[&str],
|
||||||
|
extra_run_args: &[&str],
|
||||||
|
) -> Result<(), Box<dyn Error>> {
|
||||||
|
let test_app_path = tests_dir.join(format!("{}.app", test_app_name));
|
||||||
|
build_object(
|
||||||
|
&tests_dir,
|
||||||
|
&tests_dir
|
||||||
|
.join(format!("{}.app", test_app_name))
|
||||||
|
.join(test_app_name),
|
||||||
|
sources.iter().map(|file| {
|
||||||
|
tests_dir
|
||||||
|
.join(format!("{}_source", test_app_name))
|
||||||
|
.join(file)
|
||||||
|
}),
|
||||||
|
extra_compile_args,
|
||||||
|
)?;
|
||||||
let binary_name = "touchHLE";
|
let binary_name = "touchHLE";
|
||||||
let binary_path = target_dir().join(format!("{}{}", binary_name, env::consts::EXE_SUFFIX));
|
let binary_path = target_dir().join(format!("{}{}", binary_name, env::consts::EXE_SUFFIX));
|
||||||
|
|
||||||
let mut cmd = Command::new(binary_path);
|
let mut cmd = Command::new(binary_path);
|
||||||
|
|
||||||
let output = cmd
|
let output = cmd
|
||||||
.arg(test_app_path)
|
.arg(test_app_path)
|
||||||
// headless mode avoids a distracting window briefly appearing during
|
// headless mode avoids a distracting window briefly appearing during
|
||||||
// testing, and works in CI.
|
// testing, and works in CI.
|
||||||
.arg("--headless")
|
.arg("--headless")
|
||||||
|
.args(extra_run_args)
|
||||||
.output()
|
.output()
|
||||||
.expect("failed to execute touchHLE process");
|
.expect("failed to execute touchHLE process");
|
||||||
|
|
||||||
std::io::stdout().write_all(&output.stdout).unwrap();
|
std::io::stdout().write_all(&output.stdout).unwrap();
|
||||||
std::io::stderr().write_all(&output.stderr).unwrap();
|
std::io::stderr().write_all(&output.stderr).unwrap();
|
||||||
|
|
||||||
assert!(output.status.success());
|
assert!(output.status.success());
|
||||||
// sanity check: check that emulation actually happened
|
// sanity check: check that emulation actually happened
|
||||||
assert_ne!(
|
assert_ne!(
|
||||||
find_subsequence(output.stderr.as_slice(), b"CPU emulation begins now."),
|
find_subsequence(output.stderr.as_slice(), b"CPU emulation begins now."),
|
||||||
None
|
None
|
||||||
);
|
);
|
||||||
|
write!(
|
||||||
|
&mut std::io::stdout(),
|
||||||
|
"Finished running {}.\n\n\n",
|
||||||
|
test_app_name
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_app() -> Result<(), Box<dyn Error>> {
|
||||||
|
let tests_dir = current_dir()?.join("tests");
|
||||||
|
let sdk_libs_dir = "-L".to_owned() + current_dir()?.join("touchHLE_dylibs").to_str().unwrap();
|
||||||
|
let libc_stub_dir = "-L".to_owned() + tests_dir.join("libc_stub").to_str().unwrap();
|
||||||
|
let extra_compile_args = [
|
||||||
|
"-mlinker-version=253",
|
||||||
|
"-Wno-expansion-to-defined",
|
||||||
|
"-Wno-literal-range",
|
||||||
|
sdk_libs_dir.as_str(),
|
||||||
|
libc_stub_dir.as_str(),
|
||||||
|
"-ObjC",
|
||||||
|
"-fno-objc-exceptions",
|
||||||
|
// ARC is not available until IOS 5, so it can't be used.
|
||||||
|
"-fno-objc-arc",
|
||||||
|
"-fno-objc-arc-exceptions",
|
||||||
|
];
|
||||||
|
|
||||||
|
// Generate symbols.
|
||||||
|
let symbols_path = tests_dir.join("SYMBOLS.txt");
|
||||||
|
let dump_file_option = format!("--dump-file={}", symbols_path.to_str().unwrap());
|
||||||
|
let dump_run_args = ["--dump=symbols", dump_file_option.as_str(), "--headless"];
|
||||||
|
let binary_name = "touchHLE";
|
||||||
|
let binary_path = target_dir().join(format!("{}{}", binary_name, env::consts::EXE_SUFFIX));
|
||||||
|
let mut cmd = Command::new(binary_path);
|
||||||
|
let output = cmd
|
||||||
|
.args(dump_run_args)
|
||||||
|
.output()
|
||||||
|
.expect("failed to execute touchHLE process");
|
||||||
|
assert!(output.status.success());
|
||||||
|
|
||||||
|
let symbols_file = BufReader::new(File::open(symbols_path).unwrap());
|
||||||
|
let mut output_file =
|
||||||
|
BufWriter::new(File::create(tests_dir.join("libc_stub").join("libc_stub.c")).unwrap());
|
||||||
|
generate_libc_stub(
|
||||||
|
&mut output_file,
|
||||||
|
&mut symbols_file.lines().map(|s| s.unwrap()),
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
// Close the file so it gets flushed
|
||||||
|
std::mem::drop(output_file);
|
||||||
|
let libc_compile_args = [
|
||||||
|
"-mlinker-version=253",
|
||||||
|
"-fno-builtin",
|
||||||
|
"-nostdlib",
|
||||||
|
&format!(
|
||||||
|
"-Wl,-install_name,{}",
|
||||||
|
Path::new("usr")
|
||||||
|
.join("lib")
|
||||||
|
.join("libSystem.B.dylib")
|
||||||
|
.to_str()
|
||||||
|
.unwrap()
|
||||||
|
),
|
||||||
|
"-Wl,-dylib",
|
||||||
|
];
|
||||||
|
build_object(
|
||||||
|
&tests_dir,
|
||||||
|
&tests_dir.join("libc_stub").join("libSystem.dylib"),
|
||||||
|
[tests_dir.join("libc_stub").join("libc_stub.c")].iter(),
|
||||||
|
&libc_compile_args,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let sources = ["main.m", "SyncTester.m"].map(|file| Path::new(file));
|
||||||
|
run_test_app(&tests_dir, "TestApp", &sources, &extra_compile_args, &[])
|
||||||
|
}
|
||||||
|
|||||||
0
tests/libc_stub/.gitignore
vendored
Normal file
0
tests/libc_stub/.gitignore
vendored
Normal file
Reference in New Issue
Block a user