Skip to content

Commit 63edcbe

Browse files
authored
feat(buildtool): build zlib, openssl, libevent, and tor for iOS (#1370)
This diff extends buildtool to builds zlib, openssl, libevent, and tor for iOS. We're only targeting 64 bit architectures, which is what ooni/probe-ios needs. We're targeting iOS >= 12.0, which is what ooni/probe-ios needs. A subsequent diff will introduce unit tests to make sure we don't break the iOS build. Reference issue: ooni/probe#2564. This diff was extracted from #1366.
1 parent ea4ef4e commit 63edcbe

File tree

9 files changed

+194
-1
lines changed

9 files changed

+194
-1
lines changed

.github/workflows/ios.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,9 @@ jobs:
3535
PSIPHON_CONFIG_KEY: ${{ secrets.PSIPHON_CONFIG_KEY }}
3636
PSIPHON_CONFIG_JSON_AGE_BASE64: ${{ secrets.PSIPHON_CONFIG_JSON_AGE_BASE64 }}
3737
38+
# ./internal/cmd/buildtool needs coreutils for sha256 plus GNU build tools
39+
- run: brew install autoconf automake coreutils libtool
40+
3841
- run: make EXPECTED_XCODE_VERSION=14.2 MOBILE/ios
3942

4043
- uses: actions/upload-artifact@v3

Makefile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,7 @@ android: search/for/java
9898
#help: The `make MOBILE/ios` command builds the oonimkall library for iOS.
9999
.PHONY: MOBILE/ios
100100
MOBILE/ios: search/for/zip search/for/xcode
101+
go run ./internal/cmd/buildtool ios cdeps zlib openssl libevent tor
101102
go run ./internal/cmd/buildtool ios gomobile
102103
./MOBILE/ios/zipframework
103104
./MOBILE/ios/createpodspec

internal/cmd/buildtool/android.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ func androidSubcommand() *cobra.Command {
4444
})
4545

4646
cmd.AddCommand(&cobra.Command{
47-
Use: "cdeps {zlib|openssl|libevent|tor} [zlib|openssl|libevent|tor...]",
47+
Use: "cdeps [zlib|openssl|libevent|tor...]",
4848
Short: "Cross compiles C dependencies for Android",
4949
Run: func(cmd *cobra.Command, args []string) {
5050
for _, arg := range args {

internal/cmd/buildtool/builddeps.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,3 +82,8 @@ func (*buildDeps) GOOS() string {
8282
func (*buildDeps) VerifySHA256(expectedSHA256 string, tarball string) {
8383
cdepsMustVerifySHA256(expectedSHA256, tarball)
8484
}
85+
86+
// XCRun implements buildtoolmodel.Dependencies
87+
func (*buildDeps) XCRun(args ...string) string {
88+
return iosXCRun(args...)
89+
}

internal/cmd/buildtool/internal/buildtoolmodel/buildtoolmodel.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,4 +52,8 @@ type Dependencies interface {
5252
// WindowsMingwCheck makes sure we're using the
5353
// expected version of mingw-w64.
5454
WindowsMingwCheck()
55+
56+
// XCRun executes Xcode's xcrun tool with the given arguments and returns
57+
// the first line of text emitted by xcrun or PANICS on failure.
58+
XCRun(args ...string) string
5559
}

internal/cmd/buildtool/internal/buildtooltest/buildtooltest.go

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -245,3 +245,22 @@ func (cc *DependenciesCallCounter) increment(name string) {
245245
}
246246
cc.Counter[name]++
247247
}
248+
249+
// XCRun implements buildtoolmodel.Dependencies.
250+
func (*DependenciesCallCounter) XCRun(args ...string) string {
251+
runtimex.Assert(len(args) >= 1, "expected at least one argument")
252+
switch args[0] {
253+
case "-sdk":
254+
runtimex.Assert(len(args) == 3, "expected three arguments")
255+
runtimex.Assert(args[2] == "--show-sdk-path", "the third argument must be --show-sdk-path")
256+
return filepath.Join("Developer", "SDKs", args[1])
257+
258+
case "-find":
259+
runtimex.Assert(len(args) == 4, "expected four arguments")
260+
runtimex.Assert(args[1] == "-sdk", "the second argument must be -sdk")
261+
return filepath.Join("Developer", "SDKs", args[2], "bin", args[3])
262+
263+
default:
264+
panic(errors.New("the first argument must be -sdk or -find"))
265+
}
266+
}

internal/cmd/buildtool/ios.go

Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,15 @@ package main
55
//
66

77
import (
8+
"errors"
9+
"fmt"
810
"path/filepath"
11+
"runtime"
912

1013
"github.com/apex/log"
1114
"github.com/ooni/probe-cli/v3/internal/cmd/buildtool/internal/buildtoolmodel"
15+
"github.com/ooni/probe-cli/v3/internal/must"
16+
"github.com/ooni/probe-cli/v3/internal/runtimex"
1217
"github.com/ooni/probe-cli/v3/internal/shellx"
1318
"github.com/spf13/cobra"
1419
)
@@ -19,13 +24,26 @@ func iosSubcommand() *cobra.Command {
1924
Use: "ios",
2025
Short: "Builds oonimkall and its dependencies for iOS",
2126
}
27+
2228
cmd.AddCommand(&cobra.Command{
2329
Use: "gomobile",
2430
Short: "Builds oonimkall for iOS using gomobile",
2531
Run: func(cmd *cobra.Command, args []string) {
2632
iosBuildGomobile(&buildDeps{})
2733
},
2834
})
35+
36+
cmd.AddCommand(&cobra.Command{
37+
Use: "cdeps [zlib|openssl|libevent|tor...]",
38+
Short: "Cross compiles C dependencies for iOS",
39+
Run: func(cmd *cobra.Command, args []string) {
40+
for _, arg := range args {
41+
iosCdepsBuildMain(arg, &buildDeps{})
42+
}
43+
},
44+
Args: cobra.MinimumNArgs(1),
45+
})
46+
2947
return cmd
3048
}
3149

@@ -41,6 +59,145 @@ func iosBuildGomobile(deps buildtoolmodel.Dependencies) {
4159
output: filepath.Join("MOBILE", "ios", "oonimkall.xcframework"),
4260
target: "ios",
4361
}
62+
4463
log.Info("building the mobile library using gomobile")
4564
gomobileBuild(config)
4665
}
66+
67+
// iosCdepsBuildMain builds C dependencies for ios.
68+
func iosCdepsBuildMain(name string, deps buildtoolmodel.Dependencies) {
69+
runtimex.Assert(runtime.GOOS == "darwin", "this command requires darwin")
70+
71+
// The ooni/probe-ios app explicitly only targets amd64 and arm64. It also targets
72+
// as the minimum version iOS 12, while one cannot target a version of iOS > 10 when
73+
// building for 32-bit targets. Hence, using only 64 bit archs here is fine.
74+
archs := []string{"arm64", "amd64"}
75+
for _, arch := range archs {
76+
iosCdepsBuildArch(deps, arch, name)
77+
}
78+
}
79+
80+
// iosPlatformForOONIArch maps the ooniArch to the iOS platform
81+
var iosPlatformForOONIArch = map[string]string{
82+
"amd64": "iphonesimulator",
83+
"arm64": "iphoneos",
84+
}
85+
86+
// iosAppleArchForOONIArch maps the ooniArch to the corresponding apple arch
87+
var iosAppleArchForOONIArch = map[string]string{
88+
"amd64": "x86_64",
89+
"arm64": "arm64",
90+
}
91+
92+
// iosMinVersionFlagForOONIArch maps the ooniArch to the corresponding compiler flag
93+
// to set the minimum version of either iphoneos or iphonesimulator.
94+
//
95+
// Note: the documentation of clang fetched on 2023-10-12 explicitly mentions that
96+
// ios-version-min is an alias for iphoneos-version-min. Likewise, ios-simulator-version-min
97+
// aliaes iphonesimulator-version-min.
98+
//
99+
// See https://clang.llvm.org/docs/ClangCommandLineReference.html#cmdoption-clang-mios-simulator-version-min
100+
var iosMinVersionFlagForOONIArch = map[string]string{
101+
"amd64": "-miphonesimulator-version-min=",
102+
"arm64": "-miphoneos-version-min=",
103+
}
104+
105+
// iosCdepsBuildArch builds the given dependency for the given arch
106+
func iosCdepsBuildArch(deps buildtoolmodel.Dependencies, ooniArch string, name string) {
107+
cdenv := iosNewCBuildEnv(deps, ooniArch)
108+
switch name {
109+
case "libevent":
110+
cdepsLibeventBuildMain(cdenv, deps)
111+
case "openssl":
112+
cdepsOpenSSLBuildMain(cdenv, deps)
113+
case "tor":
114+
cdepsTorBuildMain(cdenv, deps)
115+
case "zlib":
116+
cdepsZlibBuildMain(cdenv, deps)
117+
default:
118+
panic(fmt.Errorf("unknown dependency: %s", name))
119+
}
120+
}
121+
122+
// iosMinVersion is the minimum version that we support. We're using the
123+
// same value used by the ooni/probe-ios app as of 2023-10.12.
124+
const iosMinVersion = "12.0"
125+
126+
// iosNewCBuildEnv creates a new [cBuildEnv] for the given ooniArch ("arm64" or "amd64").
127+
func iosNewCBuildEnv(deps buildtoolmodel.Dependencies, ooniArch string) *cBuildEnv {
128+
destdir := runtimex.Try1(filepath.Abs(filepath.Join( // must be absolute
129+
"internal", "libtor", "ios", ooniArch,
130+
)))
131+
132+
var (
133+
appleArch = iosAppleArchForOONIArch[ooniArch]
134+
minVersionFlag = iosMinVersionFlagForOONIArch[ooniArch]
135+
platform = iosPlatformForOONIArch[ooniArch]
136+
)
137+
runtimex.Assert(appleArch != "", "empty appleArch")
138+
runtimex.Assert(minVersionFlag != "", "empty minVersionFlag")
139+
runtimex.Assert(platform != "", "empty platform")
140+
141+
isysroot := deps.XCRun("-sdk", platform, "--show-sdk-path")
142+
143+
out := &cBuildEnv{
144+
ANDROID_HOME: "", // not needed
145+
ANDROID_NDK_ROOT: "", // not needed
146+
AS: deps.XCRun("-find", "-sdk", platform, "as"),
147+
AR: deps.XCRun("-find", "-sdk", platform, "ar"),
148+
BINPATH: "", // not needed
149+
CC: deps.XCRun("-find", "-sdk", platform, "cc"),
150+
CFLAGS: []string{
151+
"-isysroot", isysroot,
152+
minVersionFlag + iosMinVersion, // tricky: they must be concatenated
153+
"-O2",
154+
"-arch", appleArch,
155+
"-fembed-bitcode",
156+
},
157+
CONFIGURE_HOST: "", // later
158+
DESTDIR: destdir,
159+
CXX: deps.XCRun("-find", "-sdk", platform, "c++"),
160+
CXXFLAGS: []string{
161+
"-isysroot", isysroot,
162+
minVersionFlag + iosMinVersion, // tricky: they must be concatenated
163+
"-arch", appleArch,
164+
"-fembed-bitcode",
165+
"-O2",
166+
},
167+
GOARCH: ooniArch,
168+
GOARM: "", // not needed
169+
LD: deps.XCRun("-find", "-sdk", platform, "ld"),
170+
LDFLAGS: []string{
171+
"-isysroot", isysroot,
172+
minVersionFlag + iosMinVersion, // tricky: they must be concatenated
173+
"-arch", appleArch,
174+
"-fembed-bitcode",
175+
},
176+
OPENSSL_COMPILER: "", // later
177+
OPENSSL_POST_COMPILER_FLAGS: []string{
178+
minVersionFlag + iosMinVersion, // tricky: they must be concatenated
179+
"-fembed-bitcode",
180+
},
181+
RANLIB: deps.XCRun("-find", "-sdk", platform, "ranlib"),
182+
STRIP: deps.XCRun("-find", "-sdk", platform, "strip"),
183+
}
184+
185+
switch ooniArch {
186+
case "arm64":
187+
out.CONFIGURE_HOST = "arm-apple-darwin"
188+
out.OPENSSL_COMPILER = "ios64-xcrun"
189+
case "amd64":
190+
out.CONFIGURE_HOST = "x86_64-apple-darwin"
191+
out.OPENSSL_COMPILER = "iossimulator-xcrun"
192+
default:
193+
panic(errors.New("unsupported ooniArch"))
194+
}
195+
196+
return out
197+
}
198+
199+
// iosXCRun invokes `xcrun [args]` and returns its result of panics. This function
200+
// is called indirectly by the iOS build through [buildtoolmodel.Dependencies].
201+
func iosXCRun(args ...string) string {
202+
return string(must.FirstLineBytes(must.RunOutput(log.Log, "xcrun", args...)))
203+
}

internal/libtor/ios/amd64/.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
/include
2+
/lib

internal/libtor/ios/arm64/.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
/include
2+
/lib

0 commit comments

Comments
 (0)