From 2da3ed2c1b52433e5a735bb6f11ee4d24a7848a1 Mon Sep 17 00:00:00 2001 From: "Ngo Iok Ui (Wu Yu Wei)" Date: Mon, 5 Aug 2024 12:30:17 +0900 Subject: [PATCH] fix: macOS nightly-release (#105) * Add script to package libs * Install gstreamer on macOS CI * Add cargo packager to macOS * Install cargo-packager on macos * Update version env variable * Move step order * Update artifact path * Add develop installer * Use fallback path instead * Move env variable to build steps instead * Manually export instead * Move dylib to framework instead * Try ad-hoc codesign * Try frameworks * Test custom package script * Update dmg version * Add rpath in build script * Add entitlements * Cleanup * Revert CI condition for packaging --- .github/workflows/build.yml | 36 +++++++++++-- Cargo.toml | 3 +- README.md | 6 ++- build.rs | 3 ++ etc/package_libs.py | 103 ++++++++++++++++++++++++++++++++++++ 5 files changed, 143 insertions(+), 8 deletions(-) create mode 100644 etc/package_libs.py diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 7589adf6..7be363dc 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -127,6 +127,7 @@ jobs: uses: dsherret/rust-toolchain-file@v1 - name: Install Rust toolchain for packager + if: ${{ github.event_name == 'schedule' }} uses: actions-rust-lang/setup-rust-toolchain@v1 with: toolchain: stable @@ -148,6 +149,7 @@ jobs: uses: Mozilla-Actions/sccache-action@v0.0.4 - name: Install Cargo Packager + if: ${{ github.event_name == 'schedule' }} run: cargo +stable install cargo-packager - name: Build @@ -196,6 +198,12 @@ jobs: - name: Install Rust uses: dsherret/rust-toolchain-file@v1 + - name: Install Rust toolchain for packager + if: ${{ github.event_name == 'schedule' }} + uses: actions-rust-lang/setup-rust-toolchain@v1 + with: + toolchain: stable + - name: Install Python uses: actions/setup-python@v5 with: @@ -203,30 +211,48 @@ jobs: - name: Install dependencies run: | - brew install cmake pkg-config + brew install cmake python -m pip install mako + curl https://gstreamer.freedesktop.org/data/pkg/osx/1.24.6/gstreamer-1.0-1.24.6-universal.pkg -o runtime.pkg + sudo installer -pkg runtime.pkg -target / + curl https://gstreamer.freedesktop.org/data/pkg/osx/1.24.6/gstreamer-1.0-devel-1.24.6-universal.pkg -o develop.pkg + sudo installer -pkg develop.pkg -target / - name: Run sccache-cache uses: Mozilla-Actions/sccache-action@v0.0.4 + - name: Install Cargo Packager + if: ${{ github.event_name == 'schedule' }} + run: cargo +stable install cargo-packager + - name: Build run: | + export PATH="/Library/Frameworks/GStreamer.framework/Versions/1.0/bin${PATH:+:$PATH}" + export DYLD_LIBRARY_PATH="/Library/Frameworks/GStreamer.framework/Versions/1.0/lib${DYLD_LIBRARY_PATH:+:$DYLD_LIBRARY_PATH}" cargo build --release # - name: Test # run: | # cargo test --release - - name: Tar Binary + - name: Bundle + if: ${{ github.event_name == 'schedule' }} + run: | + export PATH="/Library/Frameworks/GStreamer.framework/Versions/1.0/bin${PATH:+:$PATH}" + export DYLD_LIBRARY_PATH="/Library/Frameworks/GStreamer.framework/Versions/1.0/lib${DYLD_LIBRARY_PATH:+:$DYLD_LIBRARY_PATH}" + cargo packager --release + + - name: Fetch Verso version if: ${{ github.event_name == 'schedule' }} - run: tar -czvf verso-${{ matrix.platform.target }}.tar.gz -C ./target/release/ verso + run: | + echo "VERSO_VERSION=$(cargo metadata --format-version=1 --no-deps | jq -r '.packages[] | select(.name == "verso") | .version')" >> $GITHUB_ENV - name: Upload artifact if: ${{ github.event_name == 'schedule' }} uses: actions/upload-artifact@v4 with: - name: verso-${{ matrix.platform.target }} - path: verso-${{ matrix.platform.target }}.tar.gz + name: verso_${{ env.VERSO_VERSION }}_${{ matrix.platform.arch }} + path: target/release/verso_${{ env.VERSO_VERSION }}_${{ matrix.platform.arch }}.dmg build-result: name: Build Result diff --git a/Cargo.toml b/Cargo.toml index fcd04534..173af7eb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,12 +18,13 @@ name = "verso" product-name = "verso" identifier = "org.versotile.verso" version = "0.0.1" -before-each-package-command = "cargo build --release --features packager" +before-each-package-command = "python etc/package_libs.py" resources = [ "resources", "icons", "target/release/build/**/libEGL.dll", "target/release/build/**/libGLESv2.dll", + "target/release/lib", ] icons = ["icons/icon256x256.png", "icons/icon.ico"] diff --git a/README.md b/README.md index 477d2186..8aa75eae 100644 --- a/README.md +++ b/README.md @@ -94,9 +94,11 @@ But please understand we don't triage any build issue without flatpak or nix set Nightly releases built with CrabNebula Cloud can be found at [releases](https://web.crabnebula.cloud/verso/verso-nightly/releases). +> Packages are unsigned currently. If you have problem opening the app on macOS, try `xattr -d com.apple.quarantine /Applications/verso.app` after installation. + ## Future Work - Multiwindow support. - Enable multiprocess mode. -- Enable sandobx in all platforms. -- Enable `Gstreamer` feature and remove `brew install harfbuzz` in README. +- Enable sandbox in all platforms. +- Enable `Gstreamer` feature. diff --git a/build.rs b/build.rs index 1d2e1d71..f7c76afc 100644 --- a/build.rs +++ b/build.rs @@ -8,4 +8,7 @@ fn main() { apple: { any(target_os = "ios", target_os = "macos") }, linux: { all(unix, not(apple), not(android)) }, } + + #[cfg(all(feature = "packager", target_os = "macos"))] + println!("cargo:rustc-link-arg=-Wl,-rpath,@executable_path/../Resources/lib"); } diff --git a/etc/package_libs.py b/etc/package_libs.py new file mode 100644 index 00000000..232a6868 --- /dev/null +++ b/etc/package_libs.py @@ -0,0 +1,103 @@ +import os +import os.path as path +import shutil +import subprocess +import sys + +def otool(s): + o = subprocess.Popen(['/usr/bin/otool', '-L', s], stdout=subprocess.PIPE) + for line in map(lambda s: s.decode('ascii'), o.stdout): + if line[0] == '\t': + yield line.split(' ', 1)[0][1:] + + +def install_name_tool(binary, *args): + try: + subprocess.check_call(['install_name_tool', *args, binary]) + except subprocess.CalledProcessError as e: + print("install_name_tool exited with return value %d" % e.returncode) + + +def change_link_name(binary, old, new): + install_name_tool(binary, '-change', old, f"@executable_path/{new}") + + +def is_system_library(lib): + return lib.startswith("/System/Library") or lib.startswith("/usr/lib") or ".asan." in lib + + +def is_relocatable_library(lib): + return lib.startswith("@rpath/") + + +def change_non_system_libraries_path(libraries, relative_path, binary): + for lib in libraries: + if is_system_library(lib) or is_relocatable_library(lib): + continue + new_path = path.join(relative_path, path.basename(lib)) + change_link_name(binary, lib, new_path) + + +def resolve_rpath(lib, rpath_root): + if not is_relocatable_library(lib): + return lib + + rpaths = ['', '../', 'gstreamer-1.0/'] + for rpath in rpaths: + full_path = rpath_root + lib.replace('@rpath/', rpath) + if path.exists(full_path): + return path.normpath(full_path) + + raise Exception("Unable to satisfy rpath dependency: " + lib) + + +def copy_dependencies(binary_path, lib_path, gst_lib_dir): + relative_path = path.relpath(lib_path, path.dirname(binary_path)) + "/" + + # Update binary libraries + binary_dependencies = set(otool(binary_path)) + change_non_system_libraries_path(binary_dependencies, relative_path, binary_path) + + # Update dependencies libraries + need_checked = binary_dependencies + checked = set() + while need_checked: + checking = set(need_checked) + need_checked = set() + for f in checking: + # No need to check these for their dylibs + if is_system_library(f): + continue + full_path = resolve_rpath(f, gst_lib_dir) + need_relinked = set(otool(full_path)) + new_path = path.join(lib_path, path.basename(full_path)) + if not path.exists(new_path): + shutil.copyfile(full_path, new_path) + change_non_system_libraries_path(need_relinked, relative_path, new_path) + need_checked.update(need_relinked) + checked.update(checking) + need_checked.difference_update(checked) + +def package_gstreamer_dylibs(bin): + gst_root = "/Library/Frameworks/GStreamer.framework/Versions/1.0" + lib_dir = path.join(path.dirname(bin), "lib") + if os.path.exists(lib_dir): + shutil.rmtree(lib_dir) + os.mkdir(lib_dir) + try: + copy_dependencies(bin, lib_dir, path.join(gst_root, 'lib', '')) + except Exception as e: + print("ERROR: could not package required dylibs") + print(e) + return False + return True + +if __name__ == '__main__': + try: + subprocess.check_call(['cargo', 'build', '--release', '--features', 'packager']) + except subprocess.CalledProcessError as e: + print("cargo build exited with return value %d" % e.returncode) + + if sys.platform == "darwin": + binary = "./target/release/verso" + package_gstreamer_dylibs(binary)