mirror of
https://github.com/shadowsocks/shadowsocks-libev.git
synced 2026-10-28 03:33:42 +00:00
Gate static Clang builds on macOS, Windows and Linux
This commit is contained in:
@@ -73,6 +73,90 @@ jobs:
|
||||
& ./consumer/consumer_shared.exe
|
||||
if ($LASTEXITCODE -ne 0) { throw "Shared consumer failed: $LASTEXITCODE" }
|
||||
|
||||
static-macos:
|
||||
runs-on: macos-latest
|
||||
env:
|
||||
CC: clang
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- name: Build Clang static-only installation
|
||||
run: |
|
||||
cmake -S . -B build-static -DCMAKE_C_COMPILER=clang -DSS_DEPENDENCY_MODE=bundled -DWITH_STATIC=ON -DSS_BUILD_STATIC_LIBRARY=ON -DSS_BUILD_SHARED_LIBRARY=OFF
|
||||
cmake --build build-static --parallel 4
|
||||
ctest --test-dir build-static -L 'unit|vendor' --output-on-failure --no-tests=error
|
||||
cmake --install build-static --prefix "$PWD/install-static"
|
||||
mv install-static relocated-static
|
||||
cmake -S tests/consumer -B consumer-static -DCMAKE_C_COMPILER=clang -DCMAKE_PREFIX_PATH="$PWD/relocated-static"
|
||||
cmake --build consumer-static
|
||||
test ! -e consumer-static/consumer_shared
|
||||
./consumer-static/consumer_static
|
||||
- name: Reject third-party dynamic libraries
|
||||
run: python3 tests/check_static_runtime.py relocated-static/bin/ss-* consumer-static/consumer_static
|
||||
- name: Exercise installed static programs
|
||||
run: |
|
||||
python3 tests/interop.py --self --bin relocated-static/bin
|
||||
python3 tests/interop.py --self --bin relocated-static/bin --plugin tests/sip003_fixture.py
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: static-macos-${{ runner.arch }}
|
||||
path: relocated-static/
|
||||
if-no-files-found: error
|
||||
|
||||
static-windows:
|
||||
runs-on: windows-latest
|
||||
defaults:
|
||||
run:
|
||||
shell: msys2 {0}
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- uses: msys2/setup-msys2@v2
|
||||
with:
|
||||
msystem: CLANG64
|
||||
update: true
|
||||
install: >-
|
||||
mingw-w64-clang-x86_64-clang
|
||||
mingw-w64-clang-x86_64-llvm
|
||||
mingw-w64-clang-x86_64-cmake
|
||||
mingw-w64-clang-x86_64-ninja
|
||||
mingw-w64-clang-x86_64-python
|
||||
- name: Build Clang static-only installation
|
||||
run: |
|
||||
cmake -S . -B build-static -G Ninja -DCMAKE_C_COMPILER=clang -DSS_DEPENDENCY_MODE=bundled -DWITH_STATIC=ON -DSS_BUILD_STATIC_LIBRARY=ON -DSS_BUILD_SHARED_LIBRARY=OFF
|
||||
cmake --build build-static --parallel 4
|
||||
ctest --test-dir build-static -L 'unit|vendor' --output-on-failure --no-tests=error
|
||||
cmake --install build-static --prefix "$PWD/install-static"
|
||||
mv install-static relocated-static
|
||||
cmake -S tests/consumer -B consumer-static -G Ninja -DCMAKE_C_COMPILER=clang -DCMAKE_PREFIX_PATH="$PWD/relocated-static"
|
||||
cmake --build consumer-static
|
||||
test ! -e consumer-static/consumer_shared.exe
|
||||
- name: Reject third-party DLL imports, including delay imports
|
||||
run: python tests/check_static_runtime.py relocated-static/bin/ss-*.exe consumer-static/consumer_static.exe
|
||||
- name: Exercise installed programs without toolchain DLLs
|
||||
run: python tests/interop.py --self --bin relocated-static/bin --isolate-windows-runtime
|
||||
- name: Run static consumer with only Windows system directories on PATH
|
||||
shell: pwsh
|
||||
run: |
|
||||
$env:PATH = "$env:SystemRoot\System32;$env:SystemRoot"
|
||||
& ./consumer-static/consumer_static.exe
|
||||
if ($LASTEXITCODE -ne 0) { throw "Static consumer failed: $LASTEXITCODE" }
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: static-windows-${{ runner.arch }}
|
||||
path: relocated-static/
|
||||
if-no-files-found: error
|
||||
|
||||
static-linux:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v6
|
||||
- name: Build and test fully static Clang/musl executables
|
||||
run: docker build -f docker/static/Dockerfile --target artifacts --output type=local,dest=static-linux .
|
||||
- uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: static-linux-${{ runner.arch }}
|
||||
path: static-linux/
|
||||
if-no-files-found: error
|
||||
|
||||
freebsd:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
ARG ALPINE_IMAGE=alpine:3.24
|
||||
FROM ${ALPINE_IMAGE} AS build
|
||||
ARG ALPINE_MIRROR=https://dl-cdn.alpinelinux.org/alpine
|
||||
RUN sed -i "s|https://dl-cdn.alpinelinux.org/alpine|${ALPINE_MIRROR}|g" /etc/apk/repositories \
|
||||
&& apk add --no-cache clang lld make musl-dev compiler-rt cmake linux-headers python3 binutils file
|
||||
ENV CC=clang
|
||||
WORKDIR /src
|
||||
COPY . .
|
||||
ARG SS_MINIMAL=OFF
|
||||
RUN --network=none cmake -S . -B /build \
|
||||
-DCMAKE_BUILD_TYPE=Release \
|
||||
-DSS_DEPENDENCY_MODE=bundled -DWITH_STATIC=ON \
|
||||
-DSS_BUILD_STATIC_LIBRARY=OFF -DSS_BUILD_SHARED_LIBRARY=OFF \
|
||||
-DSS_MINIMAL=${SS_MINIMAL} -DCMAKE_EXE_LINKER_FLAGS="-static -fuse-ld=lld --rtlib=compiler-rt" \
|
||||
-DCMAKE_INSTALL_PREFIX=/out \
|
||||
&& cmake --build /build --parallel 4 \
|
||||
&& ctest --test-dir /build -L 'unit|vendor' --output-on-failure --no-tests=error \
|
||||
&& python3 tests/interop.py --self --bin /build/bin \
|
||||
&& cmake --install /build --strip \
|
||||
&& for binary in /out/bin/ss-*; do \
|
||||
file "$binary"; \
|
||||
! readelf -l "$binary" | grep -q INTERP || exit 1; \
|
||||
! readelf -d "$binary" | grep -q NEEDED || exit 1; \
|
||||
done
|
||||
|
||||
# Export binaries and their license files with --target artifacts --output.
|
||||
FROM scratch AS artifacts
|
||||
COPY --from=build /out/ /
|
||||
|
||||
# No libc, dynamic loader, package manager or shell in the runtime image.
|
||||
FROM scratch AS runtime
|
||||
COPY --from=build /out/ /usr/local/
|
||||
ENTRYPOINT ["/usr/local/bin/ss-server"]
|
||||
@@ -0,0 +1,8 @@
|
||||
.git
|
||||
build*
|
||||
release
|
||||
libcork
|
||||
libipset
|
||||
libbloom
|
||||
**/__pycache__
|
||||
**/.DS_Store
|
||||
@@ -0,0 +1,41 @@
|
||||
# Fully static Linux builds
|
||||
|
||||
The Alpine/musl builder uses Clang with LLD by default and links both third-party dependencies and libc statically.
|
||||
`WITH_STATIC=ON` alone only selects static third-party libraries; this image also
|
||||
passes `-DCMAKE_EXE_LINKER_FLAGS=-static` and omits shared-library outputs.
|
||||
The compile, unit/vendor tests and six-method TCP/UDP relay tests run with Docker
|
||||
network access disabled. The final ELF check rejects interpreters and dynamic
|
||||
library dependencies. Initial image/toolchain installation requires networking.
|
||||
|
||||
From the repository root, export binaries and licenses (ARM64 example):
|
||||
|
||||
```sh
|
||||
docker build --platform linux/arm64 -f docker/static/Dockerfile \
|
||||
--target artifacts --output type=local,dest=build-artifacts/static-linux-arm64 .
|
||||
```
|
||||
|
||||
Requires Docker Buildx/BuildKit. If Docker Hub is unavailable, add
|
||||
`--build-arg ALPINE_IMAGE=public.ecr.aws/docker/library/alpine:3.24`.
|
||||
An alternate package mirror can be selected with `--build-arg ALPINE_MIRROR=https://mirrors.aliyun.com/alpine`; APK signature checks remain enabled.
|
||||
Docker build proxy settings apply to package installation; a broken configured
|
||||
proxy can be bypassed for this command with empty `HTTP_PROXY`, `HTTPS_PROXY`,
|
||||
`ALL_PROXY` build arguments and their lowercase equivalents.
|
||||
|
||||
Use `--platform linux/amd64` and a different destination for x86-64. Docker needs
|
||||
a matching native builder or CPU emulation to compile and run the tests for a
|
||||
foreign architecture. Add `--build-arg SS_MINIMAL=ON` for the minimal profile.
|
||||
|
||||
Build the scratch runtime image using the same cached build stage:
|
||||
|
||||
```sh
|
||||
docker build --platform linux/arm64 -f docker/static/Dockerfile \
|
||||
--target runtime -t shadowsocks-libev:static-arm64 .
|
||||
docker run --rm -p 8388:8388/tcp -p 8388:8388/udp \
|
||||
shadowsocks-libev:static-arm64 \
|
||||
-s 0.0.0.0 -p 8388 -k example-password -m aes-256-gcm -u
|
||||
```
|
||||
|
||||
The runtime image has no shell or external plugin executables. Supply any SIP003
|
||||
plugin and its own runtime requirements separately. Configuration files can be
|
||||
bind-mounted and passed with `-c`. Exported binaries run on Linux with the same
|
||||
CPU architecture; they do not run directly on macOS or Windows.
|
||||
@@ -147,3 +147,19 @@ submodule dependencies and reduces what is built without replacing audited
|
||||
cryptographic algorithms with project-specific implementations.
|
||||
|
||||
See [measurements and tradeoffs](performance.md) for reproducible local results.
|
||||
|
||||
## Static build CI
|
||||
|
||||
The `static-macos` and `static-windows` jobs use Clang, bundled dependencies and
|
||||
`SS_BUILD_SHARED_LIBRARY=OFF`. They run unit/vendor tests, relocate the installed
|
||||
static library, build its consumer, and exercise the installed TCP/UDP programs.
|
||||
The macOS job also exercises SIP003. `tests/check_static_runtime.py` rejects
|
||||
non-system dylibs and DLLs (including Windows delay imports) in every installed
|
||||
program and the static consumer. Windows runtime tests exclude toolchain DLL
|
||||
paths. Windows uses the native [MSYS2 CLANG64 environment](https://www.msys2.org/docs/environments/).
|
||||
|
||||
macOS and Windows retain their OS libraries; static builds on these platforms
|
||||
mean no separately distributed project, crypto, event-loop or toolchain shared
|
||||
libraries. The `static-linux` job builds the Clang/musl Dockerfile, which also
|
||||
links libc statically and rejects ELF interpreters and `NEEDED` entries. All
|
||||
three jobs publish the tested installations as workflow artifacts.
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Reject non-system runtime dependencies in static macOS/Windows outputs."""
|
||||
import argparse
|
||||
import platform
|
||||
import re
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
|
||||
WINDOWS_SYSTEM_DLLS = {
|
||||
'advapi32.dll', 'bcrypt.dll', 'crypt32.dll', 'dnsapi.dll', 'iphlpapi.dll',
|
||||
'kernel32.dll', 'msvcrt.dll', 'ntdll.dll', 'ole32.dll', 'psapi.dll',
|
||||
'rpcrt4.dll', 'secur32.dll', 'shell32.dll', 'ucrtbase.dll', 'user32.dll',
|
||||
'userenv.dll', 'winmm.dll', 'ws2_32.dll',
|
||||
}
|
||||
|
||||
|
||||
def dependencies(binary, system):
|
||||
if system == 'Darwin':
|
||||
output = subprocess.check_output(['otool', '-L', str(binary)], text=True)
|
||||
return [line.strip().split(' (compatibility version', 1)[0]
|
||||
for line in output.splitlines() if line.startswith('\t')]
|
||||
output = subprocess.check_output(
|
||||
['llvm-readobj', '--coff-imports', str(binary)], text=True)
|
||||
return re.findall(r'^\s+Name: (.+)$', output, re.MULTILINE)
|
||||
|
||||
|
||||
def is_system_dependency(name, system):
|
||||
if system == 'Darwin':
|
||||
return name.startswith(('/usr/lib/', '/System/Library/'))
|
||||
name = name.strip().lower()
|
||||
return name in WINDOWS_SYSTEM_DLLS or (
|
||||
name.startswith(('api-ms-win-', 'ext-ms-win-')) and name.endswith('.dll'))
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument('binaries', type=Path, nargs='+')
|
||||
args = parser.parse_args()
|
||||
system = platform.system()
|
||||
if system not in ('Darwin', 'Windows'):
|
||||
parser.error('this check supports macOS and native Windows Python')
|
||||
for binary in args.binaries:
|
||||
names = dependencies(binary, system)
|
||||
if not names:
|
||||
raise SystemExit(f'{binary}: no OS imports found; check file format/tool output')
|
||||
unexpected = [name for name in names if not is_system_dependency(name, system)]
|
||||
if unexpected:
|
||||
raise SystemExit(f'{binary}: non-system dependencies: {unexpected}')
|
||||
print(f'PASS {binary}: {", ".join(names)}')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
Reference in New Issue
Block a user