diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 02b047e..8c0ea55 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -16,14 +16,21 @@ on: schedule: - cron: '0 0 * * 1' +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + jobs: + # native build on desktop desktop: strategy: matrix: - os: [ubuntu-latest] + include: + - host: linux + image: ubuntu-latest - runs-on: ${{ matrix.os }} - name: desktop/${{ matrix.os }} + runs-on: ${{ matrix.image }} + name: desktop/${{ matrix.host }} steps: - name: Checkout @@ -35,10 +42,14 @@ jobs: channel: stable cache: true - - name: Install doit - uses: awalsh128/cache-apt-pkgs-action@v1.4.2 + - name: Install python + uses: actions/setup-python@v5 with: - packages: python3-doit + python-version: 3.11 + + - name: Install doit + run: | + pip install doit - name: Run dart analyzer run: | @@ -48,13 +59,22 @@ jobs: run: | doit test + # build for android on different hosts android: strategy: matrix: - os: [ubuntu-latest, macos-latest, windows-latest] + include: + - host: linux + image: ubuntu-latest - runs-on: ${{ matrix.os }} - name: android/${{ matrix.os }} + - host: macos + image: macos-latest + + - host: windows + image: windows-latest + + runs-on: ${{ matrix.image }} + name: android/${{ matrix.host }} steps: - name: Checkout @@ -97,9 +117,36 @@ jobs: run: | doit build:apk variant=release + # build for different targets on different hosts using docker + docker: + strategy: + matrix: + include: + - host: linux + target: android + image: ubuntu-latest + + runs-on: ${{ matrix.image }} + name: docker/${{ matrix.target }}-${{ matrix.host }} + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Build + if: runner.os != 'Windows' + run: | + ./script/docker_build.py + + - name: Build (Windows) + if: runner.os == 'Windows' + run: | + .\script\docker_build.bat + + # build and publish github release release: if: startsWith(github.ref, 'refs/tags/v') - needs: [desktop, android] + needs: [desktop, android, docker] runs-on: ubuntu-latest steps: diff --git a/android/app/build.gradle b/android/app/build.gradle index 7380142..0e84c0e 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -88,9 +88,10 @@ android { abortOnError false } - signingConfigs { - release { - if (System.getenv("SIGNING_STORE_FILE") != null) { + + if (System.getenv("SIGNING_STORE_FILE") != null) { + signingConfigs { + release { storeFile file(System.getenv("SIGNING_STORE_FILE")) storePassword System.getenv("SIGNING_STORE_PASSWORD") keyAlias System.getenv("SIGNING_KEY_ALIAS") @@ -110,7 +111,7 @@ android { if (System.getenv("SIGNING_STORE_FILE") != null) { signingConfig signingConfigs.release } else { - signingConfig = signingConfigs.debug + signingConfig signingConfigs.debug } } } diff --git a/docs/building/build_project.md b/docs/building/build_project.md index 94d7c7a..25006ae 100644 --- a/docs/building/build_project.md +++ b/docs/building/build_project.md @@ -1,9 +1,31 @@ # Build project -First follow instructions to set up environment: +## Build with docker + +The easiest way to build project is to use our pre-built [docker images](https://github.com/roc-streaming/dockerfiles) with Flutter SDK. In this case you don't need to set up build environment by yourself. + +First install Docker CE ([Linux](https://docs.docker.com/engine/install/)) or Docker Desktop ([macOS](https://docs.docker.com/desktop/install/mac-install/), [Windows](https://docs.docker.com/desktop/install/windows-install/)). In case of Docker Desktop, don't forget to open GUI and start engine. + +Then open terminal in project root and run: + + * On macOS and Linux: + + ./script/docker_build.py + + * On Windows: + + .\script\docker_build.bat + +After building, you can find APK here: + + dist/android/release/roc-droid-.apk + +## Build without docker + +First follow instructions to set up build environment: -* [Windows setup](./windows_setup.md) * [macOS and Linux setup](./macos_linux_setup.md) +* [Windows setup](./windows_setup.md) Then open terminal in project root and run: @@ -12,5 +34,3 @@ Then open terminal in project root and run: After building, you can find APK here: dist/android/release/roc-droid-.apk - -You can upload it to device and tap to install. diff --git a/mkdocs.yml b/mkdocs.yml index 840eed4..68ee4ef 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -19,8 +19,8 @@ nav: - Agent: architecture/agent.md - Building: - Build project: building/build_project.md - - Windows setup: building/windows_setup.md - macOS and Linux setup: building/macos_linux_setup.md + - Windows setup: building/windows_setup.md - Development: - Automation: development/automation.md - Testing: development/testing.md diff --git a/script/docker_build.bat b/script/docker_build.bat new file mode 100644 index 0000000..96bd4d8 --- /dev/null +++ b/script/docker_build.bat @@ -0,0 +1,105 @@ +@rem -*- coding: utf-8; mode: powershell -*- +@cd /d "%~dp0\.." +@powershell -ExecutionPolicy Unrestricted -Command ^ + "$Self = '%~f0'; $Target = '%~1'; iex ((gc $Self | out-string) -replace '(?s).*:start\r?\n', '')" +@exit /b +:start + +# On fresh Windows installation, there may be problems with running .ps1 file as is: +# .\script\docker_build.ps1 +# +# We may instead need: +# powershell -ExecutionPolicy Unrestricted -File .\script\docker_build.ps1 +# +# It would work, but is cumbersome. +# +# For convenience, we name file as .bat file (so that it can be invoked as is), but in the +# preable above we evaluate contents of the file after :start marker as a powershell script +# (so that we don't have to deal with batch programming madness). + +$ErrorActionPreference = "Stop" + +function Project-Version { + $content = Get-Content -Path "pubspec.yaml" -Raw + $match = [regex]::Match($content, "^version:\s*(\S+)\s*$", "Multiline") + return $match.Groups[1].Value +} + +function Print-Msg ($msg) { + Write-Host "$msg" -ForegroundColor Blue +} + +function Print-Err ($msg) { + Write-Host "$msg" -ForegroundColor Red +} + +function Run-Cmd ($cmd) { + Write-Host ($cmd -join " ") + + $proc = Start-Process -FilePath $cmd[0] ` + -ArgumentList ($cmd[1..$cmd.Length] | ForEach-Object { "'$_'" }) ` + -PassThru -Wait -NoNewWindow + + if ($proc.ExitCode -ne 0) { + Print-Err "Command failed with code $($proc.ExitCode)" + exit 1 + } +} + +if ($Target -eq "") { + $Target = "android" +} +if (-not (@("android") -contains $Target)) { + Write-Host "usage: .\script\docker_build.bat [android]" + exit 1 +} + +$cwd = Get-Location + +if ($Target -eq "android") { + $workDir = "/root/build" # container + $cacheDirs = @{ + # host: container + "${cwd}\build\dockercache\pub" = "/root/.pub-cache" + "${cwd}\build\dockercache\gradle" = "/root/.gradle" + "${cwd}\build\dockercache\android" = "/root/.android/cache" + } +} + +$dockerCmd = @( + "docker", "run", + "--rm", "-t", + "-w", $workDir, + "-v", "${cwd}:${workDir}" +) + +foreach ($pair in $cacheDirs.GetEnumerator()) { + $hostDir = $pair.Key + $containerDir = $pair.Value + New-Item -Path $hostDir -ItemType Directory -Force | Out-Null + $dockerCmd += @( + "-v", "${hostDir}:${containerDir}" + ) +} + +$dockerCmd += @( + "rocstreaming/env-flutter:${Target}" +) + +if ($Target -eq "android") { + $dockerCmd += @( + "flutter", "build", "apk", "--release" + ) +} + +Print-Msg "Running ${Target} build in docker" +Run-Cmd $dockerCmd + +if ($Target -eq "android") { + $appType = "apk" + $appFile = "roc-droid-$(Project-Version).apk" +} + +Print-Msg +Print-Msg "Copied ${Target} ${appType} to dist\${Target}\release\${appFile}" +Get-ChildItem "dist\${Target}\release" diff --git a/script/docker_build.py b/script/docker_build.py new file mode 100755 index 0000000..be24c2d --- /dev/null +++ b/script/docker_build.py @@ -0,0 +1,99 @@ +#! /usr/bin/env python3 + +import argparse +import os +import platform +import re +import subprocess +import sys + +os.chdir(os.path.join( + os.path.dirname(os.path.abspath(__file__)), '..')) + +# We have docker_build.bat that is a windows-specific version of docker_build.py. +# It exists so that docker build doesn't require python on windows. +# +# But if the user still runs docker_build.py on windows, we redirect call to +# docker_build.bat, as we don't want duplicating windows-specific logic in two scripts. +# +# The rest of the script assumes that we're on a Unix-like system. +if platform.system() == 'Windows': + try: + subprocess.check_call( + ['script/docker_build.bat'] + sys.argv[1:], shell=False) + exit(0) + except: + exit(1) + +def project_version(): + with open('pubspec.yaml') as fp: + m = re.search(r'^version:\s*(\S+)\s*$', fp.read(), re.MULTILINE) + return m.group(1) + +def print_msg(msg=''): + print(f'\033[1;34m{msg}\033[0m', file=sys.stderr) + +def run_cmd(cmd): + print(' '.join(cmd), file=sys.stderr) + try: + subprocess.check_call(cmd) + except: + exit(1) + +parser = argparse.ArgumentParser() +parser.add_argument('target', + nargs='?', + choices=['android'], default='android') + +args = parser.parse_args() + +cwd = os.path.abspath(os.getcwd()) +uid = os.getuid() +gid = os.getgid() + +work_dir = None +cache_dirs = {} + +if args.target == 'android': + work_dir = '/root/build' # container + cache_dirs = { + # host: container + f'{cwd}/build/dockercache/pub': '/root/.pub-cache', + f'{cwd}/build/dockercache/gradle': '/root/.gradle', + f'{cwd}/build/dockercache/android': '/root/.android/cache', + } + +docker_cmd = [ + 'docker', 'run', + '--rm', '-t', + '-u', f'{uid}:{gid}', + '-w', work_dir, + '-v', f'{cwd}:{work_dir}', +] + +for host_dir, container_dir in cache_dirs.items(): + os.makedirs(host_dir, exist_ok=True) + docker_cmd += [ + '-v', f'{host_dir}:{container_dir}', + ] + +docker_cmd += [ + f'rocstreaming/env-flutter:{args.target}', +] + +if args.target == 'android': + docker_cmd += [ + 'flutter', 'build', 'apk', '--release', + ] + +print_msg(f'Running {args.target} build in docker') +run_cmd(docker_cmd) + +if args.target == 'android': + app_type = 'apk' + app_file = f'roc-droid-{project_version()}.apk' + +print_msg() + +print_msg(f'Copied {args.target} {app_type} to dist/{args.target}/release/{app_file}') +run_cmd(['ls', '-lh', f'dist/{args.target}/release'])