Skip to content
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

Add support for Linux ARM64 #3853

Merged
merged 10 commits into from
Aug 23, 2022
Merged
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ Line wrap the file at 100 chars. Th

#### Linux
- GUI: Add electron flags to run Wayland native if in a compositor/desktop known to work well
- Add support for Linux ARM64.

### Changed
- Reject invalid WireGuard ports in the CLI.
Expand Down
60 changes: 51 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,35 @@ sudo dnf install dbus-devel
sudo dnf install rpm-build
```

### Cross-compiling for Linux ARM64

By default, the app will build for the host platform. It is also possible to cross-compile the app
for ARM64 on x64.

#### Debian

```bash
# As root
dpkg --add-architecture arm64 && \
apt update && \
apt install libdbus-1-dev:arm64 gcc-aarch64-linux-gnu
```

```bash
rustup target add aarch64-unknown-linux-gnu
```

To make sure the right linker and libraries are used, add the following to `~/.cargo/config.toml`:

```
[target.aarch64-unknown-linux-gnu]
linker = "aarch64-linux-gnu-gcc"

[target.aarch64-unknown-linux-gnu.dbus]
rustc-link-search = ["/usr/aarch64-linux-gnu/lib"]
rustc-link-lib = ["dbus-1"]
```

### Android

These instructions are for building the app for Android **under Linux**.
Expand Down Expand Up @@ -317,28 +346,39 @@ Building this requires at least 1GB of memory.
By default, `build.sh` produces a pkg for your current architecture only. To build a universal
app that works on both Intel and Apple Silicon macs, build with `--universal`.

##### Apple ARM64 (aka Apple Silicon)
#### Linux ARM64

To cross-compile for ARM64 rather than the current architecture, set the `TARGETS` environment
variable to `aarch64-unknown-linux-gnu`:

```bash
TARGETS="aarch64-unknown-linux-gnu" ./build.sh
```

##### ARM64

Due to inability to build the management interface proto files on ARM64 (see
[this](https://github.com/grpc/grpc-node/issues/1497) issue) the Apple ARM64 build must be done in 2 stages:
[this](https://github.com/grpc/grpc-node/issues/1497) issue), building on ARM64 must be done in
2 stages:

1. Build management interface proto files on a non-ARM64 platform
2. Use the built proto files during the main build by setting the `MANAGEMENT_INTERFACE_PROTO_BUILD_DIR`
environment variable to the path the proto files
2. Use the built proto files during the main build by setting the
`MANAGEMENT_INTERFACE_PROTO_BUILD_DIR` environment variable to the path the proto files

To build the management interface proto files there is a script (execute it on a non-ARM64 platform):
To build the management interface proto files there is a script (execute it on a non-ARM64
platform):

```bash
cd gui/scripts
npm ci
./build-proto.sh
```

After that copy the files from `gui/src/main/management_interface/` and `gui/build/src/main/management_interface/`
directories into a single directory on your Apple Silicon Mac, and set the value of
`MANAGEMENT_INTERFACE_PROTO_BUILD_DIR` to that directory while running the main build.
After that copy the files from `gui/src/main/management_interface/` and
`gui/build/src/main/management_interface/` directories into a single directory, and set the value
of `MANAGEMENT_INTERFACE_PROTO_BUILD_DIR` to that directory while running the main build.

Install `protobuf` by running:
Install `protobuf`. On macOS, this can be done using Homebrew:

```bash
brew install protobuf
Expand All @@ -351,6 +391,8 @@ directory, the build command will look as follows:
MANAGEMENT_INTERFACE_PROTO_BUILD_DIR=/tmp/management_interface_proto ./build.sh --dev-build
```

On Linux, you may also have to specify `USE_SYSTEM_FPM=true` to generate the deb/rpm packages.

If you want to build each component individually, or run in development mode, read the following
sections.

Expand Down
20 changes: 17 additions & 3 deletions build.sh
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,16 @@ fi
CARGO_ARGS=()
NPM_PACK_ARGS=()

if [[ -n ${TARGETS:-""} ]]; then
NPM_PACK_ARGS+=(--targets "${TARGETS[*]}")
fi

if [[ "$UNIVERSAL" == "true" ]]; then
if [[ -n ${TARGETS:-""} ]]; then
log_error "'TARGETS' and '--universal' cannot be specified simultaneously."
exit 1
fi

TARGETS=(x86_64-apple-darwin aarch64-apple-darwin)
NPM_PACK_ARGS+=(--universal)
fi
Expand Down Expand Up @@ -197,8 +206,13 @@ function sign_win {
function build {
local current_target=${1:-""}
local for_target_string=""
local stripbin="strip"
if [[ -n $current_target ]]; then
for_target_string=" for $current_target"

if [[ "$current_target" == "aarch64-unknown-linux-gnu" && "$(uname -m)" != "aarch64" ]]; then
stripbin="aarch64-linux-gnu-strip"
fi
fi

################################################################################
Expand Down Expand Up @@ -255,8 +269,8 @@ function build {

if [[ -n $current_target ]]; then
local cargo_output_dir="$CARGO_TARGET_DIR/$current_target/$RUST_BUILD_MODE"
# To make it easier to package universal builds on macOS the binaries are located in a
# directory with the name of the target triple.
# To make it easier to package multiple targets, the binaries are
# located in a directory with the name of the target triple.
local destination_dir="dist-assets/$current_target"
mkdir -p "$destination_dir"
else
Expand All @@ -273,7 +287,7 @@ function build {
cp "$source" "$destination"
else
log_info "Stripping $source => $destination"
strip "$source" -o "$destination"
"${stripbin}" "$source" -o "$destination"
fi

if [[ "$SIGN" == "true" && "$(uname -s)" == "MINGW"* ]]; then
Expand Down
3 changes: 2 additions & 1 deletion env.sh
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"

case "$(uname -s)" in
Linux*)
HOST="x86_64-unknown-linux-gnu"
arch="$(uname -m)"
HOST="${arch}-unknown-linux-gnu"
;;
Darwin*)
arch="$(uname -m)"
Expand Down
9 changes: 5 additions & 4 deletions gui/scripts/build-proto.sh
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ set -eu
SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
cd "$SCRIPT_DIR"

PLATFORM="$(uname -s)-$(uname -m)"
ARCH="$(uname -m)"
PLATFORM="$(uname -s)-${ARCH}"
MANAGEMENT_INTERFACE_PROTO_BUILD_DIR=${MANAGEMENT_INTERFACE_PROTO_BUILD_DIR:-}
NODE_MODULES_DIR="$(cd ../node_modules/.bin && pwd)"
PROTO_DIR="../../mullvad-management-interface/proto"
Expand All @@ -21,14 +22,14 @@ fi
mkdir -p $DESTINATION_DIR
mkdir -p $TYPES_DESTINATION_DIR

if [[ "${PLATFORM}" == "Darwin-arm64" ]]; then
if [[ "${ARCH,,}" == "arm64" || "${ARCH,,}" == "aarch64" ]]; then
if [[ -n "${MANAGEMENT_INTERFACE_PROTO_BUILD_DIR}" ]]; then
cp $MANAGEMENT_INTERFACE_PROTO_BUILD_DIR/*.js $DESTINATION_DIR
cp $MANAGEMENT_INTERFACE_PROTO_BUILD_DIR/*.ts $TYPES_DESTINATION_DIR
else
>&2 echo "Building management interface proto files on Apple Silicon is not supported"
>&2 echo "Building management interface proto files on aarch64 is not supported"
>&2 echo "(see https://github.com/grpc/grpc-node/issues/1497)."
>&2 echo "Please build the proto files on another platform using build_mi_proto.sh script,"
>&2 echo "Please build the proto files on another platform using build-proto.sh script,"
>&2 echo "and set MANAGEMENT_INTERFACE_PROTO_BUILD_DIR environment variable to the directory of the build."
exit 1
fi
Expand Down
72 changes: 62 additions & 10 deletions gui/tasks/distribution.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,13 @@ const noAppleNotarization = process.argv.includes('--no-apple-notarization');
const universal = process.argv.includes('--universal');
const release = process.argv.includes('--release');

const targetsIndex = process.argv.indexOf('--targets');
let targets = null;

if (targetsIndex !== -1) {
targets = process.argv[targetsIndex + 1];
}

const config = {
appId: 'net.mullvad.vpn',
copyright: 'Mullvad VPN AB',
Expand Down Expand Up @@ -147,17 +154,26 @@ const config = {
},

linux: {
target: ['deb', 'rpm'],
target: [
{
target: 'deb',
arch: getLinuxTargetArch(),
},
{
target: 'rpm',
arch: getLinuxTargetArch(),
},
],
artifactName: 'MullvadVPN-${version}_${arch}.${ext}',
category: 'Network',
icon: distAssets('icon.icns'),
extraFiles: [{ from: distAssets('linux/mullvad-gui-launcher.sh'), to: '.' }],
extraResources: [
{ from: distAssets('mullvad-problem-report'), to: '.' },
{ from: distAssets('mullvad-daemon'), to: '.' },
{ from: distAssets('mullvad-setup'), to: '.' },
{ from: distAssets('libtalpid_openvpn_plugin.so'), to: '.' },
{ from: distAssets('binaries/x86_64-unknown-linux-gnu/openvpn'), to: '.' },
{ from: distAssets(path.join(getLinuxTargetSubdir(), 'mullvad-problem-report')), to: '.' },
{ from: distAssets(path.join(getLinuxTargetSubdir(), 'mullvad-daemon')), to: '.' },
{ from: distAssets(path.join(getLinuxTargetSubdir(), 'mullvad-setup')), to: '.' },
{ from: distAssets(path.join(getLinuxTargetSubdir(), 'libtalpid_openvpn_plugin.so')), to: '.' },
{ from: distAssets(path.join('binaries', '${env.TARGET_TRIPLE}', 'openvpn')), to: '.' },
{ from: distAssets('linux/mullvad-daemon.service'), to: '.' },
],
},
Expand All @@ -173,8 +189,8 @@ const config = {
distAssets('linux/before-remove.sh'),
'--config-files',
'/opt/Mullvad VPN/resources/mullvad-daemon.service',
distAssets('mullvad') + '=/usr/bin/',
distAssets('mullvad-exclude') + '=/usr/bin/',
distAssets(path.join(getLinuxTargetSubdir(), 'mullvad')) + '=/usr/bin/',
distAssets(path.join(getLinuxTargetSubdir(), 'mullvad-exclude')) + '=/usr/bin/',
distAssets('linux/problem-report-link') + '=/usr/bin/mullvad-problem-report',
distAssets('shell-completions/mullvad.bash') +
'=/usr/share/bash-completion/completions/mullvad',
Expand All @@ -196,8 +212,8 @@ const config = {
distAssets('linux/post-transaction.sh'),
'--config-files',
'/opt/Mullvad VPN/resources/mullvad-daemon.service',
distAssets('mullvad') + '=/usr/bin/',
distAssets('mullvad-exclude') + '=/usr/bin/',
distAssets(path.join(getLinuxTargetSubdir(), 'mullvad')) + '=/usr/bin/',
distAssets(path.join(getLinuxTargetSubdir(), 'mullvad-exclude')) + '=/usr/bin/',
distAssets('linux/problem-report-link') + '=/usr/bin/mullvad-problem-report',
distAssets('shell-completions/mullvad.bash') +
'=/usr/share/bash-completion/completions/mullvad',
Expand Down Expand Up @@ -304,6 +320,21 @@ function packLinux() {
targets: builder.Platform.LINUX.createTarget(),
config: {
...config,
beforeBuild: (options) => {
switch (options.arch) {
case 'x64':
process.env.TARGET_TRIPLE = 'x86_64-unknown-linux-gnu';
raksooo marked this conversation as resolved.
Show resolved Hide resolved
break;
case 'arm64':
process.env.TARGET_TRIPLE = 'aarch64-unknown-linux-gnu';
break;
default:
delete process.env.TARGET_TRIPLE;
break;
}

return true;
},
afterPack: async (context) => {
config.afterPack?.(context);

Expand All @@ -328,6 +359,27 @@ function root(relativePath) {
return path.join(path.resolve(__dirname, '../../'), relativePath);
}

function getLinuxTargetArch() {
if (targets) {
if (targets === 'aarch64-unknown-linux-gnu') {
return 'arm64';
}
throw new Error(`Invalid or unknown target (only one may be specified)`);
}
// Use host architecture.
return undefined;
}

function getLinuxTargetSubdir() {
raksooo marked this conversation as resolved.
Show resolved Hide resolved
if (targets) {
if (targets === 'aarch64-unknown-linux-gnu') {
return targets;
}
throw new Error(`Invalid or unknown target (only one may be specified)`);
}
return '';
}

function getMacArch() {
if (universal) {
return 'universal';
Expand Down
33 changes: 22 additions & 11 deletions wireguard/build-wireguard-go.sh
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,8 @@ function build_windows {
function unix_target_triple {
local platform="$(uname -s)"
if [[ ("${platform}" == "Linux") ]]; then
echo "x86_64-unknown-linux-gnu"
local arch="$(uname -m)"
echo "${arch}-unknown-linux-gnu"
elif [[ ("${platform}" == "Darwin") ]]; then
local arch="$(uname -m)"
if [[ ("${arch}" == "arm64") ]]; then
Expand All @@ -87,16 +88,26 @@ function unix_target_triple {
function build_unix {
echo "Building wireguard-go for $1"

# Flags for cross compiling for M1 macs
if [[ "$(unix_target_triple)" != "$1" && "$1" == "aarch64-apple-darwin" ]]; then
export CGO_ENABLED=1
export GOOS=darwin
export GOARCH=arm64
export CC="$(xcrun -sdk $SDKROOT --find clang) -arch $GOARCH -isysroot $SDKROOT"
export CFLAGS="-isysroot $SDKROOT -arch $GOARCH -I$SDKROOT/usr/include"
export LD_LIBRARY_PATH="$SDKROOT/usr/lib"
export CGO_CFLAGS="-isysroot $SDKROOT -arch $GOARCH"
export CGO_LDFLAGS="-isysroot $SDKROOT -arch $GOARCH"
# Flags for cross compiling
if [[ "$(unix_target_triple)" != "$1" ]]; then
# Linux arm
if [[ "$1" == "aarch64-unknown-linux-gnu" ]]; then
export CGO_ENABLED=1
export GOARCH=arm64
export CC=aarch64-linux-gnu-gcc
fi

# Apple silicon
if [[ "$1" == "aarch64-apple-darwin" ]]; then
export CGO_ENABLED=1
export GOOS=darwin
export GOARCH=arm64
export CC="$(xcrun -sdk $SDKROOT --find clang) -arch $GOARCH -isysroot $SDKROOT"
export CFLAGS="-isysroot $SDKROOT -arch $GOARCH -I$SDKROOT/usr/include"
export LD_LIBRARY_PATH="$SDKROOT/usr/lib"
export CGO_CFLAGS="-isysroot $SDKROOT -arch $GOARCH"
export CGO_LDFLAGS="-isysroot $SDKROOT -arch $GOARCH"
fi
fi

pushd libwg
Expand Down