diff --git a/.gitignore b/.gitignore index 85e09d1..d2e35d4 100644 --- a/.gitignore +++ b/.gitignore @@ -5,8 +5,6 @@ bin offline config.json logs -view_log -wda_wrapper/go.sum temp dist.tgz cache/* diff --git a/Makefile b/Makefile index 36ed6d3..9a34735 100644 --- a/Makefile +++ b/Makefile @@ -98,10 +98,12 @@ repos/osx_ios_device_trigger/osx_ios_device_trigger/main.cpp: | repos/osx_ios_de # --- VIEW LOG --- -view_log: view_log.go - go get github.com/fsnotify/fsnotify - go get github.com/sirupsen/logrus - go build view_log.go +view_log: view_log/view_log + +view_log_sources: $(wildcard view_log/*.go) + +view_log/view_log: $(view_log_sources) + $(MAKE) -C view_log # --- H264_TO_JPEG --- @@ -202,7 +204,6 @@ bin/ios_video_pull: repos/ios_video_pull/ios_video_pull # --- WDA / WebDriverAgent --- repos/WebDriverAgent/Carthage/Checkouts/RoutingHTTPServer/Info.plist: | repos/WebDriverAgent - cd repos/WebDriverAgent && ./Scripts/bootstrap.sh wda: bin/wda/build_info.json @@ -270,7 +271,7 @@ clone: repos/WebDriverAgent repos/osx_ios_device_trigger repos/stf-ios-provider repos/stf-ios-provider/package.json: repos/stf-ios-provider repos/stf-ios-provider: - $(eval REPO=$(shell jq '.repo_stf // "https://github.com/nanoscopic/stf-ios-provider.git"' config.json -j)) + $(eval REPO=$(shell if test -f config.json; then jq '.repo_stf // "https://github.com/nanoscopic/stf-ios-provider.git"' -j config.json; else echo "https://github.com/nanoscopic/stf-ios-provider.git"; fi)) git clone $(REPO) repos/stf-ios-provider --branch master repos/ios_video_stream: @@ -282,8 +283,8 @@ repos/ios_video_pull: repos/WebDriverAgent/WebDriverAgent.xcodeproj: repos/WebDriverAgent repos/WebDriverAgent: - $(eval REPO=$(shell jq '.repo_wda // "https://github.com/appium/WebDriverAgent.git"' config.json -j)) - $(eval REPO_BR=$(shell jq '.repo_wda_branch // "master"' config.json -j)) + $(eval REPO=$(shell if test -f config.json; then jq '.repo_wda // "https://github.com/appium/WebDriverAgent.git"' -j config.json; else echo "https://github.com/appium/WebDriverAgent.git"; fi)) + $(eval REPO_BR=$(shell if test -f config.json; then jq '.repo_wda_branch // "master"' -j config.json; else echo "master"; fi)) git clone $(REPO) repos/WebDriverAgent --branch $(REPO_BR) repos/osx_ios_device_trigger: @@ -334,7 +335,7 @@ repos/libimobiledevice/Makefile: | repos/libimobiledevice stf: repos/stf-ios-provider/package-lock.json repos/stf-ios-provider/package-lock.json: repos/stf-ios-provider/package.json - cd repos/stf-ios-provider && PATH="/usr/local/opt/node@12/bin:$(PATH)" npm install + cd repos/stf-ios-provider && PATH="/usr/local/opt/node@14/bin:$(PATH)" npm install touch repos/stf-ios-provider/package-lock.json # --- OFFLINE STF --- @@ -406,7 +407,7 @@ dist.tgz: offline/build_info.json ios_video_stream wda device_trigger halias bin @if [ ! -f offline/logs/openvpn.log ]; then touch offline/logs/openvpn.log; fi; tar -h -czf dist.tgz $(distfiles) -C offline $(offlinefiles) -clean: cleanstf cleanwda cleanlogs cleanivs cleanwdaproxy cleanrunner +clean: cleanwda cleanviewlog cleanlogs cleanivs cleanwdaproxy cleanrunner $(MAKE) -C coordinator clean $(RM) build_info.json @@ -414,9 +415,6 @@ cleanwdaproxy: $(MAKE) -C repos/wdaproxy clean $(RM) bin/wdaproxy -cleanstf: - $(MAKE) -C repos/stf clean - cleanivs: $(MAKE) -C repos/ios_video_stream clean $(RM) bin/ios_video_stream @@ -432,6 +430,9 @@ cleanwda: cleanrunner: $(MAKE) -C runner clean +cleanviewlog: + $(MAKE) -C view_log clean + cleanlogs: $(RM) logs/* touch logs/.gitkeep diff --git a/README.md b/README.md index 147c5fb..11f2190 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,7 @@ ## STF IOS Support +[Actual instruction for deploy service on 2023/08/09](docs/DEPLOYMENT.md) + ### Prerequisites 1. A machine running MacOS ( to build and run the "provider" ) 1. A machine running Linux with Docker container support ( to run the STF server ) diff --git a/coordinator/coordinator.go b/coordinator/coordinator.go index c2f8647..001f854 100644 --- a/coordinator/coordinator.go +++ b/coordinator/coordinator.go @@ -832,25 +832,21 @@ func event_loop( if devd != nil && !devd.wdaStarted { o.config = devd.confDup - - if !o.config.Video.Enabled || - ( o.devd.okVidInterface == true && o.devd.okFirstFrame == true ) || - videoMethod == "app" { - o.devd.wdaStarted = true + + o.devd.wdaStarted = true - time.Sleep( time.Second * 2 ) + time.Sleep( time.Second * 2 ) - fmt.Printf("trying to get ios version\n") + fmt.Printf("trying to get ios version\n") - log.WithFields( log.Fields{ - "type": "ios_version", - "dev_name": o.devd.name, - "dev_uuid": uuid, - "ios_version": o.devd.iosVersion, - } ).Debug("IOS Version") + log.WithFields( log.Fields{ + "type": "ios_version", + "dev_name": o.devd.name, + "dev_uuid": uuid, + "ios_version": o.devd.iosVersion, + } ).Debug("IOS Version") - proc_wdaproxy( o, devEventCh, false ) - } + proc_wdaproxy( o, devEventCh, false ) } } } diff --git a/coordinator/go.mod b/coordinator/go.mod index 4d7df1b..7fa2c7b 100644 --- a/coordinator/go.mod +++ b/coordinator/go.mod @@ -1,13 +1,12 @@ module coordinator.go -go 1.12 +go 1.16 require ( - github.com/elastic/go-sysinfo v1.4.0 - github.com/fsnotify/fsnotify v1.4.7 - github.com/go-cmd/cmd v1.2.0 - github.com/jviney/go-proc v0.2.0 - github.com/nanoscopic/ujsonin v1.9.0 - github.com/sirupsen/logrus v1.4.2 + github.com/elastic/go-sysinfo v1.8.1 + github.com/fsnotify/fsnotify v1.6.0 + github.com/go-cmd/cmd v1.4.1 + github.com/nanoscopic/ujsonin v1.13.0 + github.com/sirupsen/logrus v1.9.0 github.com/zeromq/goczmq v4.1.0+incompatible ) diff --git a/coordinator/proc_device_unit.go b/coordinator/proc_device_unit.go index d2cda96..b93dbcf 100644 --- a/coordinator/proc_device_unit.go +++ b/coordinator/proc_device_unit.go @@ -115,7 +115,7 @@ func proc_device_ios_unit( o ProcOptions, uuid string, curIP string) { return true } o.procName = "stf_device_ios" - o.binary = "/usr/local/opt/node@12/bin/node" + o.binary = "/usr/local/opt/node@14/bin/node" o.startDir = "./repos/stf-ios-provider" proc_generic( o ) } diff --git a/coordinator/proc_stf_provider.go b/coordinator/proc_stf_provider.go index fd0cc2e..92f9836 100644 --- a/coordinator/proc_stf_provider.go +++ b/coordinator/proc_stf_provider.go @@ -26,7 +26,7 @@ func proc_stf_provider( o ProcOptions, curIP string ) { "server_hostname": serverHostname, "location": location, } - o.binary = "/usr/local/opt/node@12/bin/node" + o.binary = "/usr/local/opt/node@14/bin/node" o.args = []string { "--inspect=127.0.0.1:9230", "runmod.js" , "provider", diff --git a/coordinator/shutdown.go b/coordinator/shutdown.go index 41f77e3..0c08be4 100644 --- a/coordinator/shutdown.go +++ b/coordinator/shutdown.go @@ -65,7 +65,7 @@ func cleanup_procs(config *Config) { }*/ // node --inspect=[ip]:[port] runmod.js device-ios - if cmd[0] == "/usr/local/opt/node@12/bin/node" && cmd[3] == "device-ios" { + if cmd[0] == "/usr/local/opt/node@14/bin/node" && cmd[3] == "device-ios" { pid := proc.PID() plog.WithFields( log.Fields{ @@ -78,7 +78,7 @@ func cleanup_procs(config *Config) { } // node --inspect=[ip]:[port] runmod.js provider - if cmd[0] == "/usr/local/opt/node@12/bin/node" && cmd[3] == "provider" { + if cmd[0] == "/usr/local/opt/node@14/bin/node" && cmd[3] == "provider" { pid := proc.PID() plog.WithFields( log.Fields{ diff --git a/docs/DEPLOYMENT.md b/docs/DEPLOYMENT.md new file mode 100644 index 0000000..f50f4ee --- /dev/null +++ b/docs/DEPLOYMENT.md @@ -0,0 +1,287 @@ +# Deployment + +## Tested working configuration +- Built using: XCode 14.3.1 on MacOS Ventura 13.5 + +## 1. Prepare PC (build machine) +1. Install XCode +1. Add your developer Apple ID to XCode + + 1. XCode -> XCode menu -> Preferences -> Accounts Tab + 1. Click `+` under `Apple IDs` list + 1. Choose `Apple ID` + 1. Login to your account +1. Download a "Apple Development certificate" for your user + + 1. Continue from previous step, right after logging into your Developer account in Xcode + 1. Select `Manage Certificates` + 1. Click `+` in the lower left corner + 1. Select `Apple Development` +1. Install [Homebrew](https://docs.brew.sh/Installation) +1. Install Python 2.7 from MacPorts + 1. [Install MacPorts](https://www.macports.org/install.php) + 1. In terminal: `ports install python27` + +## 2. Prepare server STF +1. Add in NGINX config (in STF server deployment) block configuration for new provider: +- Client IP should be changed from `[^/]` to some more specific range such as: `(?192.168.255.[0-9]+)` to restrict it to a reasonable IP range +- If left alone this example config will let clients arbitrarily tunnel to any IP on ports `8000-8009` +``` +# MacOS +location ~ "^/frames/(?[^/]+)/(?800[0-9])/x$" { + proxy_pass http://$client_ip:$client_port/echo/; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection $connection_upgrade; + proxy_set_header X-Forwarded-For $remote_addr; + proxy_set_header X-Real-IP $remote_addr; +} +``` + +## 3. Prepare mobile devices for work with provider +1. Register(provision) your IOS device to your developer account as a developer device. + + 1. Use the API -or- + + 1. Follow https://developer.apple.com/documentation/appstoreconnectapi/creating_api_keys_for_app_store_connect_api to create + an app store connect API key. Give it Developer access. + 1. Gain a session using JSON Web Tokens + 1. Create a provisioning profile if none exist using profiles: https://developer.apple.com/documentation/appstoreconnectapi/profiles + See also https://github.com/cidertool/asc-go/blob/f08b8151f7fd92ff54924480338dafbf8a383255/asc/provisioning_profiles.go + 1. Post to the devices endpoint to register a device: https://developer.apple.com/documentation/appstoreconnectapi/devices + See also https://github.com/cidertool/asc-go/blob/f08b8151f7fd92ff54924480338dafbf8a383255/asc/provisioning_devices.go + 1. Follow these instructions: https://www.telerik.com/blogs/how-to-add-ios-devices-to-your-developer-profile + I couldn't find updated instructions on Apple's website. If you find them please let me know so I can link to them. +1. Plug in your IOS device to PC +1. Accept pairing on IOS device screen +1. Access device on PC: + + 1. Open `Finder` + 2. Click to device + 3. Accept pairing to device +1. Have Xcode setup the "developer image" on your IOS device: + + 1. Open Xcode + 1. Go to Windows... Devices and Simulators + 1. Wait while Developer Image is installed to your phone +1. Download actual profiles for device by Xcode or TestFlight: +https://docs.fastlane.tools/actions/sigh/ + +## 4. Prepare dependencies on build machine +1. Run `./init.sh` +1. View error: +```bash +mobiledevice => version 2.0.0 +libplist - Installing HEAD +Running `brew update --auto-update`... +==> Auto-updated Homebrew! +Updated 2 taps (homebrew/core and homebrew/cask). + +You have 32 outdated formulae installed. +You can upgrade them with brew upgrade +or list them with brew outdated. + +Cloning into '/Users/emb/Library/Caches/Homebrew/libplist--git'... +Already on 'master' +/usr/local/lib/pkgconfig/libplist.pc was missing; creating symlink to /usr/local/Cellar/libplist/HEAD-c3af449/lib/pkgconfig//libplist-2.0.pc +ln: /usr/local/lib/pkgconfig/libplist.pc: File exists +libusbmuxd - Installing HEAD +Already on 'master' +Please create pull requests instead of asking for help on Homebrew's GitHub, +Twitter or any other official channels. +Could not fix pkgconfig for libusbmuxd; could not locate installed pc file in Cellar +``` +1. Need manual install needed [libs](https://github.com/libimobiledevice), run: `brew install --HEAD libusbmuxd` +1. And view new error: +```bash +==> Cloning https://github.com/libimobiledevice/libusbmuxd.git +Cloning into '/Users/emb/Library/Caches/Homebrew/libusbmuxd--git'... +==> Checking out branch master +Already on 'master' +Your branch is up to date with 'origin/master'. +==> ./autogen.sh +Last 15 lines from /Users/emb/Library/Logs/Homebrew/libusbmuxd/01.autogen.sh: +checking whether to build static libraries... yes +checking for pkg-config... /usr/local/Homebrew/Library/Homebrew/shims/mac/super/pkg-config +checking pkg-config is at least version 0.9.0... yes +checking for libplist-2.0 >= 2.2.0... yes +checking for libimobiledevice-glue-1.0 >= 1.0.0... no +configure: error: Package requirements (libimobiledevice-glue-1.0 >= 1.0.0) were not met: + +No package 'libimobiledevice-glue-1.0' found + +Consider adjusting the PKG_CONFIG_PATH environment variable if you +installed software in a non-standard prefix. + +Alternatively, you may set the environment variables limd_glue_CFLAGS +and limd_glue_LIBS to avoid the need to call pkg-config. +See the pkg-config man page for more details. + +Do not report this issue to Homebrew/brew or Homebrew/core! + +Please create pull requests instead of asking for help on Homebrew's GitHub, +Twitter or any other official channels. +``` + +This error initial by new dependenses: https://github.com/libimobiledevice/libimobiledevice-glue + +1. Resolve: + + 1. `brew create --set-name libimobiledevice-glue-1.0 "https://github.com/libimobiledevice/libimobiledevice-glue.git"` + 1. Save file without changes + 1. Edit file: `brew edit libimobiledevice-glue-1.0` + + It text needed: + ```text + class LibimobiledeviceGlue10 < Formula + desc "" + homepage "" + url "https://github.com/libimobiledevice/libimobiledevice-glue.git" + head "https://github.com/libimobiledevice/libimobiledevice-glue.git" + version "1.0.0" + sha256 "" + license "" + + depends_on "autoconf" => :build + depends_on "automake" => :build + depends_on "libtool" => :build + depends_on "pkg-config" => :build + depends_on "libplist" + + def install + system "./autogen.sh" + system "./configure", "--disable-dependency-tracking", "--disable-silent-rules", "--prefix=#{prefix}" + system "make", "install" + end + + test do + system "false" + end + end + ``` + 1. Install library: `HOMEBREW_NO_INSTALL_FROM_API=1 HOMEBREW_NO_INSTALL_CLEANUP=1 brew install --HEAD libimobiledevice-glue-1.0` + 1. Manual fix symlinks: + ```bash + ./util/brewser.pl fixpc libimobiledevice-glue-1.0 1.0.0 + + cd /usr/local/lib/pkgconfig/ + rm libimobiledevice-glue-1.0.pc libimobiledevice-glue-1.0-1.0.0.pc + ln -s ../../Cellar/libimobiledevice-glue-1.0/HEAD-/lib/pkgconfig/libimobiledevice-glue-1.0.pc libimobiledevice-glue-1.0.pc + ln -s ../../Cellar/libimobiledevice-glue-1.0/HEAD-/lib/pkgconfig/libimobiledevice-glue-1.0.pc libimobiledevice-glue-1.0-1.0.0.pc + ls -al libimobiledevice-glue-1.0* + ``` + 1. Fix env: `export PKG_CONFIG_PATH=$(find /usr/local/Cellar -name 'pkgconfig' -type d | grep lib/pkgconfig | tr '\n' ':' | sed s/.$//)` + 1. Fix formulae: `brew edit libusbmuxd` + 1. Add in file new dependens: + ```text + depends_on "libimobiledevice-glue-1.0" + ``` + 1. Save file + 1. Install libs: + ```bash + HOMEBREW_NO_INSTALL_FROM_API=1 HOMEBREW_NO_INSTALL_CLEANUP=1 brew install --HEAD libusbmuxd + HOMEBREW_NO_INSTALL_FROM_API=1 HOMEBREW_NO_INSTALL_CLEANUP=1 brew install --HEAD libimobiledevice + ./util/brewser.pl fixpc libusbmuxd 2.0 + ./util/brewser.pl fixpc libimobiledevice 1.0 + ``` + +## 5. Prepare sources +1. Clone the various needed repos + + 1. Run `make clone` + +1. Configure WebDriverAgent to use your identity for signing + + 1. Open `repos/WebDriverAgent/WebDriverAgent.xcodeproj` in XCode + 1. Select the ***WebDriverAgentLib*** target + 1. Go to the `Signing & Capabilities` tab + 1. Select your team under `Team` + 1. Change ***Bundle Identifier*** to your + 1. Repeate this action for ***WebDriverAgentRunner***, ***UnitTests***, ***IntegrationTests_[1:3]*** and ***IntegrationApps*** + 1. Select in top target for builder: ***WebDriverAgentRunner*** + 1. Select in top physical device for builder + 1. Test build and run test: select in XCode menu `Product` and `Test` + 1. In log chech what start server and success + 1. Close XCode + +1. Create config: + + 1. Copy the first {} block from `config.json.example` into `config.json`. Do not include any comment lines starting with // + 1. Edit config.json + 1. Update `xcode_dev_team_id` to be the OU of your developer account. If you add your account into Xcode first, you can then run + `make ou` to display what the OU is. You can also find it by opening the keychain, selecting the Apple Development certificate + for your account, and then looking at what the `Organization Unit` is. + 1. Update `root_path` to be where provider code should be installed, such as `/Users/user/stf` + 1. Update `config_path` to match that, such as `/Users/user/stf/config.json` + 1. Set `use_vnc` to `false` (https://githubhelp.com/devicefarmer/stf_ios_support/issues/37) + 1. Sample: + ```json + { + "xcode_dev_team_id": "XXXXXXXXX", + "stf": { + "ip": "192.168.1.10", + "hostname": "stf.example.com" + }, + "video": { + "enabled": true, + "use_vnc": false, + "vnc_scale": 2, + "vnc_password": "", + "frame_rate": 10 + }, + "install": { + "root_path": "/Users/user/stf", + "config_path": "/Users/user/stf/config.json", + "set_working_dir": false + }, + "bin_paths": { + "video_enabler": "bin/video_enabler" + } + } + ``` + +## 6. Build and package application +1. Fix env before build: +```bash +export PKG_CONFIG_PATH=$(find /usr/local/Cellar -name 'pkgconfig' -type d | grep lib/pkgconfig | tr '\n' ':' | sed s/.$//) +``` +1. Fix [error](https://juejin.cn/post/6985818094794965006#heading-6) repos `wdaproxy`: +```bash +cd repos/wdaproxy +go get github.com/DHowett/go-plist@v0.0.0-20170330020707-795cf23fd27f +go install github.com/DHowett/go-plist@v0.0.0-20170330020707-795cf23fd27f +go mod tidy +``` +1. Build: `make` +1. Run then `make dist` + + 1. ***dist.tgz*** will be created + +## 7. Deploy provider setup: +1. Copy `dist.tgz` from build machine +1. Run `tar -xf dist.tgz` +1. Tweak `config.json` as desired + +## 8. Starting provider +1. Check plugging devices: `idevice_id` +1. Pair its with your system + + 1. Run `idevicepair pair` + 1. Accept pairing on IOS device screen +1. Activate video by devices + + 1. Activate screen devices + 1. Run ***QuickTime Player*** in GUI Mac + 1. `File` --> `New video` + 1. Choice device + 1. Check what video work + 1. Exit ***QuickTime Player*** + 1. Check what video activate on all physical devices: + ```bash + ./bin/ivf_pull list + ``` +1. Run `./bin/ios_video_pull -devices -decimal` to determine the PID ( product ID ) of your IOS device in decimal +1. Run `./bin/devreset [decimal product ID] 1452` to reset the video streaming status of your IOS device +1. Activate screen on all physical devices and run `./run` ( and leave it running ) +1. Permissions dialog boxes appear for coordinator to listen on various ports; select accept for all of them +1. Device shows up in STF with video and can be controlled. diff --git a/get-version-info.sh b/get-version-info.sh index 2c458f8..f507688 100755 --- a/get-version-info.sh +++ b/get-version-info.sh @@ -1,4 +1,4 @@ -#!/usr/bin/python +#!/opt/local/bin/python2.7 import json import os diff --git a/init.sh b/init.sh index 4335325..8eb457d 100755 --- a/init.sh +++ b/init.sh @@ -43,4 +43,6 @@ assert_has_xcodebuild ./util/brewser.pl fixpc libplist 2.0 ./util/brewser.pl ensurehead libusbmuxd 2.0.3 ./util/brewser.pl fixpc libusbmuxd 2.0 +./util/brewser.pl ensurehead libimobiledevice 1.3.1 +./util/brewser.pl fixpc libimobiledevice 1.0 #make libimd \ No newline at end of file diff --git a/stf_ios_support.rb b/stf_ios_support.rb index eb6ceac..30bdeaa 100644 --- a/stf_ios_support.rb +++ b/stf_ios_support.rb @@ -24,8 +24,10 @@ def install depends_on "wget" # depends_on "libimobiledevice" # need to install with --HEAD depends_on "go" => :build + depends_on "ossp-uuid" + depends_on "socat" depends_on :xcode => "10.3" - depends_on "node@12" + depends_on "node@14" depends_on "libsodium" depends_on "czmq" depends_on "jpeg-turbo" diff --git a/view_log/Makefile b/view_log/Makefile new file mode 100644 index 0000000..d98c44b --- /dev/null +++ b/view_log/Makefile @@ -0,0 +1,13 @@ +TARGET = ../view_log + +all: $(TARGET) + +$(TARGET): view_log.go go.sum + go build -o $(TARGET) . + +go.sum: + go get + go get . + +clean: + $(RM) $(TARGET) go.sum \ No newline at end of file diff --git a/view_log/go.mod b/view_log/go.mod new file mode 100644 index 0000000..6a0e898 --- /dev/null +++ b/view_log/go.mod @@ -0,0 +1,8 @@ +module view_log + +go 1.16 + +require ( + github.com/fsnotify/fsnotify v1.6.0 + github.com/sirupsen/logrus v1.9.0 +) diff --git a/view_log.go b/view_log/view_log.go similarity index 100% rename from view_log.go rename to view_log/view_log.go diff --git a/wda_wrapper/Makefile b/wda_wrapper/Makefile index d5e5986..b108970 100644 --- a/wda_wrapper/Makefile +++ b/wda_wrapper/Makefile @@ -3,7 +3,7 @@ TARGET = ../bin/wda_wrapper all: $(TARGET) $(TARGET): wda_wrapper.go go.sum - go build -o $(TARGET) . + go build -o $(TARGET) . go.sum: go get diff --git a/wda_wrapper/go.mod b/wda_wrapper/go.mod index a81d8d0..cd1069a 100644 --- a/wda_wrapper/go.mod +++ b/wda_wrapper/go.mod @@ -1,10 +1,9 @@ module wda_wrapper -go 1.12 +go 1.16 require ( - github.com/go-cmd/cmd v1.2.0 - github.com/jviney/go-proc v0.2.0 - github.com/sirupsen/logrus v1.4.2 + github.com/go-cmd/cmd v1.4.1 + github.com/sirupsen/logrus v1.9.0 github.com/zeromq/goczmq v4.1.0+incompatible )