-
Notifications
You must be signed in to change notification settings - Fork 843
Docs: add ARM64 cross-compile instructions for Windows and Linux #1116
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
val-ms
merged 2 commits into
Cisco-Talos:main
from
val-ms:CLAM-2463-CLAM-2088-cross-compile-instructions
Mar 26, 2024
Merged
Changes from 1 commit
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,263 @@ | ||
| # Cross-compiling ClamAV on Linux for arm64 | ||
|
|
||
| These are instructions to cross-compile ClamAV on Linux amd64 (`x86_64-unknown-linux-gnu`) with GCC for Linux arm64 (`aarch64-unknown-linux-gnu`). | ||
|
|
||
| > _Note_: These build instructions were written for Ubuntu. You may need to change a few steps to work with your distro. | ||
|
|
||
| ## Install build tools, if missing | ||
|
|
||
| Install the GCC/G++ and Rust toolchains needed to cross-compile to aarch64: | ||
|
|
||
| ```bash | ||
| # Install toolchain | ||
| sudo apt install -y g++-aarch64-linux-gnu | ||
| rustup target add aarch64-unknown-linux-gnu | ||
| ``` | ||
|
|
||
| ## Install build dependencies | ||
|
|
||
| If you have a sysroot for your `aarch64-unknown-linux-gnu` target platform with the required dependencies installed, skip this step. Else, do these things to install arm64 (aarch64) versions of the ClamAV library dependencies on the local host. | ||
|
|
||
| ```bash | ||
| sudo dpkg --add-architecture arm64 | ||
| ``` | ||
|
|
||
| Create a new .list file in `/etc/apt/sources.list.d`: | ||
|
|
||
| ```bash | ||
| sudo vim /etc/apt/sources.list.d/arm-cross-compile-sources.list | ||
| ``` | ||
|
|
||
| Add arm64 package sources to this new list: | ||
| ``` | ||
| deb [arch=arm64] http://ports.ubuntu.com/ focal main restricted | ||
| deb [arch=arm64] http://ports.ubuntu.com/ focal-updates main restricted | ||
| deb [arch=arm64] http://ports.ubuntu.com/ focal universe | ||
| deb [arch=arm64] http://ports.ubuntu.com/ focal-updates universe | ||
| deb [arch=arm64] http://ports.ubuntu.com/ focal multiverse | ||
| deb [arch=arm64] http://ports.ubuntu.com/ focal-updates multiverse | ||
| deb [arch=arm64] http://ports.ubuntu.com/ focal-backports main restricted universe multiverse | ||
| ``` | ||
|
|
||
| > _Tip_: "focal" is for Ubuntu 20.04LTS. You may need to swap to another to match your release: | ||
| > - focal (20.04LTS) | ||
| > - jammy (22.04LTS) | ||
| > - kinetic (22.10) | ||
| > - lunar (23.04) | ||
| > - mantic (23.10) | ||
| > | ||
| > See https://packages.ubuntu.com/ for more. | ||
|
|
||
| Now install the arm64 libraries: | ||
|
|
||
| ```bash | ||
| apt-get update && apt-get install -y \ | ||
| check:arm64 \ | ||
| libbz2-dev:arm64 \ | ||
| libcurl4-openssl-dev:arm64 \ | ||
| libjson-c-dev:arm64 \ | ||
| libmilter-dev:arm64 \ | ||
| libncurses5-dev:arm64 \ | ||
| libpcre2-dev:arm64 \ | ||
| libssl-dev:arm64 \ | ||
| libxml2-dev:arm64 \ | ||
| zlib1g-dev:arm64 | ||
| ``` | ||
|
|
||
| After install, the `.a` and `.so` libraries will be found under `/usr/lib/aarch64-linux-gnu/`. The headers are the same as for any other arch, so those will be under `/usr/include/` as per usual. | ||
|
|
||
| ## Create a CMake toolchain file | ||
|
|
||
| A CMake toolchain file specifies some toolchain specific variables. | ||
|
|
||
| Note: The `CMAKE_SYSROOT` variable may **not** be set using the `cmake -D CMAKE_SYROOT=PATH` method and must be in this file. Meanwhile, some other variables (namely `CMAKE_INSTALL_PREFIX`) *cannot* be set in the toolchain file, and should be passed as a command parameter. | ||
|
|
||
| ### If using a sysroot | ||
|
|
||
| `CMAKE_TOOLCHAIN_ARM64.cmake`: | ||
| ```cmake | ||
| # Platform | ||
| set(CMAKE_SYSTEM_NAME Linux) | ||
| set(CMAKE_SYSTEM_PROCESSOR arm64) | ||
| set(CMAKE_C_COMPILER "aarch64-linux-gnu-gcc") | ||
| set(CMAKE_CXX_COMPILER "aarch64-linux-gnu-g++") | ||
| set(RUST_COMPILER_TARGET "aarch64-unknown-linux-gnu") | ||
|
|
||
| # Project Variables needed to cross compile | ||
| set(HAVE_ATTRIB_ALIGNED 1) | ||
| set(HAVE_ATTRIB_PACKED 1) | ||
| set(HAVE_UNAME_SYSCALL 1) | ||
| set(HAVE_SAR 1) | ||
| set(HAVE_FD_PASSING 1) | ||
| set(MMAP_FOR_CROSSCOMPILING ON) | ||
| set(ENABLE_SYSTEMD OFF) | ||
|
|
||
| set( test_run_result | ||
| "PLEASE_FILL_OUT-FAILED_TO_RUN" | ||
| CACHE STRING "Result from try_run" FORCE) | ||
|
|
||
| set( test_run_result__TRYRUN_OUTPUT | ||
| "PLEASE_FILL_OUT-NOTFOUND" | ||
| CACHE STRING "Output from try_run" FORCE) | ||
|
|
||
| # | ||
| # Dependencies | ||
| # | ||
|
|
||
| # If using a sysroot / rootfs for the target, set these. | ||
| set(CMAKE_SYSROOT /opt/aarch64-wrs-linux-sysroot) | ||
|
|
||
| # If your CMAKE_SYSROOT directory is readonly, or for some reason you want to install to a different staging prefix before copying to your host, set this: | ||
| #set(CMAKE_STAGING_PREFIX /home/user/stage) | ||
|
|
||
| # Note, you may need to set ENABLE_JSON_SHARED if your sysroot provides libjson-c.so instead of libjson-c.a. | ||
| #set(ENABLE_JSON_SHARED ON) | ||
|
|
||
| # You may need to set the following if CMake has some trouble finding the depenencies. | ||
| # For example if you have `libjson-c.a` in your sysroot, here: `/opt/aarch64-wrs-linux-sysroot/usr/lib64/libjson-c.a` | ||
| # then you would set: | ||
| #set(JSONC_LIBRARY "/usr/lib64/libjson-c.a") | ||
|
|
||
| # | ||
| # Uncomment these as needed: | ||
| # | ||
| #set(JSONC_INCLUDE_DIR "/usr/include/json-c") | ||
| #set(JSONC_LIBRARY "/usr/lib64/libjson-c.a") | ||
| #set(ENABLE_JSON_SHARED OFF) | ||
|
|
||
| #set(BZIP2_INCLUDE_DIR "/usr/include/") | ||
| #set(BZIP2_LIBRARY "/usr/lib64/libbz2.a") | ||
|
|
||
| #set(OPENSSL_ROOT_DIR "/usr/") | ||
| #set(OPENSSL_INCLUDE_DIR "/usr/include/") | ||
| #set(OPENSSL_CRYPTO_LIBRARY "/usr/lib64/libcrypto.so") | ||
| #set(OPENSSL_SSL_LIBRARY "/usr/lib64/libssl.so") | ||
|
|
||
| #set(LIBXML2_INCLUDE_DIR "/usr/include/libxml2") | ||
| #set(LIBXML2_LIBRARY "/usr/lib64/libxml2.so") | ||
|
|
||
| #set(PCRE2_INCLUDE_DIR "/usr/include/") | ||
| #set(PCRE2_LIBRARY "/usr/lib64/libpcre2-8.so") | ||
|
|
||
| #set(CURSES_INCLUDE_DIR "/usr/include/") | ||
| #set(CURSES_LIBRARY "/usr/lib/aarch64-linux-gnu/libncurses.a;/usr/lib/aarch64-linux-gnu/libtinfo.a") | ||
| # Tip: You may not need to also link with libtinfo.a, depending on what your distribution provides: | ||
| #set(CURSES_LIBRARY "/usr/lib/aarch64-linux-gnu/libncurses.a") | ||
| # Tip: Alternatively, you could link with the shared library: | ||
| #set(CURSES_LIBRARY "/usr/lib/aarch64-linux-gnu/libncurses.so") | ||
|
|
||
| #set(ZLIB_INCLUDE_DIR "/usr/include/") | ||
| #set(ZLIB_LIBRARY "/usr/lib64/libz.so") | ||
|
|
||
| #set(LIBCHECK_INCLUDE_DIR "/usr/include/") | ||
| #set(LIBCHECK_LIBRARY "/usr/lib64/libcheck.a") | ||
| ``` | ||
|
|
||
| ### If not using a sysroot | ||
|
|
||
| Without a sysroot, you must tell CMake exactly where to find the library dependencies built for aarch64. | ||
|
|
||
| > _IMPORTANT_: Without a sysroot, your runtime platform must have these EXACT SAME libraries. | ||
|
|
||
| `CMAKE_TOOLCHAIN_ARM64.cmake`: | ||
| ```cmake | ||
| # Platform | ||
| set(CMAKE_SYSTEM_NAME Linux) | ||
| set(CMAKE_SYSTEM_PROCESSOR arm64) | ||
| set(CMAKE_C_COMPILER "aarch64-linux-gnu-gcc") | ||
| set(CMAKE_CXX_COMPILER "aarch64-linux-gnu-g++") | ||
| set(RUST_COMPILER_TARGET "aarch64-unknown-linux-gnu") | ||
|
|
||
| # Project Variables needed to cross compile | ||
| set(HAVE_ATTRIB_ALIGNED 1) | ||
| set(HAVE_ATTRIB_PACKED 1) | ||
| set(HAVE_UNAME_SYSCALL 1) | ||
| set(HAVE_SAR 1) | ||
| set(HAVE_FD_PASSING 1) | ||
| set(MMAP_FOR_CROSSCOMPILING ON) | ||
| set(ENABLE_SYSTEMD OFF) | ||
|
|
||
| set( test_run_result | ||
| "PLEASE_FILL_OUT-FAILED_TO_RUN" | ||
| CACHE STRING "Result from try_run" FORCE) | ||
|
|
||
| set( test_run_result__TRYRUN_OUTPUT | ||
| "PLEASE_FILL_OUT-NOTFOUND" | ||
| CACHE STRING "Output from try_run" FORCE) | ||
|
|
||
| # | ||
| # Dependencies | ||
| # | ||
|
|
||
| set(JSONC_INCLUDE_DIR "/usr/include/json-c") | ||
| set(JSONC_LIBRARY "/usr/lib/aarch64-linux-gnu/libjson-c.a") | ||
| set(ENABLE_JSON_SHARED OFF) | ||
|
|
||
| set(BZIP2_INCLUDE_DIR "/usr/include/") | ||
| set(BZIP2_LIBRARY "/usr/lib/aarch64-linux-gnu/libbz2.a") | ||
|
|
||
| set(OPENSSL_ROOT_DIR "/usr/") | ||
| set(OPENSSL_INCLUDE_DIR "/usr/include/") | ||
| set(OPENSSL_CRYPTO_LIBRARY "/usr/lib/aarch64-linux-gnu/libcrypto.so") | ||
| set(OPENSSL_SSL_LIBRARY "/usr/lib/aarch64-linux-gnu/libssl.so") | ||
|
|
||
| set(LIBXML2_INCLUDE_DIR "/usr/include/libxml2") | ||
| set(LIBXML2_LIBRARY "/usr/lib/aarch64-linux-gnu/libxml2.so") | ||
|
|
||
| set(PCRE2_INCLUDE_DIR "/usr/include/") | ||
| set(PCRE2_LIBRARY "/usr/lib/aarch64-linux-gnu/libpcre2-8.so") | ||
|
|
||
| set(CURSES_INCLUDE_DIR "/usr/include/") | ||
| set(CURSES_LIBRARY "/usr/lib/aarch64-linux-gnu/libncurses.a;/usr/lib/aarch64-linux-gnu/libtinfo.a") | ||
| # Tip: You may not need to also link with libtinfo.a, depending on what your distribution provides: | ||
| #set(CURSES_LIBRARY "/usr/lib/aarch64-linux-gnu/libncurses.a") | ||
| # Tip: Alternatively, you could link with the shared library: | ||
| #set(CURSES_LIBRARY "/usr/lib/aarch64-linux-gnu/libncurses.so") | ||
|
|
||
| set(ZLIB_INCLUDE_DIR "/usr/include/") | ||
| set(ZLIB_LIBRARY "/usr/lib/aarch64-linux-gnu/libz.so") | ||
|
|
||
| set(LIBCHECK_INCLUDE_DIR "/usr/include/") | ||
| set(LIBCHECK_LIBRARY "/usr/lib/aarch64-linux-gnu/libcheck.a") | ||
| ``` | ||
|
|
||
| ## Build ClamAV | ||
|
|
||
| You may need to adjust the paths in the command below to suit your needs. | ||
|
|
||
| You'll definitely need to set `CMAKE_STAGING_PREFIX` to your own path, or maybe remove it (see the note, below). | ||
|
|
||
| You may wish to set `CMAKE_INSTALL_PREFIX` to some directory other than `/usr` | ||
|
|
||
| > _Note_: If using a sysroot and `CMAKE_SYSROOT` is set in your `CMAKE_TOOLCHAIN_ARM64.cmake` file, then the `make install` command will install to that sysroot directory. If you want, you can override it with `CMAKE_STAGING_PREFIX`. After the `make install`, it will be on you to copy the stuff from your staging directory to target system. The instructions below do this, because you may not wish to contaminate your sysroot with output from this build, or because your sysroot may be read-only. | ||
|
|
||
| ```bash | ||
| mkdir build-arm64 && cd build-arm64 | ||
|
|
||
| cmake .. \ | ||
| -D CMAKE_TOOLCHAIN_FILE=(pwd)/../CMAKE_TOOLCHAIN_ARM64.cmake \ | ||
| -D ENABLE_STATIC_LIB=OFF \ | ||
| -D ENABLE_SHARED_LIB=ON \ | ||
| -D MAINTAINER_MODE=OFF \ | ||
| -D ENABLE_EXAMPLES=OFF \ | ||
| -D BYTECODE_RUNTIME=interpreter \ | ||
| -D CMAKE_BUILD_TYPE=Release \ | ||
| -D CMAKE_INSTALL_PREFIX="/usr" \ | ||
| -D CMAKE_STAGING_PREFIX=/home/user/stage/usr | ||
|
|
||
| make | ||
| make install | ||
| ``` | ||
|
|
||
| ## Verify it built for right platform | ||
|
|
||
| We cannot run the ClamAV unit test suite, because we're cross compiling and can't run the programs we build. But we can do a very small test with the Unix `file` command to see that it built for the right platform. For example: | ||
|
|
||
| ```bash | ||
| file install/bin/clamscan | ||
| ``` | ||
|
|
||
| Example output: | ||
| `install/bin/clamscan: ELF 64-bit LSB shared object, ARM aarch64, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux-aarch64.so.1, BuildID[sha1]=289a6b738e7421c5bb09c7ee5fc5bb20bfe98025, for GNU/Linux 3.7.0, with debug_info, not stripped` | ||
|
|
||
| If everything looks good, you can probably copy the install files to your system and run it. | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,124 @@ | ||
| # Cross-compiling ClamAV on Windows for ARM64 | ||
|
|
||
| These are instructions to cross-compile ClamAV on Windows x64 (`x86_64-pc-windows-msvc`) with GCC for Linux arm64 (`aarch64-pc-windows-msvc`). | ||
|
|
||
| ## Install build tools, if missing | ||
|
|
||
| Use the Visual Studio Installer tools to add the ARM64 components. E.g. this stuff: | ||
| - MSVC v143 - VS 2022 C++ ARM build tools (Latest) | ||
| - MSVC v143 - VS 2022 C++ ARM Spectre-mitigated libs (Latest) | ||
| - MSVC v143 - VS 2022 C++ ARM64/ARM64EC build tools (Latest) | ||
| - MSVC v143 - VS 2022 C++ ARM64/ARM64EC Spectre-mitigated libs (Latest) | ||
| - C++ ATL for latest v143 build tools (ARM) | ||
| - C++ ATL for latest v143 build tools (ARM64/ARM64EC) | ||
| - C++ ATL for latest v143 build tools with Spectre-Mitigations (ARM) | ||
| - C++ ATL for latest v143 build tools with Spectre-Mitigations (ARM64/ARM64EC) | ||
| - C++ MFC for latest v143 build tools (ARM) | ||
| - C++ MFC for latest v143 build tools (ARM64/ARM64EC) | ||
| - C++ MFC for latest v143 build tools with Spectre-Mitigations (ARM) | ||
| - C++ MFC for latest v143 build tools with Spectre-Mitigations (ARM64/ARM64EC) | ||
|
|
||
| Install the Rust toolchains needed to cross-compile to arm64: | ||
|
|
||
| ```powershell | ||
| rustup target add aarch64-pc-windows-msvc | ||
| ``` | ||
|
|
||
| ## Use Mussels to build ARM64 C-based library dependencies | ||
|
|
||
| See the [online documentation regarding building dependencies with Mussels](https://docs.clamav.net/manual/Development/build-installer-packages.html#windows). To build for ARM64, change the commands to build like this: | ||
|
|
||
| ```powershell | ||
| msl build -t arm64 clamav_deps | ||
| ``` | ||
|
|
||
| Once the build is complete, you'll find the ARM64 compiled libraries under `~\.mussels\install\arm64\`. | ||
|
|
||
| ## Create a CMake toolchain file | ||
|
|
||
| A CMake toolchain file specifies some toolchain specific variables. | ||
|
|
||
| `CMAKE_TOOLCHAIN_ARM64.cmake`: | ||
| ```cmake | ||
| # Platform | ||
| set(CMAKE_SYSTEM_NAME Windows) | ||
| set(CMAKE_SYSTEM_PROCESSOR arm64) | ||
| set(RUST_COMPILER_TARGET "aarch64-pc-windows-msvc") | ||
|
|
||
| # Project Variables needed to cross compile | ||
| set(HAVE_PRAGMA_PACK 1) | ||
| set(HAVE_SAR 1) | ||
| set(MMAP_FOR_CROSSCOMPILING OFF) | ||
| set(ENABLE_SYSTEMD OFF) | ||
|
|
||
| set( test_run_result | ||
| "PLEASE_FILL_OUT-FAILED_TO_RUN" | ||
| CACHE STRING "Result from try_run" FORCE) | ||
|
|
||
| set( test_run_result__TRYRUN_OUTPUT | ||
| "PLEASE_FILL_OUT-NOTFOUND" | ||
| CACHE STRING "Output from try_run" FORCE) | ||
| ``` | ||
|
|
||
| ## Build ClamAV | ||
|
|
||
| You may need to adjust the paths in the command below to suit your needs. | ||
|
|
||
| ```powershell | ||
| mkdir build-arm64 | ||
| cd build-arm64 | ||
|
|
||
| cmake .. -G "Visual Studio 17 2022" -A arm64 ` | ||
| -D JSONC_INCLUDE_DIR="$HOME\\.mussels\\install\\arm64\\include\\json-c" ` | ||
| -D JSONC_LIBRARY="$HOME\\.mussels\\install\\arm64\\lib\\json-c.lib" ` | ||
| -D BZIP2_INCLUDE_DIR="$HOME\\.mussels\\install\\arm64\\include" ` | ||
| -D BZIP2_LIBRARY_RELEASE="$HOME\\.mussels\\install\\arm64\\lib\\libbz2.lib" ` | ||
| -D CURL_INCLUDE_DIR="$HOME\\.mussels\\install\\arm64\\include" ` | ||
| -D CURL_LIBRARY="$HOME\\.mussels\\install\\arm64\\lib\\libcurl_imp.lib" ` | ||
| -D OPENSSL_ROOT_DIR="$HOME\\.mussels\\install\\arm64\\" ` | ||
| -D OPENSSL_INCLUDE_DIR="$HOME\\.mussels\\install\\arm64\\include" ` | ||
| -D LIB_EAY_DEBUG="$HOME\\.mussels\\install\\arm64\\lib\\libcrypto.lib" ` | ||
| -D SSL_EAY_DEBUG="$HOME\\.mussels\\install\\arm64\\lib\\libssl.lib" ` | ||
| -D ZLIB_LIBRARY="$HOME\\.mussels\\install\\arm64\\lib\\libssl.lib" ` | ||
| -D LIBXML2_INCLUDE_DIR="$HOME\\.mussels\\install\\arm64\\include\\libxml" ` | ||
| -D LIBXML2_LIBRARY="$HOME\\.mussels\\install\\arm64\\lib\\libxml2.lib" ` | ||
| -D PCRE2_INCLUDE_DIR="$HOME\\.mussels\\install\\arm64\\include" ` | ||
| -D PCRE2_LIBRARY="$HOME\\.mussels\\install\\arm64\\lib\\pcre2-8.lib" ` | ||
| -D PDCURSES_INCLUDE_DIR="$HOME\\.mussels\\install\\arm64\\include" ` | ||
| -D CURSES_LIBRARY="$HOME\\.mussels\\install\\arm64\\lib\\pdcurses.lib" ` | ||
| -D PThreadW32_INCLUDE_DIR="$HOME\\.mussels\\install\\arm64\\include" ` | ||
| -D PThreadW32_LIBRARY="$HOME\\.mussels\\install\\arm64\\lib\\pthreadVC3.lib" ` | ||
| -D ZLIB_INCLUDE_DIR="$HOME\\.mussels\\install\\arm64\\include" ` | ||
| -D ZLIB_LIBRARY="$HOME\\.mussels\\install\\arm64\\lib\\zlibstatic.lib" ` | ||
| -D LIBCHECK_INCLUDE_DIR="$HOME\\.mussels\\install\\arm64\\include" ` | ||
| -D LIBCHECK_LIBRARY="$HOME\\.mussels\\install\\arm64\\lib\\checkDynamic.lib" ` | ||
| -D CMAKE_TOOLCHAIN_FILE=$pwd\\..\\CMAKE_TOOLCHAIN_ARM64.cmake ` | ||
| -D ENABLE_STATIC_LIB=OFF ` | ||
| -D ENABLE_SHARED_LIB=ON ` | ||
| -D MAINTAINER_MODE=OFF ` | ||
| -D ENABLE_EXAMPLES=OFF ` | ||
| -D BYTECODE_RUNTIME=interpreter ` | ||
| -D HAVE_PRAGMA_PACK=1 ` | ||
| -D HAVE_SAR=1 ` | ||
| -D CMAKE_INSTALL_PREFIX="install" | ||
|
|
||
| cmake --build . --config Release --target install | ||
| ``` | ||
|
|
||
| ## Verify it built for right platform | ||
|
|
||
| We cannot run the ClamAV unit test suite, because we're cross compiling and can't run the programs we build. But we can do a very small test to see that it built for the right platform. | ||
|
|
||
| Pop into WSL2 (Windows Subsystem for Linux 2) to make use of the `file` utility: | ||
|
|
||
| ```powershell | ||
| ❯ wsl | ||
| Welcome to fish, the friendly interactive shell | ||
| Type `help` for instructions on how to use fish | ||
|
|
||
| clamav-micah-2/build-arm64 on main [$] via C v9.4.0-gcc via △ v3.27.2 | ||
| ❯ file install/clamscan.exe | ||
| install/clamscan.exe: PE32+ executable (console) Aarch64, for MS Windows | ||
| ``` | ||
|
|
||
| If everything looks good, you can probably copy the install files to your system and run it. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.