diff --git a/.github/workflows/package_for_release.yml b/.github/workflows/package_for_release.yml new file mode 100644 index 000000000..8e052d3a5 --- /dev/null +++ b/.github/workflows/package_for_release.yml @@ -0,0 +1,49 @@ +name: package for release +on: + workflow_dispatch: {} + +jobs: + linux: + runs-on: ubuntu-latest + container: + image: ubuntu:mantic-20230712 + steps: + - name: 🛠 Install system dependencies + run: | + apt-get update -y -qq + apt-get install -y libegl1-mesa-dev libgl1-mesa-dri libxcb-xfixes0-dev ffmpeg libavcodec-dev libavformat-dev libavfilter-dev libavdevice-dev + # TMP until we need to build in docker container + apt-get install -y curl build-essential curl pkg-config libssl-dev libclang-dev git libnss3 libatk1.0-0 libatk-bridge2.0-0 libgdk-pixbuf2.0-0 libgtk-3-0 + - name: 🔧 Install the rust toolchain + uses: dtolnay/rust-toolchain@stable + + - name: 📥 Checkout repo + uses: actions/checkout@v3 + + - name: 📦 Package + run: cargo run --bin package_for_release --features standalone + + - uses: actions/upload-artifact@v3 + with: + name: video_compositor_linux_x86_64.tar.gz + path: video_compositor_linux_x86_64.tar.gz + + macos: + runs-on: macos-latest + steps: + - name: 🛠 Install system dependencies + run: brew install ffmpeg + + - name: 🔧 Install the rust toolchain + uses: dtolnay/rust-toolchain@stable + + - name: 📥 Checkout repo + uses: actions/checkout@v3 + + - name: 📦 Package + run: cargo run --bin package_for_release --features standalone + + - uses: actions/upload-artifact@v3 + with: + name: video_compositor_darwin_x86_64.app.tar.gz + path: video_compositor_darwin_x86_64.app.tar.gz diff --git a/.github/workflows/shellcheck.yml b/.github/workflows/shellcheck.yml new file mode 100644 index 000000000..b1cf7940f --- /dev/null +++ b/.github/workflows/shellcheck.yml @@ -0,0 +1,28 @@ +name: shellcheck +on: + push: + branches: [master] + paths: + - "scripts/*" + - .github/workflows/shellcheck.yml + pull_request: + types: [opened, synchronize] + paths: + - "scripts/*" + - .github/workflows/shellcheck.yml + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: ${{ github.ref != 'refs/heads/master' }} + +jobs: + dockerfile: + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v2 + + - name: shellcheck + run: | + shellcheck ./scripts/release.sh ./scripts/compositor_runtime_wrapper.sh diff --git a/.gitignore b/.gitignore index 3180cf790..772995a6b 100644 --- a/.gitignore +++ b/.gitignore @@ -26,3 +26,9 @@ target/ # Generated during runtime /shmem + +/video_compositor.app +/video_compositor +/video_compositor_linux_x86_64.tar.gz +/video_compositor_darwin_aarch64.app.tar.gz +/video_compositor_darwin_x86_64.app.tar.gz diff --git a/Cargo.lock b/Cargo.lock index d8ed04013..3b8f4c44e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -364,6 +364,7 @@ checksum = "bf43edc576402991846b093a7ca18a3477e0ef9c588cde84964b5d3e43016642" name = "compositor_chromium" version = "0.1.0" dependencies = [ + "anyhow", "chromium_sys", "compositor_common", "fs_extra", @@ -2814,6 +2815,7 @@ dependencies = [ "crossbeam-channel", "env_logger", "ffmpeg-next", + "fs_extra", "lazy_static", "log", "reqwest", diff --git a/Cargo.toml b/Cargo.toml index eb7a4ed5e..c7b6609a4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,6 +14,10 @@ members = [ ] resolver = "2" +[features] +default = [] +standalone = ["compositor_chromium/standalone"] + [workspace.dependencies] bytes = "1.4.0" env_logger = "0.10.0" @@ -27,6 +31,7 @@ reqwest = { version = "0.11.18", features = ["blocking", "json"] } signal-hook = "0.3.15" shared_memory = "0.12.4" ffmpeg-next = "6.0.0" +anyhow = "1.0.71" [dependencies] compositor_render = { path = "compositor_render" } @@ -35,7 +40,7 @@ compositor_pipeline = { path = "compositor_pipeline" } compositor_chromium = { path = "compositor_chromium" } serde = { workspace = true } serde_json = { workspace = true } -anyhow = "1.0.71" +anyhow = { workspace = true } bytes = { workspace = true } tiny_http = "0.12.0" ffmpeg-next = { workspace = true } @@ -45,6 +50,7 @@ log = { workspace = true } signal-hook = { workspace = true } shared_memory = { workspace = true } lazy_static = "1.4.0" +fs_extra = "1.3.0" [dev-dependencies] reqwest = { workspace = true } diff --git a/RELEASE.md b/RELEASE.md new file mode 100644 index 000000000..bca9e7593 --- /dev/null +++ b/RELEASE.md @@ -0,0 +1,24 @@ +# Release + +To release a new compositor version: + +- Go to `Actions` -> [`package for release`](https://github.com/membraneframework/video_compositor/actions/workflows/release.yml) -> Dispatch new workflow on a master branch. +- Wait for a build to finish. +- Run `gh run list --workflow "package for release"` and find ID of workflow run you just run. +- Run `WORKFLOW_RUN_ID={WORKFLOW_RUN_ID} RELEASE_TAG={VERSION} ./scripts/release.sh` where `WORKFLOW_RUN_ID` is an ID from the previous step, and `VERSION` has a format `v{major}.{minor}.{patch}`. e.g. `WORKFLOW_RUN_ID=6302155380 RELEASE_TAG=v1.2.3 ./scripts/release.sh ` + + +### Temporary workaround for macOS M1 binaries + +Currently we do not have a CI to build for macOS M1, so for now compositor releases are run from a developer device. + +To publish binaries for M1 devices, first follow instructions above for other platform. After GitHub release is created you can add binaries for M1 by running bellow command on M1 mac. + +```bash +RELEASE_TAG={VERSION} cargo run --bin package_for_release --features standalone +``` + +e.g. +```bash +RELEASE_TAG=v1.2.3 cargo run --bin package_for_release --features standalone +``` diff --git a/compositor_chromium/Cargo.toml b/compositor_chromium/Cargo.toml index df2de6a64..b6f2266c9 100644 --- a/compositor_chromium/Cargo.toml +++ b/compositor_chromium/Cargo.toml @@ -5,6 +5,10 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html +[features] +default = [] +standalone = [] + [dependencies] chromium_sys = { path = "chromium_sys" } compositor_common = { path = "../compositor_common" } @@ -12,3 +16,4 @@ fs_extra = "1.3.0" thiserror = { workspace = true } widestring = "1.0.2" log = { workspace = true } +anyhow = { workspace = true } diff --git a/compositor_chromium/chromium_sys/build.rs b/compositor_chromium/chromium_sys/build.rs index 12e56a3de..13e83311c 100644 --- a/compositor_chromium/chromium_sys/build.rs +++ b/compositor_chromium/chromium_sys/build.rs @@ -1,6 +1,7 @@ use anyhow::{Context, Result}; use bindgen::callbacks::ParseCallbacks; use fs_extra::dir::{self, CopyOptions}; +use reqwest::StatusCode; use std::{ env, fs, path::{Path, PathBuf}, @@ -124,27 +125,31 @@ impl ParseCallbacks for RemoveCommentsCallback { } } -fn download_cef(cef_root_path: &Path) -> Result<()> { - let platform = if cfg!(target_os = "macos") { +fn cef_url() -> &'static str { + if cfg!(target_os = "macos") { if cfg!(target_arch = "aarch64") { - "macosarm64" - } else { - "macosx64" + return "https://cef-builds.spotifycdn.com/cef_binary_117.1.4%2Bga26f38b%2Bchromium-117.0.5938.92_macosarm64_minimal.tar.bz2"; + } else if cfg!(target_arch = "x86_64") { + return "https://cef-builds.spotifycdn.com/cef_binary_115.3.11%2Bga61da9b%2Bchromium-115.0.5790.114_macosx64_minimal.tar.bz2"; } } else if cfg!(target_os = "linux") { - "linux64" - } else { - panic!("Unsupported platform"); + return "https://cef-builds.spotifycdn.com/cef_binary_117.1.4%2Bga26f38b%2Bchromium-117.0.5938.92_linux64_minimal.tar.bz2"; }; + panic!("Unsupported platform"); +} +fn download_cef(cef_root_path: &Path) -> Result<()> { + let url = cef_url(); let download_path = cef_root_path .parent() .context("Failed to retrieve CEF_ROOT parent directory")?; - let url = format!("https://cef-builds.spotifycdn.com/cef_binary_117.1.4%2Bga26f38b%2Bchromium-117.0.5938.92_{platform}_minimal.tar.bz2"); let client = reqwest::blocking::ClientBuilder::new() .timeout(Duration::from_secs(2 * 60)) .build()?; let resp = client.get(url).send()?; + if resp.status() != StatusCode::OK { + panic!("Request to {} failed. Status code: {}", url, resp.status()); + } let archive_name = "cef.tar.bz2"; let content = resp.bytes()?; diff --git a/compositor_chromium/examples/simple.rs b/compositor_chromium/examples/simple.rs index c819804f7..8303b2399 100644 --- a/compositor_chromium/examples/simple.rs +++ b/compositor_chromium/examples/simple.rs @@ -86,7 +86,7 @@ fn main() { .unwrap() .join(".."); - if cef::bundle_app(target_path).is_err() { + if cef::bundle_for_development(target_path).is_err() { panic!("Build process helper first: cargo build --bin process_helper"); } diff --git a/compositor_chromium/src/settings.rs b/compositor_chromium/src/settings.rs index 43fc6a8e2..e8c94e3c6 100644 --- a/compositor_chromium/src/settings.rs +++ b/compositor_chromium/src/settings.rs @@ -1,7 +1,11 @@ -use std::{env, os::raw::c_int, path::PathBuf}; +use std::{env, os::raw::c_int}; + +use chromium_sys::_cef_string_utf16_t; use crate::cef_string::CefString; +pub const PROCESS_HELPER_PATH_ENV: &str = "MEMBRANE_VIDEO_COMPOSITOR_PROCESS_HELPER_PATH"; + /// Main process settings #[derive(Default)] pub struct Settings { @@ -18,38 +22,14 @@ pub struct Settings { impl Settings { pub fn into_raw(self) -> chromium_sys::cef_settings_t { - let current_exe = env::current_exe().unwrap(); - let current_dir = current_exe.parent().unwrap(); - - let main_bundle_path = if cfg!(target_os = "linux") { - String::new() - } else { - PathBuf::from(current_dir) - .join("video_compositor.app") - .display() - .to_string() - }; - - let browser_subprocess_path = if cfg!(target_os = "linux") { - current_dir.join("process_helper").display().to_string() - } else { - PathBuf::from(&main_bundle_path) - .join("Contents") - .join("Frameworks") - .join("video_compositor Helper.app") - .join("Contents") - .join("MacOS") - .join("video_compositor Helper") - .display() - .to_string() - }; + let (main_path, helper_path) = executables_paths(); chromium_sys::cef_settings_t { size: std::mem::size_of::(), no_sandbox: true as c_int, - browser_subprocess_path: CefString::new_raw(browser_subprocess_path), + browser_subprocess_path: helper_path, framework_dir_path: CefString::empty_raw(), - main_bundle_path: CefString::new_raw(main_bundle_path), + main_bundle_path: main_path, chrome_runtime: false as c_int, multi_threaded_message_loop: self.multi_threaded_message_loop as c_int, external_message_pump: self.external_message_pump as c_int, @@ -64,6 +44,7 @@ impl Settings { locale: CefString::empty_raw(), log_file: CefString::empty_raw(), log_severity: self.log_severity as u32, + #[cfg(not(all(target_os = "macos", target_arch = "x86_64")))] log_items: chromium_sys::cef_log_items_t_LOG_ITEMS_DEFAULT, javascript_flags: CefString::empty_raw(), resources_dir_path: CefString::empty_raw(), @@ -95,3 +76,49 @@ impl Default for LogSeverity { Self::Default } } + +#[cfg(target_os = "linux")] +fn executables_paths() -> (_cef_string_utf16_t, _cef_string_utf16_t) { + let browser_subprocess_path = env::var(PROCESS_HELPER_PATH_ENV).unwrap_or_else(|_| { + let current_exe = env::current_exe().unwrap(); + let current_dir = current_exe.parent().unwrap(); + current_dir.join("process_helper").display().to_string() + }); + + ( + CefString::empty_raw(), + CefString::new_raw(browser_subprocess_path), + ) +} + +#[cfg(target_os = "macos")] +fn executables_paths() -> (_cef_string_utf16_t, _cef_string_utf16_t) { + use std::path::PathBuf; + + if cfg!(feature = "standalone") { + return (CefString::empty_raw(), CefString::empty_raw()); + } + + let current_exe = env::current_exe().unwrap(); + let current_dir = current_exe.parent().unwrap(); + + let main_bundle_path = PathBuf::from(current_dir) + .join("video_compositor.app") + .display() + .to_string(); + + let browser_subprocess_path = PathBuf::from(&main_bundle_path) + .join("Contents") + .join("Frameworks") + .join("video_compositor Helper.app") + .join("Contents") + .join("MacOS") + .join("video_compositor Helper") + .display() + .to_string(); + + ( + CefString::new_raw(main_bundle_path), + CefString::new_raw(browser_subprocess_path), + ) +} diff --git a/compositor_chromium/src/utils.rs b/compositor_chromium/src/utils.rs index bb081fabc..568d0a9e7 100644 --- a/compositor_chromium/src/utils.rs +++ b/compositor_chromium/src/utils.rs @@ -1,11 +1,26 @@ -use std::error::Error; -use std::path::Path; -use std::{env, fs}; +use anyhow::Result; +use std::env; +use std::{fs, path::Path}; + +/// Creates MacOS app bundle in the same directory as the main executable. +/// Bundles `process_helper` into multiple subprocess bundles. +/// Copies CEF and subprocess bundles to `Frameworks` directory. +/// `process_helper` has to be built before the function is called +#[cfg(target_os = "macos")] +pub fn bundle_for_development(target_path: &Path) -> Result<()> { + let current_exe = env::current_exe()?; + let current_dir = current_exe.parent().unwrap(); + let bundle_path = current_dir.join("video_compositor.app"); + + bundle_app(target_path, &bundle_path)?; + + Ok(()) +} /// Moves the `process_helper` to the same directory as the main executable /// `process_helper` has to be built before the function is called #[cfg(target_os = "linux")] -pub fn bundle_app(target_path: &Path) -> Result<(), Box> { +pub fn bundle_for_development(target_path: &Path) -> Result<()> { let current_exe = env::current_exe()?; let current_dir = current_exe.parent().unwrap(); @@ -19,24 +34,24 @@ pub fn bundle_app(target_path: &Path) -> Result<(), Box> { Ok(()) } -/// Creates MacOS app bundle in the same directory as the main executable. -/// Bundles `process_helper` into multiple subprocess bundles. -/// Copies CEF and subprocess bundles to `Frameworks` directory. -/// `process_helper` has to be built before the function is called #[cfg(target_os = "macos")] -pub fn bundle_app(target_path: &Path) -> Result<(), Box> { +pub fn bundle_app(target_path: &Path, bundle_path: &Path) -> Result<()> { use fs_extra::dir::{self, CopyOptions}; - let current_exe = env::current_exe()?; - let current_dir = current_exe.parent().unwrap(); - let bundle_path = current_dir.join("video_compositor.app").join("Contents"); - - let _ = fs::remove_dir_all(&bundle_path); + let _ = fs::remove_dir_all(bundle_path); + let bundle_path = bundle_path.join("Contents"); for dir in ["MacOS", "Resources"] { fs::create_dir_all(bundle_path.join(dir))?; } + if cfg!(feature = "standalone") { + fs::copy( + target_path.join("main_process"), + bundle_path.join("MacOS/video_compositor"), + )?; + } + dir::copy( target_path.join("Frameworks"), &bundle_path, @@ -60,7 +75,13 @@ pub fn bundle_app(target_path: &Path) -> Result<(), Box> { ]; for (name, bundle_id) in helpers { - bundle_helper(name, bundle_id, &helper_info, target_path, &bundle_path)?; + bundle_helper( + name, + bundle_id, + &helper_info, + &target_path.join("process_helper"), + &bundle_path, + )?; } Ok(()) @@ -71,9 +92,9 @@ fn bundle_helper( name: &str, bundle_id: &str, info_data: &str, - target_path: &Path, + helper_path: &Path, bundle_path: &Path, -) -> Result<(), Box> { +) -> Result<()> { let bundle_path = bundle_path .join("Frameworks") .join(format!("{name}.app")) @@ -83,10 +104,7 @@ fn bundle_helper( fs::create_dir_all(bundle_path.join(dir))?; } - fs::copy( - target_path.join("process_helper"), - bundle_path.join("MacOS").join(name), - )?; + fs::copy(helper_path, bundle_path.join("MacOS").join(name))?; let info_data = info_data .replace("${EXECUTABLE_NAME}", name) diff --git a/examples/web_renderer.rs b/examples/web_renderer.rs index 3b65a5e93..46e75ceb9 100644 --- a/examples/web_renderer.rs +++ b/examples/web_renderer.rs @@ -1,5 +1,5 @@ use anyhow::Result; -use compositor_chromium::cef; +use compositor_chromium::cef::bundle_for_development; use compositor_common::{scene::Resolution, Framerate}; use log::{error, info}; use serde_json::json; @@ -38,8 +38,11 @@ fn main() { .unwrap() .join(".."); - if cef::bundle_app(target_path).is_err() { - panic!("Build process helper first: cargo build --bin process_helper"); + if let Err(err) = bundle_for_development(target_path) { + panic!( + "Build process helper first: cargo build --bin process_helper. {:?}", + err + ); } thread::spawn(|| { if let Err(err) = start_example_client_code() { diff --git a/scripts/compositor_runtime_wrapper.sh b/scripts/compositor_runtime_wrapper.sh new file mode 100755 index 000000000..a2b1a2879 --- /dev/null +++ b/scripts/compositor_runtime_wrapper.sh @@ -0,0 +1,10 @@ +#!/usr/bin/env bash + +set -eo pipefail + +SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" + +export LD_LIBRARY_PATH="$SCRIPT_DIR/lib:$LD_LIBRARY_PATH" +export MEMBRANE_VIDEO_COMPOSITOR_PROCESS_HELPER_PATH="$SCRIPT_DIR/video_compositor_process_helper" + +exec "$SCRIPT_DIR/video_compositor_main" "$@" diff --git a/scripts/release.sh b/scripts/release.sh new file mode 100755 index 000000000..5d599fae5 --- /dev/null +++ b/scripts/release.sh @@ -0,0 +1,31 @@ +#!/usr/bin/env bash + +set -euo pipefail + +ROOT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )"/.. && pwd )" + +set +u +if [[ -z "$WORKFLOW_RUN_ID" ]]; then + echo "WORKFLOW_RUN_ID env variable is required. You can list recent runs using gh run list --workflow \"package for release\" command." + echo "" + echo "Recent workflow runs:" + gh run list --workflow "package for release" | cat + exit 1 +fi + +if [[ -z "$RELEASE_TAG" ]]; then + echo "RELEASE_TAG env variable is required." + exit 1 +fi +set -u + +mkdir -p "$ROOT_DIR/release_tmp" +cd "$ROOT_DIR/release_tmp" + +gh run download "$WORKFLOW_RUN_ID" -n video_compositor_linux_x86_64.tar.gz +gh run download "$WORKFLOW_RUN_ID" -n video_compositor_darwin_x86_64.app.tar.gz +gh release create "$RELEASE_TAG" +gh release upload "$RELEASE_TAG" video_compositor_linux_x86_64.tar.gz +gh release upload "$RELEASE_TAG" video_compositor_darwin_x86_64.app.tar.gz + +rm -rf "$ROOT_DIR/release_tmp" diff --git a/src/bin/main_process.rs b/src/bin/main_process.rs new file mode 100644 index 000000000..da311ee95 --- /dev/null +++ b/src/bin/main_process.rs @@ -0,0 +1,20 @@ +use std::env; + +use log::info; +use video_compositor::http; + +pub const API_PORT_ENV: &str = "MEMBRANE_VIDEO_COMPOSITOR_API_PORT"; + +fn main() { + env_logger::init_from_env( + env_logger::Env::default().filter_or(env_logger::DEFAULT_FILTER_ENV, "info"), + ); + + ffmpeg_next::format::network::init(); + + let port = env::var(API_PORT_ENV).unwrap_or_else(|_| "8001".to_string()); + http::Server::new(port.parse::().unwrap()).run(); + + info!("Received exit signal. Terminating...") + // TODO: add graceful shutdown +} diff --git a/src/bin/package_for_release/bundle_linux.rs b/src/bin/package_for_release/bundle_linux.rs new file mode 100644 index 000000000..8876c156f --- /dev/null +++ b/src/bin/package_for_release/bundle_linux.rs @@ -0,0 +1,71 @@ +use anyhow::{anyhow, Result}; +use fs_extra::dir::{self, CopyOptions}; +use log::info; +use std::fs; +use std::path::PathBuf; +use std::process::Command; + +use crate::utils; + +const X86_TARGET: &str = "x86_64-unknown-linux-gnu"; + +pub fn bundle_linux_app() -> Result<()> { + env_logger::init_from_env( + env_logger::Env::default().filter_or(env_logger::DEFAULT_FILTER_ENV, "info"), + ); + let root_dir_str = env!("CARGO_MANIFEST_DIR"); + let root_dir: PathBuf = root_dir_str.into(); + let release_dir = root_dir.join("target/x86_64-unknown-linux-gnu/release"); + let tmp_dir = root_dir.join("video_compositor"); + + info!("Build main_process binary."); + utils::cargo_build("main_process", X86_TARGET)?; + info!("Build process_helper binary."); + utils::cargo_build("process_helper", X86_TARGET)?; + + info!("Create {} directory", tmp_dir.display()); + fs::create_dir_all(tmp_dir.clone())?; + + info!("Copy main_process binary."); + fs::copy( + release_dir.join("main_process"), + tmp_dir.join("video_compositor_main"), + )?; + info!("Copy process_helper binary."); + fs::copy( + release_dir.join("process_helper"), + tmp_dir.join("video_compositor_process_helper"), + )?; + + info!("Copy wrapper script."); + fs::copy( + root_dir.join("scripts/compositor_runtime_wrapper.sh"), + tmp_dir.join("video_compositor"), + )?; + + info!( + "Copy lib directory. {:?} {:?}", + release_dir.join("lib"), + tmp_dir.join("lib"), + ); + + dir::copy(release_dir.join("lib"), tmp_dir, &CopyOptions::default())?; + + info!("Create tar.gz archive."); + let exit_code = Command::new("tar") + .args([ + "-C", + root_dir_str, + "-czvf", + "video_compositor_linux_x86_64.tar.gz", + "video_compositor", + ]) + .spawn()? + .wait()? + .code(); + if exit_code != Some(0) { + return Err(anyhow!("Command tar failed with exit code {:?}", exit_code)); + } + + Ok(()) +} diff --git a/src/bin/package_for_release/bundle_macos.rs b/src/bin/package_for_release/bundle_macos.rs new file mode 100644 index 000000000..f3463dc45 --- /dev/null +++ b/src/bin/package_for_release/bundle_macos.rs @@ -0,0 +1,79 @@ +use std::env; +use std::path::PathBuf; +use std::process::Command; + +use anyhow::{anyhow, Result}; +use log::info; + +use crate::utils; +use compositor_chromium::cef; + +const ARM_MAC_TARGET: &str = "aarch64-apple-darwin"; +const ARM_OUTPUT_FILE: &str = "video_compositor_darwin_aarch64.app.tar.gz"; +const INTEL_MAC_TARGET: &str = "x86_64-apple-darwin"; +const INTEL_OUTPUT_FILE: &str = "video_compositor_darwin_x86_64.app.tar.gz"; + +pub fn bundle_macos_app() -> Result<()> { + if cfg!(target_arch = "x86_64") { + bundle_app(INTEL_MAC_TARGET, INTEL_OUTPUT_FILE)?; + } else if cfg!(target_arch = "aarch64") { + bundle_app(ARM_MAC_TARGET, ARM_OUTPUT_FILE)?; + // We do not have CI with M1 yet, so this will be built locally for now. + let release_tag = env::var("RELEASE_TAG")?; + let root_dir_str = env!("CARGO_MANIFEST_DIR"); + let root_dir: PathBuf = root_dir_str.into(); + let upload_path = root_dir.join(ARM_OUTPUT_FILE); + let exit_code = Command::new("gh") + .args([ + "release", + "upload", + &release_tag, + &upload_path.display().to_string(), + ]) + .spawn()? + .wait()? + .code(); + if exit_code != Some(0) { + return Err(anyhow!("Command gh failed with exit code {:?}", exit_code)); + } + } else { + panic!("Unknown architecture") + } + Ok(()) +} + +fn bundle_app(target: &'static str, output_name: &'static str) -> Result<()> { + env_logger::init_from_env( + env_logger::Env::default().filter_or(env_logger::DEFAULT_FILTER_ENV, "info"), + ); + let root_dir_str = env!("CARGO_MANIFEST_DIR"); + let root_dir: PathBuf = root_dir_str.into(); + let build_dir = root_dir.join(format!("target/{target}/release")); + let tmp_dir = root_dir.join("video_compositor.app"); + + info!("Build main_process binary."); + utils::cargo_build("main_process", target)?; + info!("Build process_helper binary."); + utils::cargo_build("process_helper", target)?; + + info!("Create macOS bundle."); + cef::bundle_app(&build_dir, &tmp_dir)?; + + info!("Create tar.gz archive."); + let exit_code = Command::new("tar") + .args([ + "-C", + root_dir_str, + "-czvf", + output_name, + "video_compositor.app", + ]) + .spawn()? + .wait()? + .code(); + if exit_code != Some(0) { + return Err(anyhow!("Command tar failed with exit code {:?}", exit_code)); + } + + Ok(()) +} diff --git a/src/bin/package_for_release/main.rs b/src/bin/package_for_release/main.rs new file mode 100644 index 000000000..36edc2972 --- /dev/null +++ b/src/bin/package_for_release/main.rs @@ -0,0 +1,15 @@ +#[cfg(target_os = "linux")] +mod bundle_linux; +#[cfg(target_os = "macos")] +mod bundle_macos; +mod utils; + +#[cfg(target_os = "linux")] +fn main() { + bundle_linux::bundle_linux_app().unwrap(); +} + +#[cfg(target_os = "macos")] +fn main() { + bundle_macos::bundle_macos_app().unwrap(); +} diff --git a/src/bin/package_for_release/utils.rs b/src/bin/package_for_release/utils.rs new file mode 100644 index 000000000..09fc907ff --- /dev/null +++ b/src/bin/package_for_release/utils.rs @@ -0,0 +1,28 @@ +use anyhow::{anyhow, Result}; +use log::{info, warn}; +use std::{process::Command, str::from_utf8}; + +pub fn cargo_build(bin: &'static str, target: &'static str) -> Result<()> { + let args = vec![ + "build", + "--release", + "--features", + "standalone", + "--target", + target, + "--locked", + "--bin", + bin, + ]; + info!("Running command \"cargo {}\"", args.join(" ")); + let output = Command::new("cargo") + .args(args) + .spawn()? + .wait_with_output()?; + if !output.status.success() { + warn!("stdout: {:?}", &from_utf8(&output.stdout)); + warn!("stderr: {:?}", &from_utf8(&output.stderr)); + return Err(anyhow!("Command failed with exit code {}.", output.status)); + } + Ok(()) +} diff --git a/src/main.rs b/src/main.rs index ef031f99e..b856562a1 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,4 +1,6 @@ -use compositor_chromium::cef; +use std::env; + +use compositor_chromium::cef::bundle_for_development; use log::info; mod api; @@ -7,6 +9,8 @@ mod http; mod rtp_receiver; mod rtp_sender; +pub const API_PORT_ENV: &str = "MEMBRANE_VIDEO_COMPOSITOR_API_PORT"; + fn main() { env_logger::init_from_env( env_logger::Env::default().filter_or(env_logger::DEFAULT_FILTER_ENV, "info"), @@ -17,13 +21,14 @@ fn main() { .parent() .unwrap() .to_owned(); - if cef::bundle_app(&target_path).is_err() { + if bundle_for_development(&target_path).is_err() { panic!("Build process helper first: cargo build --bin process_helper"); } ffmpeg_next::format::network::init(); - http::Server::new(8001).run(); + let port = env::var(API_PORT_ENV).unwrap_or_else(|_| "8001".to_string()); + http::Server::new(port.parse::().unwrap()).run(); info!("Received exit signal. Terminating...") // TODO: add graceful shutdown