├── .github ├── PULL_REQUEST_TEMPLATE.md ├── actions │ ├── dart_package │ │ └── action.yaml │ ├── flutter_package │ │ └── action.yaml │ ├── publish_flutter_package │ │ └── action.yaml │ └── rust_crate │ │ └── action.yaml ├── codecov.yml ├── dependabot.yml └── workflows │ ├── build_patch_artifacts.yaml │ ├── main.yaml │ └── publish.yaml ├── .gitignore ├── .vscode └── settings.json ├── COPYRIGHT ├── Cargo.lock ├── Cargo.toml ├── LICENSE-APACHE ├── LICENSE-MIT ├── README.md ├── cspell.config.yaml ├── library ├── .cargo │ └── config.toml ├── .gitignore ├── Cargo.toml ├── README.md ├── build.rs ├── cbindgen.toml ├── include │ └── updater.h └── src │ ├── android.rs │ ├── c_api │ ├── c_file.rs │ └── mod.rs │ ├── cache │ ├── disk_io.rs │ ├── mod.rs │ ├── patch_manager.rs │ ├── signing.rs │ └── updater_state.rs │ ├── config.rs │ ├── events.rs │ ├── lib.rs │ ├── logging.rs │ ├── logging_macros.rs │ ├── network.rs │ ├── test_utils.rs │ ├── time.rs │ ├── updater.rs │ ├── updater_lock.rs │ └── yaml.rs ├── patch ├── .cargo │ └── config.toml ├── Cargo.toml ├── README.md └── src │ ├── bin │ └── string_patch.rs │ ├── lib.rs │ └── main.rs └── shorebird_code_push ├── .gitignore ├── CHANGELOG.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── analysis_options.yaml ├── coverage_badge.svg ├── example ├── .gitignore ├── .metadata ├── README.md ├── analysis_options.yaml ├── android │ ├── .gitignore │ ├── app │ │ ├── build.gradle.kts │ │ └── src │ │ │ ├── main │ │ │ ├── AndroidManifest.xml │ │ │ ├── kotlin │ │ │ │ └── dev │ │ │ │ │ └── shorebird │ │ │ │ │ └── shorebird_code_push_example │ │ │ │ │ └── MainActivity.kt │ │ │ └── res │ │ │ │ ├── drawable-v21 │ │ │ │ └── launch_background.xml │ │ │ │ ├── drawable │ │ │ │ └── launch_background.xml │ │ │ │ ├── mipmap-hdpi │ │ │ │ └── ic_launcher.png │ │ │ │ ├── mipmap-mdpi │ │ │ │ └── ic_launcher.png │ │ │ │ ├── mipmap-xhdpi │ │ │ │ └── ic_launcher.png │ │ │ │ ├── mipmap-xxhdpi │ │ │ │ └── ic_launcher.png │ │ │ │ ├── mipmap-xxxhdpi │ │ │ │ └── ic_launcher.png │ │ │ │ ├── values-night │ │ │ │ └── styles.xml │ │ │ │ └── values │ │ │ │ └── styles.xml │ │ │ └── profile │ │ │ └── AndroidManifest.xml │ ├── build.gradle.kts │ ├── gradle.properties │ ├── gradle │ │ └── wrapper │ │ │ └── gradle-wrapper.properties │ └── settings.gradle.kts ├── ios │ ├── .gitignore │ ├── Flutter │ │ ├── AppFrameworkInfo.plist │ │ ├── Debug.xcconfig │ │ └── Release.xcconfig │ ├── Podfile │ ├── Podfile.lock │ ├── Runner.xcodeproj │ │ ├── project.pbxproj │ │ ├── project.xcworkspace │ │ │ ├── contents.xcworkspacedata │ │ │ └── xcshareddata │ │ │ │ ├── IDEWorkspaceChecks.plist │ │ │ │ └── WorkspaceSettings.xcsettings │ │ └── xcshareddata │ │ │ └── xcschemes │ │ │ └── Runner.xcscheme │ ├── Runner.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ ├── IDEWorkspaceChecks.plist │ │ │ └── WorkspaceSettings.xcsettings │ ├── Runner │ │ ├── AppDelegate.swift │ │ ├── Assets.xcassets │ │ │ ├── AppIcon.appiconset │ │ │ │ ├── Contents.json │ │ │ │ ├── Icon-App-1024x1024@1x.png │ │ │ │ ├── Icon-App-20x20@1x.png │ │ │ │ ├── Icon-App-20x20@2x.png │ │ │ │ ├── Icon-App-20x20@3x.png │ │ │ │ ├── Icon-App-29x29@1x.png │ │ │ │ ├── Icon-App-29x29@2x.png │ │ │ │ ├── Icon-App-29x29@3x.png │ │ │ │ ├── Icon-App-40x40@1x.png │ │ │ │ ├── Icon-App-40x40@2x.png │ │ │ │ ├── Icon-App-40x40@3x.png │ │ │ │ ├── Icon-App-60x60@2x.png │ │ │ │ ├── Icon-App-60x60@3x.png │ │ │ │ ├── Icon-App-76x76@1x.png │ │ │ │ ├── Icon-App-76x76@2x.png │ │ │ │ └── Icon-App-83.5x83.5@2x.png │ │ │ └── LaunchImage.imageset │ │ │ │ ├── Contents.json │ │ │ │ ├── LaunchImage.png │ │ │ │ ├── LaunchImage@2x.png │ │ │ │ ├── LaunchImage@3x.png │ │ │ │ └── README.md │ │ ├── Base.lproj │ │ │ ├── LaunchScreen.storyboard │ │ │ └── Main.storyboard │ │ ├── Info.plist │ │ └── Runner-Bridging-Header.h │ └── RunnerTests │ │ └── RunnerTests.swift ├── lib │ └── main.dart ├── linux │ ├── .gitignore │ ├── CMakeLists.txt │ ├── flutter │ │ ├── CMakeLists.txt │ │ ├── generated_plugin_registrant.cc │ │ ├── generated_plugin_registrant.h │ │ └── generated_plugins.cmake │ ├── main.cc │ ├── my_application.cc │ └── my_application.h ├── macos │ ├── .gitignore │ ├── Flutter │ │ ├── Flutter-Debug.xcconfig │ │ ├── Flutter-Release.xcconfig │ │ └── GeneratedPluginRegistrant.swift │ ├── Podfile │ ├── Runner.xcodeproj │ │ ├── project.pbxproj │ │ ├── project.xcworkspace │ │ │ └── xcshareddata │ │ │ │ └── IDEWorkspaceChecks.plist │ │ └── xcshareddata │ │ │ └── xcschemes │ │ │ └── Runner.xcscheme │ ├── Runner.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ └── IDEWorkspaceChecks.plist │ ├── Runner │ │ ├── AppDelegate.swift │ │ ├── Assets.xcassets │ │ │ └── AppIcon.appiconset │ │ │ │ ├── Contents.json │ │ │ │ ├── app_icon_1024.png │ │ │ │ ├── app_icon_128.png │ │ │ │ ├── app_icon_16.png │ │ │ │ ├── app_icon_256.png │ │ │ │ ├── app_icon_32.png │ │ │ │ ├── app_icon_512.png │ │ │ │ └── app_icon_64.png │ │ ├── Base.lproj │ │ │ └── MainMenu.xib │ │ ├── Configs │ │ │ ├── AppInfo.xcconfig │ │ │ ├── Debug.xcconfig │ │ │ ├── Release.xcconfig │ │ │ └── Warnings.xcconfig │ │ ├── DebugProfile.entitlements │ │ ├── Info.plist │ │ ├── MainFlutterWindow.swift │ │ └── Release.entitlements │ └── RunnerTests │ │ └── RunnerTests.swift ├── pubspec.yaml ├── shorebird.yaml ├── web │ ├── favicon.png │ ├── icons │ │ ├── Icon-192.png │ │ ├── Icon-512.png │ │ ├── Icon-maskable-192.png │ │ └── Icon-maskable-512.png │ ├── index.html │ └── manifest.json └── windows │ ├── .gitignore │ ├── CMakeLists.txt │ ├── flutter │ ├── CMakeLists.txt │ ├── generated_plugin_registrant.cc │ ├── generated_plugin_registrant.h │ └── generated_plugins.cmake │ └── runner │ ├── CMakeLists.txt │ ├── Runner.rc │ ├── flutter_window.cpp │ ├── flutter_window.h │ ├── main.cpp │ ├── resource.h │ ├── resources │ └── app_icon.ico │ ├── runner.exe.manifest │ ├── utils.cpp │ ├── utils.h │ ├── win32_window.cpp │ └── win32_window.h ├── lib ├── shorebird_code_push.dart └── src │ ├── generated │ └── updater_bindings.g.dart │ ├── shorebird_updater.dart │ ├── shorebird_updater_io.dart │ ├── shorebird_updater_web.dart │ └── updater.dart ├── pubspec.yaml └── test ├── override_print.dart └── src ├── shorebird_updater_io_test.dart ├── shorebird_updater_test.dart ├── shorebird_updater_web_test.dart └── updater_test.dart /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 8 | 9 | ## Description 10 | 11 | 12 | 13 | ## Type of Change 14 | 15 | 16 | 17 | - [ ] ✨ New feature (non-breaking change which adds functionality) 18 | - [ ] 🛠️ Bug fix (non-breaking change which fixes an issue) 19 | - [ ] ❌ Breaking change (fix or feature that would cause existing functionality to change) 20 | - [ ] 🧹 Code refactor 21 | - [ ] ✅ Build configuration change 22 | - [ ] 📝 Documentation 23 | - [ ] 🗑️ Chore 24 | -------------------------------------------------------------------------------- /.github/actions/dart_package/action.yaml: -------------------------------------------------------------------------------- 1 | name: Dart Package Workflow 2 | description: Build and test your Dart packages. 3 | 4 | inputs: 5 | codecov_token: 6 | required: true 7 | description: The Codecov token used to upload coverage 8 | concurrency: 9 | required: false 10 | default: "4" 11 | description: The value of the concurrency flag (-j) used when running tests 12 | coverage_excludes: 13 | required: false 14 | default: "" 15 | description: Globs to exclude from coverage 16 | dart_sdk: 17 | required: false 18 | default: "stable" 19 | description: "The dart sdk version to use" 20 | working_directory: 21 | required: false 22 | default: "." 23 | description: The working directory for this workflow 24 | min_coverage: 25 | required: false 26 | default: "100" 27 | description: The minimum coverage percentage value 28 | analyze_directories: 29 | required: false 30 | default: "lib test" 31 | description: Directories to analyze 32 | report_on: 33 | required: false 34 | default: "lib" 35 | description: Directories to report on when collecting coverage 36 | platform: 37 | required: false 38 | default: "vm" 39 | description: Platform to use when running tests 40 | 41 | runs: 42 | using: "composite" 43 | steps: 44 | - uses: dart-lang/setup-dart@v1 45 | with: 46 | sdk: ${{inputs.dart_sdk}} 47 | 48 | - name: Install Dependencies 49 | working-directory: ${{ inputs.working_directory }} 50 | shell: ${{ inputs.shell }} 51 | run: dart pub get 52 | 53 | - name: Format 54 | working-directory: ${{ inputs.working_directory }} 55 | shell: ${{ inputs.shell }} 56 | run: dart format --set-exit-if-changed . 57 | 58 | - name: Analyze 59 | working-directory: ${{ inputs.working_directory }} 60 | shell: ${{ inputs.shell }} 61 | run: dart analyze --fatal-warnings ${{inputs.analyze_directories}} 62 | 63 | - name: Test 64 | working-directory: ${{ inputs.working_directory }} 65 | shell: ${{ inputs.shell }} 66 | run: | 67 | dart pub global activate coverage 68 | dart test -j ${{inputs.concurrency}} --coverage=coverage --platform=${{inputs.platform}} && dart pub global run coverage:format_coverage --lcov --in=coverage --out=coverage/lcov.info --packages=.dart_tool/package_config.json --report-on=${{inputs.report_on}} --check-ignore 69 | 70 | # Per https://stackoverflow.com/questions/73402042/github-action-expressions-split-string 71 | - name: Split package name 72 | env: 73 | PACKAGE_PATH: ${{ inputs.working_directory}} 74 | id: split 75 | shell: ${{ inputs.shell }} 76 | run: echo "package_name=${PACKAGE_PATH##*/}" >> $GITHUB_OUTPUT 77 | 78 | - name: Upload Coverage 79 | uses: codecov/codecov-action@v3 80 | with: 81 | flags: ${{ steps.split.outputs.package_name }} 82 | token: ${{ inputs.codecov_token }} 83 | -------------------------------------------------------------------------------- /.github/actions/flutter_package/action.yaml: -------------------------------------------------------------------------------- 1 | name: Flutter Package Workflow 2 | description: Build and test your Flutter packages. 3 | 4 | inputs: 5 | codecov_token: 6 | required: true 7 | description: The Codecov token used to upload coverage 8 | concurrency: 9 | required: false 10 | default: "4" 11 | description: The value of the concurrency flag (-j) used when running tests 12 | coverage_excludes: 13 | required: false 14 | default: "" 15 | description: Globs to exclude from coverage 16 | working_directory: 17 | required: false 18 | default: "." 19 | description: The working directory for this workflow 20 | min_coverage: 21 | required: false 22 | default: "100" 23 | description: The minimum coverage percentage value 24 | analyze_directories: 25 | required: false 26 | default: "lib test" 27 | description: Directories to analyze 28 | report_on: 29 | required: false 30 | default: "lib" 31 | description: Directories to report on when collecting coverage 32 | example_platform: 33 | required: false 34 | default: "web" 35 | description: Platform to use when building example 36 | 37 | runs: 38 | using: "composite" 39 | steps: 40 | - uses: subosito/flutter-action@v2 41 | 42 | - name: Install Dependencies 43 | working-directory: ${{ inputs.working_directory }} 44 | shell: ${{ inputs.shell }} 45 | run: flutter pub get 46 | 47 | - name: Format 48 | working-directory: ${{ inputs.working_directory }} 49 | shell: ${{ inputs.shell }} 50 | run: dart format --set-exit-if-changed . 51 | 52 | - name: Analyze 53 | working-directory: ${{ inputs.working_directory }} 54 | shell: ${{ inputs.shell }} 55 | run: dart analyze --fatal-warnings ${{inputs.analyze_directories}} 56 | 57 | - name: Build Example 58 | working-directory: ${{ inputs.working_directory }}/example 59 | shell: ${{ inputs.shell }} 60 | run: flutter build ${{ inputs.example_platform }} 61 | 62 | - name: Test 63 | working-directory: ${{ inputs.working_directory }} 64 | shell: ${{ inputs.shell }} 65 | run: flutter test --coverage 66 | 67 | - name: Exclude Generated Code from Coverage 68 | if: ${{ inputs.coverage_excludes != '' }} 69 | working-directory: ${{ inputs.working_directory }} 70 | shell: ${{ inputs.shell }} 71 | run: | 72 | mv coverage/lcov.info coverage/lcov.info.bak 73 | sudo apt-get -y install lcov 74 | lcov --remove coverage/lcov.info.bak "${{inputs.coverage_excludes}}" -o coverage/lcov.info 75 | 76 | # Per https://stackoverflow.com/questions/73402042/github-action-expressions-split-string 77 | - name: Split package name 78 | env: 79 | PACKAGE_PATH: ${{ inputs.working_directory}} 80 | id: split 81 | shell: ${{ inputs.shell }} 82 | run: echo "package_name=${PACKAGE_PATH##*/}" >> $GITHUB_OUTPUT 83 | 84 | - name: Upload Coverage 85 | uses: codecov/codecov-action@v3 86 | with: 87 | # We use Codecov's carryforward flags to allow our PR testing to only 88 | # run affected packages, but also allow our coverage information from 89 | # past runs to "carry forward" on a "per flag" basis. We tag coverage 90 | # information with "flags" explaining what directory the coverage is from. 91 | # https://docs.codecov.com/docs/carryforward-flags 92 | flags: ${{ steps.split.outputs.package_name }} 93 | token: ${{ inputs.codecov_token }} 94 | 95 | - uses: VeryGoodOpenSource/very_good_coverage@v2 96 | with: 97 | path: ${{inputs.working_directory}}/coverage/lcov.info 98 | exclude: ${{inputs.coverage_excludes}} 99 | min_coverage: ${{inputs.min_coverage}} 100 | -------------------------------------------------------------------------------- /.github/actions/publish_flutter_package/action.yaml: -------------------------------------------------------------------------------- 1 | name: Publish Flutter package to pub.dev 2 | description: Publish your Flutter package to pub.dev 3 | 4 | inputs: 5 | working-directory: 6 | description: directory with-in the repository where the package is located (if not in the repository root) 7 | required: false 8 | 9 | runs: 10 | using: "composite" 11 | steps: 12 | - name: 📚 Git Checkout 13 | uses: actions/checkout@v4 14 | 15 | - name: 🐦 Setup Flutter 16 | uses: subosito/flutter-action@v2 17 | 18 | - name: 🪪 Get Id Token 19 | uses: actions/github-script@v6 20 | with: 21 | script: | 22 | const pub_token = await core.getIDToken('https://pub.dev') 23 | core.exportVariable('PUB_TOKEN', pub_token) 24 | 25 | - name: 📢 Authenticate 26 | shell: ${{ inputs.shell }} 27 | run: flutter pub pub token add https://pub.dev --env-var PUB_TOKEN 28 | 29 | - name: 📦 Install dependencies 30 | shell: ${{ inputs.shell }} 31 | run: flutter pub get 32 | working-directory: ${{ inputs.working-directory }} 33 | 34 | - name: 🌵 Dry Run 35 | shell: ${{ inputs.shell }} 36 | run: flutter pub publish --dry-run 37 | working-directory: ${{ inputs.working-directory }} 38 | 39 | - name: 📢 Publish 40 | shell: ${{ inputs.shell }} 41 | run: flutter pub publish -f 42 | working-directory: ${{ inputs.working-directory }} 43 | -------------------------------------------------------------------------------- /.github/actions/rust_crate/action.yaml: -------------------------------------------------------------------------------- 1 | name: Rust Crate Workflow 2 | description: Build and test your Rust crate 3 | 4 | inputs: 5 | codecov_token: 6 | required: true 7 | description: The codecov token used to upload coverage reports. 8 | working_directory: 9 | required: false 10 | default: "." 11 | description: The working directory for this workflow. 12 | 13 | runs: 14 | using: "composite" 15 | steps: 16 | - name: Build 17 | working-directory: ${{ inputs.working_directory }} 18 | shell: ${{ inputs.shell }} 19 | run: cargo build --verbose 20 | 21 | - name: Test 22 | working-directory: ${{ inputs.working_directory }} 23 | shell: ${{ inputs.shell }} 24 | run: | 25 | cargo install cargo-llvm-cov 26 | cargo llvm-cov --lcov --output-path lcov.info 27 | 28 | # Per https://stackoverflow.com/questions/73402042/github-action-expressions-split-string 29 | - name: Split package name 30 | env: 31 | PACKAGE_PATH: ${{ inputs.working_directory}} 32 | id: split 33 | shell: ${{ inputs.shell }} 34 | run: echo "package_name=${PACKAGE_PATH##*/}" >> $GITHUB_OUTPUT 35 | 36 | - name: Upload Coverage 37 | uses: codecov/codecov-action@v3 38 | with: 39 | # We use Codecov's carryforward flags to allow our PR testing to only 40 | # run affected packages, but also allow our coverage information from 41 | # past runs to "carry forward" on a "per flag" basis. We tag coverage 42 | # information with "flags" explaining what directory the coverage is from. 43 | # https://docs.codecov.com/docs/carryforward-flags 44 | flags: ${{ steps.split.outputs.package_name }} 45 | token: ${{ inputs.codecov_token }} 46 | -------------------------------------------------------------------------------- /.github/codecov.yml: -------------------------------------------------------------------------------- 1 | # Not sure if .yaml suffix would work, currently using .yml to match examples: 2 | # https://docs.codecov.com/docs/codecov-yaml 3 | # Validate with: 4 | # curl -X POST --data-binary @codecov.yml https://codecov.io/validate 5 | # See https://docs.codecov.com/docs/flags 6 | flag_management: 7 | default_rules: # the rules that will be followed for any flag added, generally 8 | # We use Codecov's carryforward flags to allow our PR testing to only 9 | # run affected packages, but also allow our coverage information from 10 | # past runs to "carry forward" on a "per flag" basis. We tag coverage 11 | # information with "flags" explaining what directory the coverage is from. 12 | # https://docs.codecov.com/docs/carryforward-flags 13 | carryforward: true 14 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # To get started with Dependabot version updates, you'll need to specify which 2 | # package ecosystems to update and where the package manifests are located. 3 | # Please see the documentation for all configuration options: 4 | # https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file 5 | 6 | version: 2 7 | updates: 8 | - package-ecosystem: "pub" 9 | directory: "/shorebird_code_push" 10 | schedule: 11 | interval: "weekly" 12 | groups: 13 | shorebird_code_push-deps: 14 | patterns: 15 | - "*" 16 | - package-ecosystem: "cargo" 17 | directory: "/library" 18 | schedule: 19 | interval: "weekly" 20 | groups: 21 | library-deps: 22 | patterns: 23 | - "*" 24 | - package-ecosystem: "cargo" 25 | directory: "/patch" 26 | schedule: 27 | interval: "weekly" 28 | groups: 29 | patch-deps: 30 | patterns: 31 | - "*" 32 | - package-ecosystem: "github-actions" 33 | directory: "/" 34 | schedule: 35 | interval: "weekly" 36 | groups: 37 | gh-deps: 38 | patterns: 39 | - "*" 40 | -------------------------------------------------------------------------------- /.github/workflows/build_patch_artifacts.yaml: -------------------------------------------------------------------------------- 1 | name: build_patch_artifacts 2 | 3 | permissions: 4 | contents: write 5 | 6 | on: 7 | release: 8 | types: [created] 9 | 10 | jobs: 11 | upload-artifacts: 12 | strategy: 13 | matrix: 14 | include: 15 | - target: x86_64-unknown-linux-musl 16 | os: ubuntu-latest 17 | - os: macos-latest 18 | - os: windows-latest 19 | 20 | runs-on: ${{ matrix.os }} 21 | 22 | name: 🦀 Upload Artifacts 23 | 24 | steps: 25 | - uses: actions/checkout@v4 26 | - uses: taiki-e/upload-rust-binary-action@v1 27 | with: 28 | bin: "patch" 29 | target: ${{ matrix.target }} 30 | tar: none 31 | zip: all 32 | token: ${{ secrets.GITHUB_TOKEN }} 33 | -------------------------------------------------------------------------------- /.github/workflows/main.yaml: -------------------------------------------------------------------------------- 1 | # cspell:words dorny codecov 2 | name: ci 3 | 4 | on: 5 | pull_request: 6 | push: 7 | branches: 8 | - main 9 | 10 | jobs: 11 | semantic_pull_request: 12 | name: ✅ Semantic Pull Request 13 | uses: VeryGoodOpenSource/very_good_workflows/.github/workflows/semantic_pull_request.yml@v1 14 | 15 | cspell: 16 | name: 🔤 Check Spelling 17 | uses: VeryGoodOpenSource/very_good_workflows/.github/workflows/spell_check.yml@v1 18 | with: 19 | config: cspell.config.yaml 20 | 21 | changes: 22 | runs-on: ubuntu-latest 23 | 24 | permissions: 25 | pull-requests: read 26 | 27 | outputs: 28 | needs_flutter_build: ${{ steps.needs_flutter_build.outputs.changes }} 29 | needs_rust_build: ${{ steps.needs_rust_build.outputs.changes }} 30 | 31 | name: 👀 Detect Changes 32 | 33 | steps: 34 | - name: 📚 Git Checkout 35 | uses: actions/checkout@v4 36 | 37 | - uses: dorny/paths-filter@v3 38 | name: Build Detection 39 | id: needs_flutter_build 40 | with: 41 | filters: | 42 | shorebird_code_push: 43 | - ./.github/workflows/main.yaml 44 | - ./.github/actions/flutter_package/action.yaml 45 | - shorebird_code_push/** 46 | 47 | - uses: dorny/paths-filter@v3 48 | name: Build Detection 49 | id: needs_rust_build 50 | with: 51 | filters: | 52 | library: 53 | - ./.github/actions/rust_crate/action.yaml 54 | - library/** 55 | patch: 56 | - ./.github/actions/rust_crate/action.yaml 57 | - patch/** 58 | 59 | build_rust_crates: 60 | needs: changes 61 | if: ${{ needs.changes.outputs.needs_rust_build != '[]' }} 62 | 63 | strategy: 64 | matrix: 65 | crate: ${{ fromJSON(needs.changes.outputs.needs_rust_build) }} 66 | os: [macos-latest, windows-latest, ubuntu-latest] 67 | 68 | runs-on: ${{ matrix.os }} 69 | 70 | name: 🦀 Build ${{ matrix.crate }} (${{ matrix.os }}) 71 | 72 | steps: 73 | - name: 📚 Git Checkout 74 | uses: actions/checkout@v4 75 | 76 | - name: 🦀 Build ${{ matrix.crate }} 77 | uses: ./.github/actions/rust_crate 78 | with: 79 | codecov_token: ${{ secrets.CODECOV_TOKEN }} 80 | working_directory: ${{ matrix.crate }} 81 | 82 | build_flutter_packages: 83 | needs: changes 84 | if: ${{ needs.changes.outputs.needs_flutter_build != '[]' }} 85 | 86 | strategy: 87 | matrix: 88 | package: ${{ fromJSON(needs.changes.outputs.needs_flutter_build) }} 89 | 90 | runs-on: ubuntu-latest 91 | 92 | name: 🎯 Build ${{ matrix.package }} 93 | 94 | steps: 95 | - name: 📚 Git Checkout 96 | uses: actions/checkout@v4 97 | with: 98 | submodules: recursive 99 | 100 | - name: 🐦 Build ${{ matrix.package }} 101 | uses: ./.github/actions/flutter_package 102 | with: 103 | codecov_token: ${{ secrets.CODECOV_TOKEN }} 104 | coverage_excludes: "**/*.g.dart" 105 | working_directory: ${{ matrix.package }} 106 | 107 | ci: 108 | needs: [semantic_pull_request, build_flutter_packages, build_rust_crates] 109 | if: ${{ always() }} 110 | 111 | runs-on: ubuntu-latest 112 | 113 | steps: 114 | - name: ⛔️ exit(1) on failure 115 | if: ${{ contains(join(needs.*.result, ','), 'failure') }} 116 | run: exit 1 117 | -------------------------------------------------------------------------------- /.github/workflows/publish.yaml: -------------------------------------------------------------------------------- 1 | name: Publish to pub.dev 2 | 3 | on: 4 | push: 5 | tags: 6 | - "shorebird_code_push-v[0-9]+.[0-9]+.[0-9]+*" # for tags like: 'v1.2.3' 7 | 8 | jobs: 9 | publish: 10 | environment: pub.dev 11 | permissions: 12 | id-token: write # Required for authentication using OIDC 13 | runs-on: ubuntu-latest 14 | steps: 15 | - name: 📚 Git Checkout 16 | uses: actions/checkout@v4 17 | with: 18 | submodules: recursive 19 | 20 | - name: 📦 Publish Package 21 | uses: ./.github/actions/publish_flutter_package 22 | with: 23 | # Specify the github actions deployment environment 24 | working-directory: shorebird_code_push 25 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Generated by Cargo 2 | # will have compiled files and executables 3 | debug/ 4 | target/ 5 | 6 | # These are backup files generated by rustfmt 7 | **/*.rs.bk 8 | 9 | # MSVC Windows builds of rustc generate these, which store debugging information 10 | *.pdb 11 | 12 | # Cache directory from updater cli. 13 | updater_cache 14 | 15 | # Test related 16 | lcov.info 17 | 18 | # Generated by Mac OS 19 | .DS_Store 20 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "cSpell.words": [ 3 | "aarch", 4 | "armeabi", 5 | "bipatch", 6 | "bootable", 7 | "canonicalize", 8 | "cbindgen", 9 | "comde", 10 | "Decompressor", 11 | "hasher", 12 | "libapp", 13 | "libc", 14 | "repr", 15 | "reqwest", 16 | "tempdir", 17 | "vmcode" 18 | ] 19 | } -------------------------------------------------------------------------------- /COPYRIGHT: -------------------------------------------------------------------------------- 1 | Licensed under either of Apache License, Version 2.0 (LICENSE-APACHE or 2 | http://www.apache.org/licenses/LICENSE-2.0) MIT license (LICENSE-MIT or 3 | http://opensource.org/licenses/MIT) at your option. 4 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | members = ["library", "patch"] 3 | resolver = "2" 4 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Updater library 2 | 3 | [![codecov](https://codecov.io/gh/shorebirdtech/updater/branch/main/graph/badge.svg)](https://codecov.io/gh/shorebirdtech/updater) 4 | [![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](./LICENSE-MIT) 5 | [![License: Apache](https://img.shields.io/badge/license-Apache-orange.svg)](./LICENSE-APACHE) 6 | 7 | This is the Rust side of the Shorebird code push system. This is built 8 | in Rust with a C API for easy calling from other languages, most notably 9 | for linking into `libflutter.so`. 10 | 11 | The primary modification Shorebird makes to the stock Flutter engine 12 | is adding support for the updater library (this repo). The updater library is 13 | written in Rust and is used to update the code running in the Flutter 14 | app. The updater library is built as a static library and is linked 15 | into the Flutter engine during build time. 16 | 17 | ## Parts 18 | 19 | - `library`: Runtime library linked into Flutter Engine to unpack and apply 20 | updates. Build into libupdater.a and linked into libflutter.so. 21 | - `patch`: Developer tooling to package Shorebird updates ("patches"). Built 22 | into `patch.exe` and downloaded and run by `shorebird` command line tool: 23 | https://github.com/shorebirdtech/shorebird/tree/main/packages/shorebird_cli. 24 | - `shorebird_code_push`: The Dart bindings for communicating with the updater 25 | library from within a Flutter app. Published to pub.dev, usage is optional by 26 | developers. 27 | 28 | Most interesting code is in the `library` directory. There is also 29 | a [README.md](library/README.md) in that directory explaining the design. 30 | 31 | ## Developing 32 | 33 | It's best to edit this repository from within an engine checkout. See 34 | [BUILDING_ENGINE.md](BUILDING_ENGINE.md) for instructions on how to set up an 35 | engine checkout. 36 | 37 | The workflow I use involves 2 to 3 VSC windows: 38 | 39 | 1. Opening the engine `src`. 40 | 41 | In that terminal I: 42 | 43 | ``` 44 | cd third_party/updater 45 | ``` 46 | 47 | 2. To build the updater as part of the engine: 48 | 49 | ``` 50 | cargo ndk --target aarch64-linux-android build --release && \ 51 | ninja -C ../../out/android_release_arm64 && say "done" 52 | ``` 53 | 54 | The cargo part _should not_ be needed, but I haven't yet done the work to 55 | integrate the Rust code into the gn files for the Flutter engine yet. 56 | 57 | I add `say "done"` to the end as linking can take several minutes for release 58 | Android builds. 59 | 60 | 3. In a second window, I open `code third_party/updater`. I do this because 61 | otherwise the `rust_analyzer` can't seem to find the rust code. We could 62 | fix this by adding the directory to the VSC workspace, but I'm not sure 63 | where we would put the workspace file in the first place. `src` is actually 64 | `shorebirdtech/buildroot` and is controlled via `gclient` by 65 | `shorebirdtech/engine/DEPS`. 66 | 67 | 4. In a third window I open my test app. e.g.: 68 | 69 | ``` 70 | flutter create test_app 71 | cd test_app 72 | shorebird init 73 | shorebird release 74 | code . 75 | ``` 76 | 77 | 5. To run the test app with my local engine I use: 78 | 79 | ``` 80 | shorebird run --local-engine-src-path $HOME/Documents/GitHub/engine/src \ 81 | --local-engine android_release_arm64 82 | ``` 83 | 84 | You may also need to build `out/host_release` once as `flutter build` looks for 85 | some Dart `.dill` files in `host_release`. 86 | 87 | ## Coverage 88 | 89 | We'd like to get to 100% coverage but aren't there yet. 90 | 91 | https://github.com/taiki-e/cargo-llvm-cov 92 | is the best tool I've found for generating coverage reports. 93 | 94 | Install: 95 | https://github.com/taiki-e/cargo-llvm-cov#installation 96 | 97 | `cargo llvm-cov` will then generate the report. 98 | -------------------------------------------------------------------------------- /cspell.config.yaml: -------------------------------------------------------------------------------- 1 | $schema: https://raw.githubusercontent.com/streetsidesoftware/cspell/main/cspell.schema.json 2 | version: "0.2" 3 | ignorePaths: 4 | - target 5 | - build 6 | - "**/*.g.dart" 7 | # Flutter platform directories with per-platform build files 8 | - windows 9 | - macos 10 | - linux 11 | - ios 12 | - android 13 | words: 14 | - aarch 15 | - androideabi 16 | - apks 17 | - armeabi 18 | - armv7 19 | - autogen 20 | - bidiff 21 | - bipatch 22 | - bootable 23 | - buildroot 24 | - bundletool 25 | - carryforward 26 | - cbindgen 27 | - cdylib 28 | - classpath 29 | - comde 30 | - compatch 31 | - Condvar 32 | - datadir 33 | - declspec 34 | - Decompressor 35 | - dllexport 36 | - dlopen 37 | - endtemplate 38 | - eseidel 39 | - ffigen 40 | - gclient 41 | - hdpi 42 | - ifdef 43 | - libapp 44 | - libc 45 | - libflutter 46 | - libupdater 47 | - logcat 48 | - mockall 49 | - mocktail 50 | - msvc 51 | - oslog 52 | - pubspec 53 | - repr 54 | - reqwest 55 | - rollouts 56 | - rustflags 57 | - rustls 58 | - rustup 59 | - serde 60 | - shorebirdtech 61 | - sigstore 62 | - staticlib 63 | - subosito 64 | - unbootable 65 | - Verdana 66 | - vmcode 67 | - withf 68 | - xlink 69 | - zstandard 70 | - zstd 71 | - xcodeproj 72 | - pbxproj 73 | -------------------------------------------------------------------------------- /library/.cargo/config.toml: -------------------------------------------------------------------------------- 1 | # The Flutter engine builds with static crt runtime so we need to too to match. 2 | [target.'cfg(all(windows, target_env = "msvc"))'] 3 | rustflags = ["-C", "target-feature=+crt-static"] 4 | -------------------------------------------------------------------------------- /library/.gitignore: -------------------------------------------------------------------------------- 1 | # Generated by Cargo 2 | # will have compiled files and executables 3 | debug/ 4 | target/ 5 | 6 | # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries 7 | # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html 8 | Cargo.lock 9 | 10 | # These are backup files generated by rustfmt 11 | **/*.rs.bk 12 | 13 | # MSVC Windows builds of rustc generate these, which store debugging information 14 | *.pdb -------------------------------------------------------------------------------- /library/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "updater" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [lib] 9 | # "lib" is used by the "cli" target for testing from Rust 10 | # "cdylib" is used by the "dart_cli" target for testing from Dart 11 | # "staticlib" is used by the engine build for linking into libflutter.so 12 | crate-type = ["lib", "cdylib", "staticlib"] 13 | 14 | [dependencies] 15 | # Used for error handling for now. 16 | anyhow = { version = "1.0.69", features = ["backtrace"] } 17 | base64 = "0.22.0" 18 | # For inflating compressed patch files. 19 | bipatch = "1.0.0" 20 | # Used for exposing C API 21 | # comde is a wrapper around several compression libraries. 22 | # We only use zstd and could depend on it directly instead. 23 | comde = { version = "0.2.3", default-features = false, features = [ 24 | "zstandard", 25 | ] } 26 | dyn-clone = "1.0.16" 27 | # For decoding the hex-encoded hashes in Patch network responses. 28 | hex = "0.4.3" 29 | # Used to construct mock responses. 30 | http = "1.2.0" 31 | libc = "0.2.98" 32 | # For error!(), info!(), etc macros. `print` will not show up on Android. 33 | log = "0.4.14" 34 | # For implementing thread-local-storage of ResolvedConfig object. 35 | once_cell = "1.17.1" 36 | # Json serialization/de-serialization. 37 | # Pipe is a simple in-memory pipe implementation, there might be a std way too? 38 | pipe = "0.4.0" 39 | # Used for networking. 40 | reqwest = { version = "0.12", default-features = false, features = [ 41 | "blocking", 42 | "json", 43 | "rustls-tls", 44 | ] } 45 | ring = "0.17.8" 46 | serde = { version = "1.0", features = ["derive"] } 47 | serde_json = "1.0.93" 48 | # For reading shorebird.yaml 49 | serde_yaml = "0.9.19" 50 | # For computing hashes of patch files for validation. 51 | sha2 = "0.10.6" 52 | # For decompressing .apk files. 53 | zip = { version = "0.6.4", default-features = false, features = ["deflate"] } 54 | 55 | [target.'cfg(target_os = "android")'.dependencies] 56 | # For logging to Android logcat. 57 | android_logger = "0.13.0" 58 | # Send panics to log (instead of stderr), thus logcat on Android. 59 | log-panics = { version = "2", features = ["with-backtrace"] } 60 | 61 | [target.'cfg(any(target_os = "ios", target_os = "macos"))'.dependencies] 62 | # Send panics to syslog (instead of stderr). 63 | log-panics = { version = "2", features = ["with-backtrace"] } 64 | # Support for logging on iOS (https://developer.apple.com/documentation/oslog) 65 | oslog = "0.2.0" 66 | 67 | [target.'cfg(any(target_os = "linux", target_os = "windows"))'.dependencies] 68 | simple_logger = "5.0.0" 69 | 70 | [dev-dependencies] 71 | mockall = "0.12.1" 72 | mockito = "1.2.0" 73 | mock_instant = "0.5.1" 74 | # Gives #[serial] attribute for locking all of our shorebird_init 75 | # tests to a single thread so they don't conflict with each other. 76 | serial_test = "2.0.0" 77 | tempdir = "0.3.7" 78 | 79 | # 80 | [build-dependencies] 81 | cbindgen = "0.24.0" 82 | -------------------------------------------------------------------------------- /library/build.rs: -------------------------------------------------------------------------------- 1 | extern crate cbindgen; 2 | 3 | use std::env; 4 | 5 | // See: 6 | // 7 | // 8 | // 9 | fn main() { 10 | let crate_dir = env::var("CARGO_MANIFEST_DIR").unwrap(); 11 | 12 | // Should this write to the out dir (target) instead? 13 | let result = cbindgen::generate(crate_dir); 14 | match result { 15 | Ok(contents) => { 16 | contents.write_to_file("include/updater.h"); 17 | } 18 | Err(e) => { 19 | println!("cargo:warning=Error generating bindings: {e}"); 20 | // If we were to exit 1 here we would stop local rust 21 | // analysis from working. So we just print the error 22 | // and continue. 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /library/cbindgen.toml: -------------------------------------------------------------------------------- 1 | # See https://github.com/eqrion/cbindgen/blob/master/docs.md#cbindgentoml 2 | # for detailed documentation of every option here. 3 | language = "C" 4 | include_guard = "updater_h" 5 | autogen_warning = "/* Warning, this file is autogenerated by cbindgen. Don't modify this manually. */" 6 | cpp_compat = true 7 | line_length = 80 8 | 9 | # I don't know if these are required to export the shorebird_ symbols 10 | # since I've hit multiple levels of export trouble in libflutter.so. 11 | # But I'm leaving them here for now. 12 | after_includes = """ 13 | #ifdef _WIN32 14 | #define SHOREBIRD_EXPORT __declspec(dllexport) 15 | #else 16 | #define SHOREBIRD_EXPORT __attribute__((visibility("default"))) 17 | #endif 18 | """ 19 | [fn] 20 | prefix = "SHOREBIRD_EXPORT" -------------------------------------------------------------------------------- /library/src/cache/disk_io.rs: -------------------------------------------------------------------------------- 1 | use anyhow::{bail, Context}; 2 | use serde::{de::DeserializeOwned, Serialize}; 3 | use std::{ 4 | fs::File, 5 | io::{BufReader, BufWriter}, 6 | path::Path, 7 | }; 8 | 9 | pub fn write(serializable: &S, path: &P) -> anyhow::Result<()> 10 | where 11 | S: ?Sized + Serialize, 12 | P: AsRef, 13 | { 14 | shorebird_debug!("Writing to {:?}", path.as_ref()); 15 | 16 | let path_as_ref = path.as_ref(); 17 | let containing_dir = path_as_ref 18 | .parent() 19 | .with_context(|| format!("Failed to get parent dir for {:?}", path_as_ref))?; 20 | 21 | // Because File::create can sometimes fail if the full directory path doesn't exist, 22 | // we create the directories in its path first. 23 | std::fs::create_dir_all(containing_dir) 24 | .with_context(|| format!("Failed to create dir {:?}", path_as_ref))?; 25 | 26 | let file = File::create(path).with_context(|| format!("File::create for {:?}", path_as_ref))?; 27 | let writer = BufWriter::new(file); 28 | serde_json::to_writer_pretty(writer, serializable) 29 | .with_context(|| format!("failed to serialize to {:?}", path_as_ref)) 30 | } 31 | 32 | pub fn read(path: &P) -> anyhow::Result 33 | where 34 | D: DeserializeOwned, 35 | P: AsRef, 36 | { 37 | shorebird_debug!("Reading from {:?}", path.as_ref()); 38 | 39 | let path_as_ref = path.as_ref(); 40 | if !path_as_ref.exists() { 41 | bail!("File {} does not exist", path_as_ref.display()); 42 | } 43 | 44 | let file = File::open(path_as_ref)?; 45 | let reader = BufReader::new(file); 46 | serde_json::from_reader(reader) 47 | .with_context(|| format!("failed to deserialize from {:?}", &path_as_ref)) 48 | } 49 | 50 | #[cfg(test)] 51 | mod test { 52 | use std::path::Path; 53 | 54 | use serde::{Deserialize, Serialize}; 55 | use tempdir::TempDir; 56 | 57 | use anyhow::{Ok, Result}; 58 | 59 | #[derive(Serialize, Deserialize, PartialEq, Eq)] 60 | struct TestStruct { 61 | a: u32, 62 | b: String, 63 | } 64 | 65 | #[test] 66 | fn writes_and_reads_serialized_object() -> Result<()> { 67 | let test_struct = TestStruct { 68 | a: 1, 69 | b: "hello".to_string(), 70 | }; 71 | let temp_dir = TempDir::new("test")?; 72 | let path = temp_dir.path().join("test.json"); 73 | super::write(&test_struct, &path)?; 74 | let read_struct: TestStruct = super::read(&path)?; 75 | 76 | assert!(test_struct == read_struct); 77 | 78 | Ok(()) 79 | } 80 | 81 | #[test] 82 | fn read_errs_if_file_does_not_exist() { 83 | assert!(super::read::(&Path::new("nonexistent.json")).is_err()); 84 | } 85 | 86 | #[test] 87 | fn read_errs_if_struct_cannot_be_deserialized() -> Result<()> { 88 | let temp_dir = TempDir::new("test")?; 89 | let path = &temp_dir.path().join("test.json"); 90 | std::fs::write(path, "junk")?; 91 | 92 | assert!(super::read::(&path).is_err()); 93 | 94 | Ok(()) 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /library/src/cache/mod.rs: -------------------------------------------------------------------------------- 1 | mod disk_io; 2 | mod patch_manager; 3 | mod signing; 4 | pub mod updater_state; 5 | 6 | pub use updater_state::UpdaterState; 7 | 8 | /// The public interface for talking about patches to the Cache. 9 | #[derive(PartialEq, Eq, Debug, Clone)] 10 | pub struct PatchInfo { 11 | pub path: std::path::PathBuf, 12 | pub number: usize, 13 | } 14 | -------------------------------------------------------------------------------- /library/src/cache/signing.rs: -------------------------------------------------------------------------------- 1 | // cspell:ignore pubin PKCS outform 2 | use anyhow::{bail, Context, Result}; 3 | use base64::Engine; 4 | use std::path::Path; 5 | 6 | /// Reads the file at `path` and returns the SHA-256 hash of its contents as a String. 7 | pub fn hash_file>(path: P) -> Result { 8 | use sha2::{Digest, Sha256}; // `Digest` is needed for `Sha256::new()`; 9 | 10 | let mut file = std::fs::File::open(path)?; 11 | let mut hasher = Sha256::new(); 12 | std::io::copy(&mut file, &mut hasher)?; 13 | let hash = hasher.finalize(); 14 | Ok(hex::encode(hash)) 15 | } 16 | 17 | /// `public_key` is a DER base64-encoded RSA public key. 18 | /// 19 | /// Given a public_key.pem file, this can be generated with the following command: 20 | /// openssl rsa -pubin \ 21 | /// -in public_key.pem \ 22 | /// -inform PEM \ 23 | /// -RSAPublicKey_out \ 24 | /// -outform DER \ 25 | /// -out public_key.der 26 | /// 27 | /// See https://docs.rs/ring/latest/ring/signature/index.html#signing-and-verifying-with-rsa-pkcs1-15-padding 28 | /// for more information. 29 | pub fn check_signature(message: &str, signature: &str, public_key: &str) -> Result<()> { 30 | shorebird_debug!("Message is {}", message); 31 | shorebird_debug!("Public key is {:?}", public_key); 32 | shorebird_debug!("Signature is {}", signature); 33 | 34 | let public_key_bytes = base64::prelude::BASE64_STANDARD 35 | .decode(public_key) 36 | .with_context(|| format!("Failed to decode public_key: {}", public_key))?; 37 | let public_key = ring::signature::UnparsedPublicKey::new( 38 | &ring::signature::RSA_PKCS1_2048_8192_SHA256, 39 | public_key_bytes, 40 | ); 41 | let decoded_sig = base64::prelude::BASE64_STANDARD 42 | .decode(signature) 43 | .map_err(|e| anyhow::Error::msg(format!("Failed to decode signature: {:?}", e)))?; 44 | 45 | shorebird_info!("Verifying patch signature..."); 46 | match public_key.verify(message.as_bytes(), &decoded_sig) { 47 | Ok(_) => { 48 | shorebird_info!("Patch signature is valid"); 49 | Ok(()) 50 | } 51 | Err(_) => { 52 | // The error provided by `verify` is (by design) not helpful, so we ignore it. 53 | // See https://docs.rs/ring/latest/ring/error/struct.Unspecified.html 54 | shorebird_error!("Patch signature is invalid"); 55 | bail!("Patch signature is invalid") 56 | } 57 | } 58 | } 59 | 60 | #[cfg(test)] 61 | mod tests { 62 | // The constant values below were generated by taking an arbitrary hash (`MESSAGE`) and 63 | // using openssl to sign it with a private key. 64 | 65 | // The base64-encoded public half of the key pair used to sign `MESSAGE`. 66 | const PUBLIC_KEY: &str = "MIIBCgKCAQEA2wdpEGbuvlPsb9i0qYrfMefJnEw1BHTi8SYZTKrXOvJWmEpPE1hWfbkvYzXu5a96gV1yocF3DMwn04VmRlKhC4AhsD0NL0UNhYhotbKG91Kwi1vAXpHhCdz5gQEBw0K1uB4Jz+zK6WK+31PryYpwLwbyXNqXoY8IAAUQ4STsHYV5w+BMSi8pepWMRd7DR9RHcbNOZlJvdBQ5NxvB4JN4dRMq8cC73ez1P9d7Dfwv3TWY+he9EmuXLT2UivZSlHIrGBa7MFfqyUe2ro0F7Te/B0si12itBbWIqycvqcXjeOPNn6WEpqN7IWjb9LUh162JyYaz5Lb/VeeJX8LKtElccwIDAQAB"; 67 | 68 | // The message that was signed. 69 | const MESSAGE: &str = "404e5caa5b906f6d03c97657e8c4d604d759f9cfba1a8bba9d5b49a5ebc174f9"; 70 | 71 | // The base64-encoded signature of `MESSAGE` using the private key corresponding to `PUBLIC_KEY`. 72 | const SIGNATURE: &str = "2ixSo5LpaWUSLg2GJEV+D+uyLeLjp0c3vNXnl0yb1iJjAdpn10BFlbcwCcjaJW9PNky2HU2hKOBe62PkFHOU8DDYOfxf2LGg/ToLGPHin85WrwFAceAUYDs7JpQr43dRTbrXcT8k5tuCQOTwXecGwuWcOFFvh0GbXFnyAmi7fLfN9CtTsG2GIOle/LyYLwoviTrXn/fZTZEYrqxD/wZ4QzoWOWLWNvrPbILhqWELkBLhdZeK0+nC2CIxFRYd3bUeOi1AGtPyHKBfdwuf4VO3+HbwJVaAEiD7HU2Bj+Zp1xeSdbznmYgBV86oizrLFd23D+lBfTlmDGgdfNE9J4Z2/g=="; 73 | 74 | use std::io::Write; 75 | 76 | use anyhow::Result; 77 | use tempdir::TempDir; 78 | 79 | #[test] 80 | fn errs_if_file_does_not_exist() { 81 | let path = "/tmp/does_not_exist"; 82 | let result = super::hash_file(path); 83 | assert!(result.is_err()); 84 | } 85 | 86 | #[test] 87 | fn hashes_file_contents() -> Result<()> { 88 | // Write "hello, world!" to a file. 89 | let temp_dir = TempDir::new("signing")?; 90 | let file_path = temp_dir.path().join("test.txt"); 91 | let mut file = std::fs::File::create(&file_path)?; 92 | file.write_all("hello, world!".as_bytes())?; 93 | 94 | // Verify that the hash is correct. 95 | let hashed = super::hash_file(file_path)?; 96 | assert_eq!( 97 | &hashed, 98 | "68e656b251e67e8358bef8483ab0d51c6619f3e7a1a9f0e75838d41ff368f728" 99 | ); 100 | Ok(()) 101 | } 102 | 103 | #[test] 104 | fn errs_if_public_key_cannot_be_decoded() { 105 | let result = super::check_signature(MESSAGE, SIGNATURE, "bad_public_key"); 106 | assert!(result.is_err()); 107 | let error = result.unwrap_err().to_string(); 108 | assert_eq!(error, "Failed to decode public_key: bad_public_key"); 109 | } 110 | 111 | #[test] 112 | fn errs_if_signature_cannot_be_decoded() { 113 | let result = super::check_signature(MESSAGE, "signature", PUBLIC_KEY); 114 | assert!(result.is_err()); 115 | let error = result.unwrap_err().to_string(); 116 | assert!(error.starts_with("Failed to decode signature")); 117 | } 118 | 119 | #[test] 120 | fn errs_if_signature_is_not_valid() { 121 | // Pass PUBLIC_KEY as the signature to ensure that the signature is invalid. 122 | let result = super::check_signature(MESSAGE, PUBLIC_KEY, PUBLIC_KEY); 123 | assert!(result.is_err()); 124 | let error = result.unwrap_err().to_string(); 125 | assert!(error.starts_with("Patch signature is invalid")); 126 | } 127 | 128 | #[test] 129 | fn is_ok_if_signature_is_valid() { 130 | let result = super::check_signature(MESSAGE, SIGNATURE, PUBLIC_KEY); 131 | assert!(result.is_ok()); 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /library/src/events.rs: -------------------------------------------------------------------------------- 1 | // This file's job is to deal with the update_server and network side 2 | // of the updater library. 3 | 4 | use serde::{Deserialize, Deserializer, Serialize, Serializer}; 5 | 6 | use crate::{ 7 | config::{current_arch, current_platform, UpdateConfig}, 8 | time, 9 | }; 10 | 11 | #[derive(Debug, Clone, PartialEq)] 12 | pub enum EventType { 13 | PatchInstallSuccess, 14 | PatchInstallFailure, 15 | PatchDownload, 16 | } 17 | 18 | impl Serialize for EventType { 19 | fn serialize(&self, serializer: S) -> Result 20 | where 21 | S: Serializer, 22 | { 23 | serializer.serialize_str(match self { 24 | EventType::PatchInstallSuccess => "__patch_install__", 25 | EventType::PatchInstallFailure => "__patch_install_failure__", 26 | EventType::PatchDownload => "__patch_download__", 27 | }) 28 | } 29 | } 30 | 31 | impl<'de> Deserialize<'de> for EventType { 32 | fn deserialize(deserializer: D) -> Result 33 | where 34 | D: Deserializer<'de>, 35 | { 36 | let s = String::deserialize(deserializer)?; 37 | match s.as_str() { 38 | "__patch_install__" => Ok(EventType::PatchInstallSuccess), 39 | "__patch_install_failure__" => Ok(EventType::PatchInstallFailure), 40 | "__patch_download__" => Ok(EventType::PatchDownload), 41 | _ => Err(serde::de::Error::custom(format!("Unknown event type: {s}"))), 42 | } 43 | } 44 | } 45 | /// Any edits to this struct should be made carefully and in accordance 46 | /// with our privacy policy: 47 | /// 48 | /// An event that is sent to the server when a patch is successfully installed. 49 | #[derive(Debug, Serialize, Deserialize, Clone)] 50 | pub struct PatchEvent { 51 | /// The Shorebird app_id built into the shorebird.yaml in the app. 52 | pub app_id: String, 53 | 54 | /// The architecture we're running (e.g. "aarch64", "x86", "x86_64"). 55 | pub arch: String, 56 | 57 | /// The identifier of this event. 58 | #[serde(rename = "type")] 59 | pub identifier: EventType, 60 | 61 | /// The patch number that was installed. 62 | pub patch_number: usize, 63 | 64 | /// The platform we're running on (e.g. "android", "ios", "windows", "macos", "linux"). 65 | pub platform: String, 66 | 67 | /// The release version from AndroidManifest.xml, Info.plist in the app. 68 | pub release_version: String, 69 | 70 | /// When this event occurred as a Unix epoch timestamp in seconds. 71 | pub timestamp: u64, 72 | 73 | /// An optional message to be sent with the event. 74 | /// Care should be taken that this field *never* contain PII or sensitive information. 75 | pub message: Option, 76 | } 77 | 78 | impl PatchEvent { 79 | /// Creates a `PatchEvent` for the given `EventType` and patch number for reporting to the server. 80 | pub fn new( 81 | config: &UpdateConfig, 82 | event_type: EventType, 83 | patch_number: usize, 84 | message: Option<&str>, 85 | ) -> PatchEvent { 86 | PatchEvent { 87 | app_id: config.app_id.clone(), 88 | arch: current_arch().to_string(), 89 | identifier: event_type, 90 | patch_number, 91 | platform: current_platform().to_string(), 92 | release_version: config.release_version.clone(), 93 | timestamp: time::unix_timestamp(), 94 | message: message.map(|s| s.to_string()), 95 | } 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /library/src/lib.rs: -------------------------------------------------------------------------------- 1 | // This is a required file for rust libraries which declares what files are 2 | // part of the library and what interfaces are public from the library. 3 | 4 | #[macro_use] 5 | mod logging_macros; 6 | 7 | // Declare that the c_api.rs file exists and is a public sub-namespace. 8 | // C doesn't care about the namespaces, but Rust does. 9 | pub mod c_api; 10 | 11 | // Declare other .rs file/module exists, but make them private. 12 | mod cache; 13 | mod config; 14 | mod events; 15 | mod logging; 16 | mod network; 17 | mod time; 18 | mod updater; 19 | mod updater_lock; 20 | mod yaml; 21 | 22 | #[cfg(any(target_os = "android", test))] 23 | mod android; 24 | 25 | #[cfg(test)] 26 | mod test_utils; 27 | 28 | // Take all public items from the updater namespace and make them public. 29 | pub use self::updater::*; 30 | 31 | #[cfg(test)] 32 | extern crate tempdir; 33 | -------------------------------------------------------------------------------- /library/src/logging.rs: -------------------------------------------------------------------------------- 1 | #[cfg(target_os = "android")] 2 | pub fn init_logging() { 3 | log_panics::init(); 4 | 5 | android_logger::init_once( 6 | android_logger::Config::default() 7 | // `flutter` tool ignores non-flutter tagged logs. 8 | .with_tag("flutter") 9 | .with_max_level(log::LevelFilter::Info), 10 | ); 11 | shorebird_debug!("Logging initialized"); 12 | } 13 | 14 | #[cfg(any(target_os = "ios", target_os = "macos"))] 15 | pub fn init_logging() { 16 | let init_result = oslog::OsLogger::new("dev.shorebird") 17 | .level_filter(log::LevelFilter::Info) 18 | .init(); 19 | match init_result { 20 | Ok(_) => shorebird_debug!("Logging initialized"), 21 | Err(e) => shorebird_error!("Failed to initialize logging: {}", e), 22 | } 23 | } 24 | 25 | #[cfg(any(target_os = "linux", target_os = "windows"))] 26 | pub fn init_logging() { 27 | let _ = simple_logger::SimpleLogger::new().init(); 28 | } 29 | 30 | #[cfg(all( 31 | not(target_os = "android"), 32 | not(target_os = "ios"), 33 | not(target_os = "linux"), 34 | not(target_os = "macos"), 35 | not(target_os = "windows") 36 | ))] 37 | pub fn init_logging() { 38 | // Nothing to do on non-Android, non-iOS platforms. 39 | } 40 | -------------------------------------------------------------------------------- /library/src/logging_macros.rs: -------------------------------------------------------------------------------- 1 | // Wrappers around crate::log's logging functions that prepend "[shorebird]" to the log message. 2 | // 3 | // See https://stackoverflow.com/questions/67087597/is-it-possible-to-use-rusts-log-info-for-tests 4 | // for the rationale behind the use of the #[cfg(test)] attribute. 5 | 6 | #[cfg(test)] 7 | #[macro_export] 8 | macro_rules! shorebird_info { 9 | ($fmt:expr $(, $($arg:tt)*)?) => { 10 | println!(concat!("[shorebird] ", $fmt), $($($arg)*)?) 11 | }; 12 | } 13 | 14 | #[cfg(not(test))] 15 | #[macro_export] 16 | macro_rules! shorebird_info { 17 | // shorebird_info!("a {} event", "log") 18 | ($fmt:expr $(, $($arg:tt)*)?) => { 19 | log::info!(concat!("[shorebird] ", $fmt), $($($arg)*)?) 20 | }; 21 | } 22 | 23 | #[cfg(test)] 24 | #[macro_export] 25 | macro_rules! shorebird_debug { 26 | ($fmt:expr $(, $($arg:tt)*)?) => { 27 | println!(concat!("[shorebird] ", $fmt), $($($arg)*)?) 28 | }; 29 | } 30 | 31 | #[cfg(not(test))] 32 | #[macro_export] 33 | macro_rules! shorebird_debug { 34 | // shorebird_debug!("a {} event", "log") 35 | ($fmt:expr $(, $($arg:tt)*)?) => { 36 | log::debug!(concat!("[shorebird] ", $fmt), $($($arg)*)?) 37 | }; 38 | } 39 | 40 | #[cfg(test)] 41 | #[macro_export] 42 | macro_rules! shorebird_warn { 43 | ($fmt:expr $(, $($arg:tt)*)?) => { 44 | println!(concat!("[shorebird] ", $fmt), $($($arg)*)?) 45 | }; 46 | } 47 | 48 | #[cfg(not(test))] 49 | #[macro_export] 50 | macro_rules! shorebird_warn { 51 | // shorebird_warn!("a {} event", "log") 52 | ($fmt:expr $(, $($arg:tt)*)?) => { 53 | log::warn!(concat!("[shorebird] ", $fmt), $($($arg)*)?) 54 | }; 55 | } 56 | 57 | #[cfg(test)] 58 | #[macro_export] 59 | macro_rules! shorebird_error { 60 | ($fmt:expr $(, $($arg:tt)*)?) => { 61 | println!(concat!("[shorebird] ", $fmt), $($($arg)*)?) 62 | }; 63 | } 64 | 65 | #[cfg(not(test))] 66 | #[macro_export] 67 | macro_rules! shorebird_error { 68 | // shorebird_error!("a {} event", "log") 69 | ($fmt:expr $(, $($arg:tt)*)?) => { 70 | log::error!(concat!("[shorebird] ", $fmt), $($($arg)*)?) 71 | }; 72 | } 73 | -------------------------------------------------------------------------------- /library/src/test_utils.rs: -------------------------------------------------------------------------------- 1 | /// Helper methods for tests. 2 | use std::fs; 3 | 4 | use crate::{ 5 | cache::{PatchInfo, UpdaterState}, 6 | config::with_config, 7 | }; 8 | 9 | /// Writes a fake patch to the patches directory and sets it as the next boot patch. 10 | pub fn install_fake_patch(patch_number: usize) -> anyhow::Result<()> { 11 | with_config(|config| { 12 | let download_dir = std::path::PathBuf::from(&config.download_dir); 13 | let artifact_path = download_dir.join(patch_number.to_string()); 14 | fs::create_dir_all(&download_dir)?; 15 | fs::write(&artifact_path, "hello")?; 16 | 17 | let mut state = UpdaterState::load_or_new_on_error( 18 | &config.storage_dir, 19 | &config.release_version, 20 | config.patch_public_key.as_deref(), 21 | ); 22 | state.install_patch( 23 | &PatchInfo { 24 | path: artifact_path, 25 | number: patch_number, 26 | }, 27 | "hash", 28 | None, 29 | )?; 30 | state.save() 31 | }) 32 | } 33 | 34 | /// Creates a fake APK at `apk_path` and writes `libapp_contents` to its relative `libapp.so` path. 35 | pub fn write_fake_apk(apk_path: &str, libapp_contents: &[u8]) { 36 | use std::io::Write; 37 | let mut zip = zip::ZipWriter::new(std::fs::File::create(apk_path).unwrap()); 38 | let options = 39 | zip::write::FileOptions::default().compression_method(zip::CompressionMethod::Stored); 40 | let app_path = crate::android::get_relative_lib_path("libapp.so"); 41 | zip.start_file(app_path.to_str().unwrap(), options).unwrap(); 42 | zip.write_all(libapp_contents).unwrap(); 43 | zip.finish().unwrap(); 44 | } 45 | -------------------------------------------------------------------------------- /library/src/time.rs: -------------------------------------------------------------------------------- 1 | #[cfg(test)] 2 | use mock_instant::global::SystemTime; 3 | 4 | #[cfg(not(test))] 5 | use std::time::SystemTime; 6 | 7 | /// The number of seconds since the Unix epoch. Returns 0 if the system clock is set before the 8 | /// Unix epoch. 9 | pub(crate) fn unix_timestamp() -> u64 { 10 | match SystemTime::now().duration_since(SystemTime::UNIX_EPOCH) { 11 | Ok(n) => n.as_secs(), 12 | Err(_) => 0, 13 | } 14 | } 15 | 16 | #[cfg(test)] 17 | mod tests { 18 | use std::time::Duration; 19 | 20 | use mock_instant::global::MockClock; 21 | 22 | #[test] 23 | fn returns_duration_since_unix_epoch() { 24 | MockClock::set_system_time(Duration::from_secs(123)); 25 | assert_eq!(super::unix_timestamp(), 123); 26 | } 27 | 28 | // Ideally, we'd be able to test the case where `duration_since` returns an error, but it 29 | // seems to only happen when system time is set before the Unix epoch, which is not possible 30 | // with the current implementation of `MockClock` because `set_system_time` expects a duration, 31 | // and a duration cannot be negative. 32 | } 33 | -------------------------------------------------------------------------------- /library/src/updater_lock.rs: -------------------------------------------------------------------------------- 1 | use crate::updater::UpdateError; 2 | 3 | // This file's job is to handle the boilerplate around locking for the 4 | // updater thread. 5 | // We lock because it doesn't make a lot of sense to ask for multiple updates 6 | // at once. We have a *separate* lock from UpdateConfig because we want to 7 | // only guard against multiple updates at once, not against multiple threads 8 | // trying to read the config at once. We also want to allow multiple threads 9 | // to read the config at once while an update is running. 10 | // We could share code with config.rs which does similar for UpdateConfig. 11 | fn updater_lock() -> &'static std::sync::Mutex { 12 | use once_cell::sync::OnceCell; 13 | use std::sync::Mutex; 14 | static INSTANCE: OnceCell> = OnceCell::new(); 15 | INSTANCE.get_or_init(|| Mutex::new(UpdaterLockState::empty())) 16 | } 17 | 18 | // Note: it is not OK to ever ask for the Updater lock *while* holding the 19 | // UpdateConfig lock because the updater thread *will* block on getting the 20 | // UpdateConfig lock while holding the Updater lock. Allowing the inverse could 21 | // cause a deadlock. We could add a check for that here by doing a tryLock on 22 | // the UpdateConfig lock and erroring out if we can't get it, but that would 23 | // probably have false positives since it is OK for some other call to be 24 | // holding the UpdateConfig lock while another thread asks for the Updater lock. 25 | pub fn with_updater_thread_lock(f: F) -> anyhow::Result 26 | where 27 | F: FnOnce(&UpdaterLockState) -> anyhow::Result, 28 | { 29 | // Unlike our UpdateConfig lock, our UpdaterThread lock does not wait 30 | // if an updater thread is already running. We use try_lock instead 31 | // of lock to error out immediately. 32 | let lock = updater_lock().try_lock(); 33 | match lock { 34 | Ok(lock) => f(&lock), 35 | Err(std::sync::TryLockError::WouldBlock) => { 36 | anyhow::bail!(UpdateError::UpdateAlreadyInProgress) 37 | } 38 | // This should never happen. Poisoning only happens if a thread panics 39 | // while holding the lock, and we never allow the updater thread to 40 | // panic. 41 | Err(std::sync::TryLockError::Poisoned(e)) => { 42 | panic!("Updater lock poisoned: {e:?}") 43 | } 44 | } 45 | } 46 | 47 | #[derive(Debug)] 48 | pub struct UpdaterLockState { 49 | // This is held by the thread doing the update, not by the thread launching 50 | // the update. This is because in the case of start_update_thread, we 51 | // don't want to block on the calling thread while the update is running. 52 | } 53 | 54 | impl UpdaterLockState { 55 | pub fn empty() -> Self { 56 | Self {} 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /library/src/yaml.rs: -------------------------------------------------------------------------------- 1 | use serde::Deserialize; 2 | 3 | /// Struct for parsing shorebird.yaml. 4 | #[derive(Deserialize)] 5 | pub struct YamlConfig { 6 | /// App ID. Required. Generated by Shorebird and included 7 | /// in your app to identify which app/channel/version triple to update. 8 | pub app_id: String, 9 | /// Update channel name. Defaults to "stable" if not set. 10 | pub channel: Option, 11 | /// Update URL. Defaults to the default update URL if not set. 12 | pub base_url: Option, 13 | /// Update behavior. Defaults to true if not set. 14 | pub auto_update: Option, 15 | /// Base64-encoded public key for verifying patch hash signatures. 16 | pub patch_public_key: Option, 17 | } 18 | 19 | impl YamlConfig { 20 | /// Read in shorebird.yaml from a string. 21 | pub fn from_yaml(yaml: &str) -> Result { 22 | serde_yaml::from_str(yaml) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /patch/.cargo/config.toml: -------------------------------------------------------------------------------- 1 | # Prevents missing DLLs when running the binary on Windows 2 | # See https://github.com/shorebirdtech/shorebird/issues/1487 3 | [target.'cfg(all(windows, target_env = "msvc"))'] 4 | rustflags = ["-C", "target-feature=+crt-static"] 5 | -------------------------------------------------------------------------------- /patch/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "patch" 3 | version = "0.2.1" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | # Compression and decompression of patch files. 10 | bidiff = "1.0.0" 11 | # Pipe is a simple in-memory pipe implementation, there might be a std way too? 12 | pipe = "0.4.0" 13 | # comde is a wrapper around several compression libraries. 14 | # We only use zstd and could depend on it directly instead. 15 | comde = { version = "0.2.3", default-features = false, features = [ 16 | "zstandard", 17 | ] } 18 | 19 | # Only used by string_patch tool: 20 | # I don't know how to make them per-target dependencies. 21 | 22 | # For computing hashes of patch files for validation. 23 | # Only used by the string_patch tool for now. 24 | sha2 = "0.10.6" 25 | # For encoding hashes for Patch network responses. 26 | hex = "0.4.3" 27 | -------------------------------------------------------------------------------- /patch/README.md: -------------------------------------------------------------------------------- 1 | # patch command line tool 2 | 3 | This is the tool used by the `shorebird` command line to package up a patch 4 | file for uploading to Shorebird's servers. 5 | 6 | ## Usage 7 | 8 | patch 9 | 10 | ## Context, design and future thoughts. 11 | 12 | Originally `patch` was written on top of the `bidiff` tool, but really no longer 13 | needs to be. For expediency we used the `comde` crate to have a generic api 14 | across multiple compression algorithms. However, we've since decided to use the 15 | `zstd` crate for compression and decompression and could remove our `comde` and 16 | `bidiff` dependencies eventually. 17 | 18 | Because `bidiff` does not check what it's patching, it's possible to apply a 19 | patch to the wrong file. To avoid this we currently have a separate hash 20 | which we save in Shorebird's database and then `library` validates that the 21 | patched file matches what we expected after inflation. This is the wrong design 22 | and we intend to move to a system whereby `patch` is responsible for including 23 | its own hash in the patch file and validating during application. 24 | 25 | In that world we might end up with two hashes. One who's purpose is to validate 26 | the patch file itself and another to validate the resulting inflated file or 27 | the original file (both are equivalent). Both of these hashes could be stored 28 | inside the patch container (.vmcode) and validated by the `library` code. 29 | 30 | We should also probably rename `patch` to `packager` or similar since it should 31 | do more than just bidiff. Also some of the apply/inflate code in library might 32 | want to move into this directory to be more of the same place. 33 | 34 | We also probably eventually want to move to something like sigstore.dev rather 35 | than rolling our own packaging/signing system. 36 | 37 | ## Generating test expectations 38 | 39 | The `string_patch` target can be used to generate test expectations for testing 40 | the updater. It takes two strings as arguments and prints the necessary 41 | variables you will need in your test to stdout. 42 | 43 | ``` 44 | % cargo run --bin=string_patch "foo" "bar" 45 | Base: foo 46 | New: bar 47 | Patch: [40, 181, 47, 253, 0, 128, 113, 0, 0, 223, 177, 0, 0, 0, 16, 0, 0, 0, 3, 98, 97, 114, 0] 48 | Hash (new): fcde2b2edba56bf408601fb721fe9b5c338d10ee429ea04fae5511b68fbf8fb9 49 | ``` 50 | 51 | Which will translate into: 52 | 53 | ```rust 54 | let base = "foo"; 55 | let new = "bar"; 56 | let patch: Vec = vec![40, 181, 47, 253, 0, 128, 113, 0, 0, 223, 177, 0, 0, 0, 16, 0, 0, 0, 3, 98, 97, 114, 0]; 57 | let hash = "fcde2b2edba56bf408601fb721fe9b5c338d10ee429ea04fae5511b68fbf8fb9"; 58 | ``` 59 | -------------------------------------------------------------------------------- /patch/src/bin/string_patch.rs: -------------------------------------------------------------------------------- 1 | // This might combine with patch/main.rs. Just starting with a copy for ease. 2 | 3 | fn main() { 4 | use sha2::{Digest, Sha256}; // Digest is needed for Sha256::new(); 5 | 6 | let mut args = std::env::args(); 7 | args.next(); // skip program name 8 | let older = args.next().expect("base string"); 9 | let newer = args.next().expect("new string"); 10 | 11 | let older_contents = older.as_bytes().to_vec(); 12 | let newer_contents = newer.as_bytes().to_vec(); 13 | let mut patch = std::io::Cursor::new(Vec::new()); 14 | 15 | patch::make_patch(older_contents, newer_contents, &mut patch); 16 | 17 | let patch = patch.into_inner(); 18 | 19 | let mut hasher = Sha256::new(); 20 | hasher.update(&newer); 21 | let hash = hasher.finalize(); 22 | 23 | println!("Base: {older}"); 24 | println!("New: {newer}"); 25 | println!("Patch: {patch:?}"); 26 | println!("Hash (new): {}", hex::encode(hash)); 27 | } 28 | -------------------------------------------------------------------------------- /patch/src/lib.rs: -------------------------------------------------------------------------------- 1 | use bidiff::DiffParams; 2 | use std::io::{BufWriter, Seek, Write}; 3 | 4 | use comde::com::Compressor; 5 | use comde::zstd::ZstdCompressor; 6 | 7 | pub fn make_patch(older: Vec, newer: Vec, patch: &mut WS) 8 | where 9 | WS: Write + Seek, 10 | { 11 | let (mut patch_r, mut patch_w) = pipe::pipe(); 12 | let diff_params = DiffParams::new(1, None).unwrap(); 13 | std::thread::spawn(move || { 14 | bidiff::simple_diff_with_params(&older[..], &newer[..], &mut patch_w, &diff_params) 15 | .unwrap(); 16 | }); 17 | 18 | let compressor = ZstdCompressor::new(); 19 | 20 | let mut compatch_w = BufWriter::new(patch); 21 | compressor 22 | .compress(&mut compatch_w, &mut patch_r) 23 | .expect("compress patch"); 24 | compatch_w.flush().expect("flush patch"); 25 | } 26 | 27 | #[cfg(test)] 28 | mod tests { 29 | use super::*; 30 | use std::io::Cursor; 31 | 32 | #[test] 33 | fn test_make_patch() { 34 | let older = b"hello world".to_vec(); 35 | let newer = b"hello world!".to_vec(); 36 | let mut patch = Cursor::new(Vec::new()); 37 | make_patch(older, newer, &mut patch); 38 | let patch = patch.into_inner(); 39 | assert_eq!( 40 | patch, 41 | vec![ 42 | 40, 181, 47, 253, 0, 128, 157, 0, 0, 104, 223, 177, 0, 0, 0, 16, 0, 0, 11, 0, 1, 43 | 33, 0, 1, 0, 27, 64, 2 44 | ] 45 | ); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /patch/src/main.rs: -------------------------------------------------------------------------------- 1 | use std::fs::{self, File}; 2 | use std::time::Instant; 3 | 4 | // Originally inspired from example in: 5 | // https://github.com/divvun/bidiff/blob/main/crates/bic/src/main.rs 6 | // and then hacked down to just service our needs. 7 | 8 | // comde is just a wrapper around various compression/decompression libraries. 9 | // and we could just depend on the zstd crate directly if we end up using 10 | // zstd long term. 11 | 12 | fn main() { 13 | let mut args = std::env::args(); 14 | if args.len() < 4 { 15 | eprintln!( 16 | "Usage: {} ", 17 | std::path::Path::new(&args.next().unwrap()) 18 | .file_name() 19 | .unwrap() 20 | .to_str() 21 | .unwrap() 22 | ); 23 | eprintln!(" base: Path to the base file"); 24 | eprintln!(" new: Path to the new file"); 25 | eprintln!(" output: Path to the output patch file"); 26 | eprintln!(); 27 | eprintln!(" This is an internal tool for creating binary diffs."); 28 | std::process::exit(1); 29 | } 30 | 31 | args.next(); // skip program name 32 | let older = args.next().expect("path to base file"); 33 | let newer = args.next().expect("path to new file"); 34 | let patch = args.next().expect("path to output file"); 35 | 36 | let start = Instant::now(); 37 | 38 | let older_contents = fs::read(older).expect("read base file"); 39 | let newer_contents = fs::read(newer).expect("read new file"); 40 | let mut patch_file = File::create(patch).expect("create patch file"); 41 | patch::make_patch(older_contents, newer_contents, &mut patch_file); 42 | 43 | println!("Completed in {:?}", start.elapsed()); 44 | } 45 | -------------------------------------------------------------------------------- /shorebird_code_push/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://www.dartlang.org/guides/libraries/private-files 2 | 3 | # Files and directories created by pub 4 | .dart_tool/ 5 | .packages 6 | build/ 7 | pubspec.lock 8 | coverage/ -------------------------------------------------------------------------------- /shorebird_code_push/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # 2.0.4 2 | 3 | - feat: allow for custom UpdateTrack names in addition to stable, staging, and 4 | beta 5 | 6 | # 2.0.3 7 | 8 | - feat: override `toString` in `ReadPatchException` and `UpdateException` 9 | 10 | # 2.0.2 11 | 12 | - fix: un-break web platform 13 | - chore: minor improvements to example 14 | 15 | # 2.0.1 16 | 17 | - Update the minimum Flutter version from 3.24.4 to 3.24.5 (3.24.4 does not 18 | include the updater changes required to support the new API). 19 | 20 | # 2.0.0 21 | 22 | - **BREAKING**: more updates to the Updater API. We now support Stable, Beta, 23 | and Staging tracks for patches, meaning you have more control over who gets 24 | your patches and when. Check out the example for a demo. 25 | 26 | # 2.0.0-dev.2 27 | 28 | - fix: tighten library exports 29 | 30 | # 2.0.0-dev.1 31 | 32 | - **BREAKING**: revamp the updater API 33 | - Remove `ShorebirdCodePush` in favor of `ShorebirdUpdater` 34 | 35 | # 1.1.6 36 | 37 | - Update log messages to explain what "using no-op implementation" means. 38 | 39 | # 1.1.5 40 | 41 | - Update example to use isNewPatchReadyToInstall. 42 | 43 | # 1.1.4 44 | 45 | - Run `dart format` over generated files to appease pub static analysis. 46 | 47 | # 1.1.3 48 | 49 | - Update README to improve example. 50 | - Remove confusing log message printed when Shorebird is not available. 51 | 52 | # 1.1.2 53 | 54 | - update README 55 | 56 | # 1.1.1 57 | 58 | - break: `package:shorebird_code_push/shorebird_code_push_io.dart` and 59 | `package:shorebird_code_push/shorebird_code_push_web.dart` have moved into 60 | `src/` to discourage accidental direct import of these files. Please import 61 | `package:shorebird_code_push/shorebird_code_push.dart` instead. 62 | - Fixes repository link in pubspec.yaml 63 | 64 | # 1.1.0 65 | 66 | - feat: introduce `isShorebirdAvailable` to determine whether the Shorebird Engine is detected 67 | - fix: crashes when running Flutter application on web 68 | - docs: improvements to example app 69 | 70 | # 1.0.0 71 | 72 | - Change `downloadUpdate` to `downloadUpdateIfAvailable`, as the Updater performs 73 | this check internally anyway. 74 | 75 | # 0.1.3 76 | 77 | - Ignore some lints in generated files to make pub.dev happy 78 | 79 | # 0.1.2 80 | 81 | - Improves documentation 82 | - Improves example, adds restart button to readme 83 | 84 | # 0.1.1 85 | 86 | - Add readiness warning to README 87 | 88 | # 0.1.0 89 | 90 | - Initial release 🎉 91 | -------------------------------------------------------------------------------- /shorebird_code_push/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | We are happy to accept contributions! 4 | 5 | ## Developing 6 | 7 | ### FFI 8 | 9 | The Dart code in this library communicates with the Updater (part of Shorebird's 10 | Flutter engine) via FFI. 11 | 12 | For an Updater function to be visible to the Dart code, it must: 13 | 14 | 1. Be declared in c_api.rs as `pub extern "C"`. 15 | 1. This will add the function to the `library/include/updater.h` header 16 | file, which is generated by [cbindgen](https://github.com/mozilla/cbindgen) 17 | when the Updater is built. 18 | 1. Be included in the generated ffi bindings. These can be regenerated using 19 | `dart run ffigen`. 20 | 1. Android specific: be listed in 21 | https://github.com/shorebirdtech/engine/blob/main/shell/platform/android/android_exports.lst 22 | -------------------------------------------------------------------------------- /shorebird_code_push/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. -------------------------------------------------------------------------------- /shorebird_code_push/README.md: -------------------------------------------------------------------------------- 1 | # Shorebird Code Push 2 | 3 | [![Discord](https://dcbadge.vercel.app/api/server/shorebird)](https://discord.gg/shorebird) 4 | 5 | [![ci](https://github.com/shorebirdtech/updater/actions/workflows/main.yaml/badge.svg)](https://github.com/shorebirdtech/updater/actions/workflows/main.yaml) 6 | [![codecov](https://codecov.io/gh/shorebirdtech/updater/branch/main/graph/badge.svg)](https://codecov.io/gh/shorebirdtech/updater) 7 | [![License: MIT][license_badge]][license_link] 8 | 9 | A Dart package for communicating with the [Shorebird](https://shorebird.dev) 10 | Code Push Updater. Use this in your Shorebird app to: 11 | 12 | - ✅ Get the currently installed patch version 13 | - ✅ Check whether a new patch is available 14 | - ✅ Download new patches 15 | 16 | ## Getting Started 17 | 18 | If your Flutter app does not already use Shorebird, follow our 19 | [Getting Started Guide](https://docs.shorebird.dev/) to add code push to your 20 | app. 21 | 22 | ## Installation 23 | 24 | ```sh 25 | flutter pub add shorebird_code_push 26 | ``` 27 | 28 | ## Usage 29 | 30 | After adding the package to your `pubspec.yaml`, you can use it in your app like 31 | this: 32 | 33 | ```dart 34 | // Import the library 35 | import 'package:shorebird_code_push/shorebird_code_push.dart'; 36 | 37 | // Launch your app 38 | void main() => runApp(const MyApp()); 39 | 40 | // [Other code here] 41 | 42 | class _MyHomePageState extends State { 43 | // Create an instance of the updater class 44 | final updater = ShorebirdUpdater(); 45 | 46 | @override 47 | void initState() { 48 | super.initState(); 49 | 50 | // Get the current patch number and print it to the console. 51 | // It will be `null` if no patches are installed. 52 | updater.readCurrentPatch().then((currentPatch) { 53 | print('The current patch number is: ${currentPatch?.number}'); 54 | }); 55 | } 56 | 57 | Future _checkForUpdates() async { 58 | // Check whether a new update is available. 59 | final status = await updater.checkForUpdate(); 60 | 61 | if (status == UpdateStatus.outdated) { 62 | try { 63 | // Perform the update 64 | await updater.update(); 65 | } on UpdateException catch (error) { 66 | // Handle any errors that occur while updating. 67 | } 68 | } 69 | } 70 | 71 | @override 72 | Widget build(BuildContext context) { 73 | return Scaffold( 74 | // [Other code here] 75 | ElevatedButton( 76 | child: Text('Check for update'), 77 | onPressed: _checkForUpdates, 78 | ) 79 | // [Other code here] 80 | ); 81 | } 82 | } 83 | ``` 84 | 85 | See the example for a complete working app. 86 | 87 | ### Tracks 88 | 89 | Shorebird also supports publishing patches to different tracks, which can be 90 | used to target different segments of your user base. See 91 | https://docs.shorebird.dev/code-push/guides/percentage-based-rollouts/ for a 92 | guide on using this functionality to implement percentage-based rollouts. 93 | 94 | You must first publish a patch to a specific track (patches are published to the 95 | `stable` track by default). To publish a patch to a different track, update your 96 | patch command to use the `--track` argument: 97 | 98 | ```sh 99 | shorebird patch android --track beta 100 | ``` 101 | 102 | (We're just using Android for this example. Tracks are supported on all 103 | platforms). 104 | 105 | To check for updates on a given track, simply pass an `UpdateTrack` to 106 | `checkForUpdate` and `update`. For example, this: 107 | 108 | ```dart 109 | final status = await updater.checkForUpdate(); 110 | if (status == UpdateStatus.outdated) { 111 | await updater.update(); 112 | } 113 | ``` 114 | 115 | Becomes this: 116 | 117 | ```dart 118 | final status = await updater.checkForUpdate(track: UpdateTrack.beta); 119 | if (status == UpdateStatus.outdated) { 120 | await updater.update(track: UpdateTrack.beta); 121 | } 122 | ``` 123 | 124 | You can also use custom track names. When creating a patch, specify a track name 125 | like this: 126 | 127 | ```sh 128 | shorebird patch android --track my-custom-track 129 | ``` 130 | 131 | And: 132 | 133 | ```dart 134 | const track = UpdateTrack('my-custom-track'); 135 | final status = await updater.checkForUpdate(track: track); 136 | if (status == UpdateStatus.outdated) { 137 | await updater.update(track: track); 138 | } 139 | ``` 140 | 141 | ## Join us on Discord! 142 | 143 | We have an active [Discord server](https://discord.gg/shorebird) where you can 144 | ask questions and get help. 145 | 146 | ## Contributing 147 | 148 | See [CONTRIBUTING.md](CONTRIBUTING.md). 149 | 150 | [license_badge]: https://img.shields.io/badge/license-MIT-blue.svg 151 | [license_link]: https://opensource.org/licenses/MIT 152 | -------------------------------------------------------------------------------- /shorebird_code_push/analysis_options.yaml: -------------------------------------------------------------------------------- 1 | include: package:very_good_analysis/analysis_options.7.0.0.yaml 2 | 3 | analyzer: 4 | exclude: 5 | - lib/**.g.dart -------------------------------------------------------------------------------- /shorebird_code_push/coverage_badge.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | coverage 16 | coverage 17 | 100% 18 | 100% 19 | 20 | 21 | -------------------------------------------------------------------------------- /shorebird_code_push/example/.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.log 4 | *.pyc 5 | *.swp 6 | .DS_Store 7 | .atom/ 8 | .buildlog/ 9 | .history 10 | .svn/ 11 | migrate_working_dir/ 12 | 13 | # IntelliJ related 14 | *.iml 15 | *.ipr 16 | *.iws 17 | .idea/ 18 | 19 | # The .vscode folder contains launch configuration and tasks you configure in 20 | # VS Code which you may wish to be included in version control, so this line 21 | # is commented out by default. 22 | #.vscode/ 23 | 24 | # Flutter/Dart/Pub related 25 | **/doc/api/ 26 | **/ios/Flutter/.last_build_id 27 | .dart_tool/ 28 | .flutter-plugins 29 | .flutter-plugins-dependencies 30 | .packages 31 | .pub-cache/ 32 | .pub/ 33 | /build/ 34 | 35 | # Symbolication related 36 | app.*.symbols 37 | 38 | # Obfuscation related 39 | app.*.map.json 40 | 41 | # Android Studio will place build artifacts here 42 | /android/app/debug 43 | /android/app/profile 44 | /android/app/release 45 | -------------------------------------------------------------------------------- /shorebird_code_push/example/.metadata: -------------------------------------------------------------------------------- 1 | # This file tracks properties of this Flutter project. 2 | # Used by Flutter tool to assess capabilities and perform upgrades etc. 3 | # 4 | # This file should be version controlled and should not be manually edited. 5 | 6 | version: 7 | revision: "ea121f8859e4b13e47a8f845e4586164519588bc" 8 | channel: "stable" 9 | 10 | project_type: app 11 | 12 | # Tracks metadata for the flutter migrate command 13 | migration: 14 | platforms: 15 | - platform: root 16 | create_revision: ea121f8859e4b13e47a8f845e4586164519588bc 17 | base_revision: ea121f8859e4b13e47a8f845e4586164519588bc 18 | - platform: android 19 | create_revision: ea121f8859e4b13e47a8f845e4586164519588bc 20 | base_revision: ea121f8859e4b13e47a8f845e4586164519588bc 21 | 22 | # User provided section 23 | 24 | # List of Local paths (relative to this file) that should be 25 | # ignored by the migrate tool. 26 | # 27 | # Files that are not part of the templates will be ignored by default. 28 | unmanaged_files: 29 | - 'lib/main.dart' 30 | - 'ios/Runner.xcodeproj/project.pbxproj' 31 | -------------------------------------------------------------------------------- /shorebird_code_push/example/README.md: -------------------------------------------------------------------------------- 1 | # example 2 | 3 | A new Flutter project. 4 | 5 | ## Getting Started 6 | 7 | This project is a starting point for a Flutter application. 8 | 9 | A few resources to get you started if this is your first Flutter project: 10 | 11 | - [Lab: Write your first Flutter app](https://docs.flutter.dev/get-started/codelab) 12 | - [Cookbook: Useful Flutter samples](https://docs.flutter.dev/cookbook) 13 | 14 | For help getting started with Flutter development, view the 15 | [online documentation](https://docs.flutter.dev/), which offers tutorials, 16 | samples, guidance on mobile development, and a full API reference. 17 | -------------------------------------------------------------------------------- /shorebird_code_push/example/analysis_options.yaml: -------------------------------------------------------------------------------- 1 | include: ../analysis_options.yaml 2 | 3 | linter: 4 | rules: 5 | public_member_api_docs: false 6 | -------------------------------------------------------------------------------- /shorebird_code_push/example/android/.gitignore: -------------------------------------------------------------------------------- 1 | gradle-wrapper.jar 2 | /.gradle 3 | /captures/ 4 | /gradlew 5 | /gradlew.bat 6 | /local.properties 7 | GeneratedPluginRegistrant.java 8 | .cxx/ 9 | 10 | # Remember to never publicly share your keystore. 11 | # See https://flutter.dev/to/reference-keystore 12 | key.properties 13 | **/*.keystore 14 | **/*.jks 15 | -------------------------------------------------------------------------------- /shorebird_code_push/example/android/app/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("com.android.application") 3 | id("kotlin-android") 4 | // The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins. 5 | id("dev.flutter.flutter-gradle-plugin") 6 | } 7 | 8 | android { 9 | namespace = "dev.shorebird.shorebird_code_push_example" 10 | compileSdk = flutter.compileSdkVersion 11 | ndkVersion = flutter.ndkVersion 12 | 13 | compileOptions { 14 | sourceCompatibility = JavaVersion.VERSION_11 15 | targetCompatibility = JavaVersion.VERSION_11 16 | } 17 | 18 | kotlinOptions { 19 | jvmTarget = JavaVersion.VERSION_11.toString() 20 | } 21 | 22 | defaultConfig { 23 | // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). 24 | applicationId = "dev.shorebird.shorebird_code_push_example" 25 | // You can update the following values to match your application needs. 26 | // For more information, see: https://flutter.dev/to/review-gradle-config. 27 | minSdk = flutter.minSdkVersion 28 | targetSdk = flutter.targetSdkVersion 29 | versionCode = flutter.versionCode 30 | versionName = flutter.versionName 31 | } 32 | 33 | buildTypes { 34 | release { 35 | // TODO: Add your own signing config for the release build. 36 | // Signing with the debug keys for now, so `flutter run --release` works. 37 | signingConfig = signingConfigs.getByName("debug") 38 | } 39 | } 40 | } 41 | 42 | flutter { 43 | source = "../.." 44 | } 45 | -------------------------------------------------------------------------------- /shorebird_code_push/example/android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 16 | 17 | 18 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /shorebird_code_push/example/android/app/src/main/kotlin/dev/shorebird/shorebird_code_push_example/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package dev.shorebird.shorebird_code_push_example 2 | 3 | import io.flutter.embedding.android.FlutterActivity 4 | 5 | class MainActivity : FlutterActivity() 6 | -------------------------------------------------------------------------------- /shorebird_code_push/example/android/app/src/main/res/drawable-v21/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /shorebird_code_push/example/android/app/src/main/res/drawable/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /shorebird_code_push/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shorebirdtech/updater/5d7690cd37112ff093b6f1a4f1d7c4c5e47619c3/shorebird_code_push/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /shorebird_code_push/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shorebirdtech/updater/5d7690cd37112ff093b6f1a4f1d7c4c5e47619c3/shorebird_code_push/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /shorebird_code_push/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shorebirdtech/updater/5d7690cd37112ff093b6f1a4f1d7c4c5e47619c3/shorebird_code_push/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /shorebird_code_push/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shorebirdtech/updater/5d7690cd37112ff093b6f1a4f1d7c4c5e47619c3/shorebird_code_push/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /shorebird_code_push/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shorebirdtech/updater/5d7690cd37112ff093b6f1a4f1d7c4c5e47619c3/shorebird_code_push/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /shorebird_code_push/example/android/app/src/main/res/values-night/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 15 | 18 | 19 | -------------------------------------------------------------------------------- /shorebird_code_push/example/android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 15 | 18 | 19 | -------------------------------------------------------------------------------- /shorebird_code_push/example/android/app/src/profile/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /shorebird_code_push/example/android/build.gradle.kts: -------------------------------------------------------------------------------- 1 | allprojects { 2 | repositories { 3 | google() 4 | mavenCentral() 5 | } 6 | } 7 | 8 | val newBuildDir: Directory = rootProject.layout.buildDirectory.dir("../../build").get() 9 | rootProject.layout.buildDirectory.value(newBuildDir) 10 | 11 | subprojects { 12 | val newSubprojectBuildDir: Directory = newBuildDir.dir(project.name) 13 | project.layout.buildDirectory.value(newSubprojectBuildDir) 14 | } 15 | subprojects { 16 | project.evaluationDependsOn(":app") 17 | } 18 | 19 | tasks.register("clean") { 20 | delete(rootProject.layout.buildDirectory) 21 | } 22 | -------------------------------------------------------------------------------- /shorebird_code_push/example/android/gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx8G -XX:MaxMetaspaceSize=4G -XX:ReservedCodeCacheSize=512m -XX:+HeapDumpOnOutOfMemoryError 2 | android.useAndroidX=true 3 | android.enableJetifier=true 4 | -------------------------------------------------------------------------------- /shorebird_code_push/example/android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | zipStoreBase=GRADLE_USER_HOME 4 | zipStorePath=wrapper/dists 5 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-all.zip 6 | -------------------------------------------------------------------------------- /shorebird_code_push/example/android/settings.gradle.kts: -------------------------------------------------------------------------------- 1 | pluginManagement { 2 | val flutterSdkPath = run { 3 | val properties = java.util.Properties() 4 | file("local.properties").inputStream().use { properties.load(it) } 5 | val flutterSdkPath = properties.getProperty("flutter.sdk") 6 | require(flutterSdkPath != null) { "flutter.sdk not set in local.properties" } 7 | flutterSdkPath 8 | } 9 | 10 | includeBuild("$flutterSdkPath/packages/flutter_tools/gradle") 11 | 12 | repositories { 13 | google() 14 | mavenCentral() 15 | gradlePluginPortal() 16 | } 17 | } 18 | 19 | plugins { 20 | id("dev.flutter.flutter-plugin-loader") version "1.0.0" 21 | id("com.android.application") version "8.7.0" apply false 22 | id("org.jetbrains.kotlin.android") version "1.8.22" apply false 23 | } 24 | 25 | include(":app") 26 | -------------------------------------------------------------------------------- /shorebird_code_push/example/ios/.gitignore: -------------------------------------------------------------------------------- 1 | **/dgph 2 | *.mode1v3 3 | *.mode2v3 4 | *.moved-aside 5 | *.pbxuser 6 | *.perspectivev3 7 | **/*sync/ 8 | .sconsign.dblite 9 | .tags* 10 | **/.vagrant/ 11 | **/DerivedData/ 12 | Icon? 13 | **/Pods/ 14 | **/.symlinks/ 15 | profile 16 | xcuserdata 17 | **/.generated/ 18 | Flutter/App.framework 19 | Flutter/Flutter.framework 20 | Flutter/Flutter.podspec 21 | Flutter/Generated.xcconfig 22 | Flutter/ephemeral/ 23 | Flutter/app.flx 24 | Flutter/app.zip 25 | Flutter/flutter_assets/ 26 | Flutter/flutter_export_environment.sh 27 | ServiceDefinitions.json 28 | Runner/GeneratedPluginRegistrant.* 29 | 30 | # Exceptions to above rules. 31 | !default.mode1v3 32 | !default.mode2v3 33 | !default.pbxuser 34 | !default.perspectivev3 35 | -------------------------------------------------------------------------------- /shorebird_code_push/example/ios/Flutter/AppFrameworkInfo.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | App 9 | CFBundleIdentifier 10 | io.flutter.flutter.app 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | App 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1.0 23 | MinimumOSVersion 24 | 12.0 25 | 26 | 27 | -------------------------------------------------------------------------------- /shorebird_code_push/example/ios/Flutter/Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" 2 | #include "Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /shorebird_code_push/example/ios/Flutter/Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" 2 | #include "Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /shorebird_code_push/example/ios/Podfile: -------------------------------------------------------------------------------- 1 | # Uncomment this line to define a global platform for your project 2 | # platform :ios, '12.0' 3 | 4 | # CocoaPods analytics sends network stats synchronously affecting flutter build latency. 5 | ENV['COCOAPODS_DISABLE_STATS'] = 'true' 6 | 7 | project 'Runner', { 8 | 'Debug' => :debug, 9 | 'Profile' => :release, 10 | 'Release' => :release, 11 | } 12 | 13 | def flutter_root 14 | generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__) 15 | unless File.exist?(generated_xcode_build_settings_path) 16 | raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first" 17 | end 18 | 19 | File.foreach(generated_xcode_build_settings_path) do |line| 20 | matches = line.match(/FLUTTER_ROOT\=(.*)/) 21 | return matches[1].strip if matches 22 | end 23 | raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get" 24 | end 25 | 26 | require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) 27 | 28 | flutter_ios_podfile_setup 29 | 30 | target 'Runner' do 31 | use_frameworks! 32 | use_modular_headers! 33 | 34 | flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) 35 | target 'RunnerTests' do 36 | inherit! :search_paths 37 | end 38 | end 39 | 40 | post_install do |installer| 41 | installer.pods_project.targets.each do |target| 42 | flutter_additional_ios_build_settings(target) 43 | end 44 | end 45 | -------------------------------------------------------------------------------- /shorebird_code_push/example/ios/Podfile.lock: -------------------------------------------------------------------------------- 1 | PODS: 2 | - Flutter (1.0.0) 3 | 4 | DEPENDENCIES: 5 | - Flutter (from `Flutter`) 6 | 7 | EXTERNAL SOURCES: 8 | Flutter: 9 | :path: Flutter 10 | 11 | SPEC CHECKSUMS: 12 | Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7 13 | 14 | PODFILE CHECKSUM: 819463e6a0290f5a72f145ba7cde16e8b6ef0796 15 | 16 | COCOAPODS: 1.16.1 17 | -------------------------------------------------------------------------------- /shorebird_code_push/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /shorebird_code_push/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /shorebird_code_push/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /shorebird_code_push/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 37 | 38 | 39 | 40 | 43 | 49 | 50 | 51 | 52 | 53 | 63 | 65 | 71 | 72 | 73 | 74 | 80 | 82 | 88 | 89 | 90 | 91 | 93 | 94 | 97 | 98 | 99 | -------------------------------------------------------------------------------- /shorebird_code_push/example/ios/Runner.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /shorebird_code_push/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /shorebird_code_push/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /shorebird_code_push/example/ios/Runner/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import Flutter 3 | 4 | @main 5 | @objc class AppDelegate: FlutterAppDelegate { 6 | override func application( 7 | _ application: UIApplication, 8 | didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? 9 | ) -> Bool { 10 | GeneratedPluginRegistrant.register(with: self) 11 | return super.application(application, didFinishLaunchingWithOptions: launchOptions) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /shorebird_code_push/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "size" : "20x20", 5 | "idiom" : "iphone", 6 | "filename" : "Icon-App-20x20@2x.png", 7 | "scale" : "2x" 8 | }, 9 | { 10 | "size" : "20x20", 11 | "idiom" : "iphone", 12 | "filename" : "Icon-App-20x20@3x.png", 13 | "scale" : "3x" 14 | }, 15 | { 16 | "size" : "29x29", 17 | "idiom" : "iphone", 18 | "filename" : "Icon-App-29x29@1x.png", 19 | "scale" : "1x" 20 | }, 21 | { 22 | "size" : "29x29", 23 | "idiom" : "iphone", 24 | "filename" : "Icon-App-29x29@2x.png", 25 | "scale" : "2x" 26 | }, 27 | { 28 | "size" : "29x29", 29 | "idiom" : "iphone", 30 | "filename" : "Icon-App-29x29@3x.png", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "size" : "40x40", 35 | "idiom" : "iphone", 36 | "filename" : "Icon-App-40x40@2x.png", 37 | "scale" : "2x" 38 | }, 39 | { 40 | "size" : "40x40", 41 | "idiom" : "iphone", 42 | "filename" : "Icon-App-40x40@3x.png", 43 | "scale" : "3x" 44 | }, 45 | { 46 | "size" : "60x60", 47 | "idiom" : "iphone", 48 | "filename" : "Icon-App-60x60@2x.png", 49 | "scale" : "2x" 50 | }, 51 | { 52 | "size" : "60x60", 53 | "idiom" : "iphone", 54 | "filename" : "Icon-App-60x60@3x.png", 55 | "scale" : "3x" 56 | }, 57 | { 58 | "size" : "20x20", 59 | "idiom" : "ipad", 60 | "filename" : "Icon-App-20x20@1x.png", 61 | "scale" : "1x" 62 | }, 63 | { 64 | "size" : "20x20", 65 | "idiom" : "ipad", 66 | "filename" : "Icon-App-20x20@2x.png", 67 | "scale" : "2x" 68 | }, 69 | { 70 | "size" : "29x29", 71 | "idiom" : "ipad", 72 | "filename" : "Icon-App-29x29@1x.png", 73 | "scale" : "1x" 74 | }, 75 | { 76 | "size" : "29x29", 77 | "idiom" : "ipad", 78 | "filename" : "Icon-App-29x29@2x.png", 79 | "scale" : "2x" 80 | }, 81 | { 82 | "size" : "40x40", 83 | "idiom" : "ipad", 84 | "filename" : "Icon-App-40x40@1x.png", 85 | "scale" : "1x" 86 | }, 87 | { 88 | "size" : "40x40", 89 | "idiom" : "ipad", 90 | "filename" : "Icon-App-40x40@2x.png", 91 | "scale" : "2x" 92 | }, 93 | { 94 | "size" : "76x76", 95 | "idiom" : "ipad", 96 | "filename" : "Icon-App-76x76@1x.png", 97 | "scale" : "1x" 98 | }, 99 | { 100 | "size" : "76x76", 101 | "idiom" : "ipad", 102 | "filename" : "Icon-App-76x76@2x.png", 103 | "scale" : "2x" 104 | }, 105 | { 106 | "size" : "83.5x83.5", 107 | "idiom" : "ipad", 108 | "filename" : "Icon-App-83.5x83.5@2x.png", 109 | "scale" : "2x" 110 | }, 111 | { 112 | "size" : "1024x1024", 113 | "idiom" : "ios-marketing", 114 | "filename" : "Icon-App-1024x1024@1x.png", 115 | "scale" : "1x" 116 | } 117 | ], 118 | "info" : { 119 | "version" : 1, 120 | "author" : "xcode" 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /shorebird_code_push/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shorebirdtech/updater/5d7690cd37112ff093b6f1a4f1d7c4c5e47619c3/shorebird_code_push/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png -------------------------------------------------------------------------------- /shorebird_code_push/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shorebirdtech/updater/5d7690cd37112ff093b6f1a4f1d7c4c5e47619c3/shorebird_code_push/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png -------------------------------------------------------------------------------- /shorebird_code_push/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shorebirdtech/updater/5d7690cd37112ff093b6f1a4f1d7c4c5e47619c3/shorebird_code_push/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png -------------------------------------------------------------------------------- /shorebird_code_push/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shorebirdtech/updater/5d7690cd37112ff093b6f1a4f1d7c4c5e47619c3/shorebird_code_push/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png -------------------------------------------------------------------------------- /shorebird_code_push/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shorebirdtech/updater/5d7690cd37112ff093b6f1a4f1d7c4c5e47619c3/shorebird_code_push/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png -------------------------------------------------------------------------------- /shorebird_code_push/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shorebirdtech/updater/5d7690cd37112ff093b6f1a4f1d7c4c5e47619c3/shorebird_code_push/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png -------------------------------------------------------------------------------- /shorebird_code_push/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shorebirdtech/updater/5d7690cd37112ff093b6f1a4f1d7c4c5e47619c3/shorebird_code_push/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png -------------------------------------------------------------------------------- /shorebird_code_push/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shorebirdtech/updater/5d7690cd37112ff093b6f1a4f1d7c4c5e47619c3/shorebird_code_push/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png -------------------------------------------------------------------------------- /shorebird_code_push/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shorebirdtech/updater/5d7690cd37112ff093b6f1a4f1d7c4c5e47619c3/shorebird_code_push/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png -------------------------------------------------------------------------------- /shorebird_code_push/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shorebirdtech/updater/5d7690cd37112ff093b6f1a4f1d7c4c5e47619c3/shorebird_code_push/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png -------------------------------------------------------------------------------- /shorebird_code_push/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shorebirdtech/updater/5d7690cd37112ff093b6f1a4f1d7c4c5e47619c3/shorebird_code_push/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png -------------------------------------------------------------------------------- /shorebird_code_push/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shorebirdtech/updater/5d7690cd37112ff093b6f1a4f1d7c4c5e47619c3/shorebird_code_push/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png -------------------------------------------------------------------------------- /shorebird_code_push/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shorebirdtech/updater/5d7690cd37112ff093b6f1a4f1d7c4c5e47619c3/shorebird_code_push/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png -------------------------------------------------------------------------------- /shorebird_code_push/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shorebirdtech/updater/5d7690cd37112ff093b6f1a4f1d7c4c5e47619c3/shorebird_code_push/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png -------------------------------------------------------------------------------- /shorebird_code_push/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shorebirdtech/updater/5d7690cd37112ff093b6f1a4f1d7c4c5e47619c3/shorebird_code_push/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png -------------------------------------------------------------------------------- /shorebird_code_push/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "LaunchImage.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "LaunchImage@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "LaunchImage@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /shorebird_code_push/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shorebirdtech/updater/5d7690cd37112ff093b6f1a4f1d7c4c5e47619c3/shorebird_code_push/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png -------------------------------------------------------------------------------- /shorebird_code_push/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shorebirdtech/updater/5d7690cd37112ff093b6f1a4f1d7c4c5e47619c3/shorebird_code_push/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png -------------------------------------------------------------------------------- /shorebird_code_push/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shorebirdtech/updater/5d7690cd37112ff093b6f1a4f1d7c4c5e47619c3/shorebird_code_push/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png -------------------------------------------------------------------------------- /shorebird_code_push/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md: -------------------------------------------------------------------------------- 1 | # Launch Screen Assets 2 | 3 | You can customize the launch screen with your own desired assets by replacing the image files in this directory. 4 | 5 | You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images. -------------------------------------------------------------------------------- /shorebird_code_push/example/ios/Runner/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /shorebird_code_push/example/ios/Runner/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /shorebird_code_push/example/ios/Runner/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CADisableMinimumFrameDurationOnPhone 6 | 7 | CFBundleDevelopmentRegion 8 | $(DEVELOPMENT_LANGUAGE) 9 | CFBundleDisplayName 10 | Example 11 | CFBundleExecutable 12 | $(EXECUTABLE_NAME) 13 | CFBundleIdentifier 14 | $(PRODUCT_BUNDLE_IDENTIFIER) 15 | CFBundleInfoDictionaryVersion 16 | 6.0 17 | CFBundleName 18 | example 19 | CFBundlePackageType 20 | APPL 21 | CFBundleShortVersionString 22 | $(FLUTTER_BUILD_NAME) 23 | CFBundleSignature 24 | ???? 25 | CFBundleVersion 26 | $(FLUTTER_BUILD_NUMBER) 27 | LSRequiresIPhoneOS 28 | 29 | UIApplicationSupportsIndirectInputEvents 30 | 31 | UILaunchStoryboardName 32 | LaunchScreen 33 | UIMainStoryboardFile 34 | Main 35 | UISupportedInterfaceOrientations 36 | 37 | UIInterfaceOrientationPortrait 38 | UIInterfaceOrientationLandscapeLeft 39 | UIInterfaceOrientationLandscapeRight 40 | 41 | UISupportedInterfaceOrientations~ipad 42 | 43 | UIInterfaceOrientationPortrait 44 | UIInterfaceOrientationPortraitUpsideDown 45 | UIInterfaceOrientationLandscapeLeft 46 | UIInterfaceOrientationLandscapeRight 47 | 48 | UIViewControllerBasedStatusBarAppearance 49 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /shorebird_code_push/example/ios/Runner/Runner-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | #import "GeneratedPluginRegistrant.h" 2 | -------------------------------------------------------------------------------- /shorebird_code_push/example/ios/RunnerTests/RunnerTests.swift: -------------------------------------------------------------------------------- 1 | import Flutter 2 | import UIKit 3 | import XCTest 4 | 5 | class RunnerTests: XCTestCase { 6 | 7 | func testExample() { 8 | // If you add code to the Runner application, consider adding tests here. 9 | // See https://developer.apple.com/documentation/xctest for more information about using XCTest. 10 | } 11 | 12 | } 13 | -------------------------------------------------------------------------------- /shorebird_code_push/example/linux/.gitignore: -------------------------------------------------------------------------------- 1 | flutter/ephemeral 2 | -------------------------------------------------------------------------------- /shorebird_code_push/example/linux/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Project-level configuration. 2 | cmake_minimum_required(VERSION 3.10) 3 | project(runner LANGUAGES CXX) 4 | 5 | # The name of the executable created for the application. Change this to change 6 | # the on-disk name of your application. 7 | set(BINARY_NAME "example") 8 | # The unique GTK application identifier for this application. See: 9 | # https://wiki.gnome.org/HowDoI/ChooseApplicationID 10 | set(APPLICATION_ID "com.example.example") 11 | 12 | # Explicitly opt in to modern CMake behaviors to avoid warnings with recent 13 | # versions of CMake. 14 | cmake_policy(SET CMP0063 NEW) 15 | 16 | # Load bundled libraries from the lib/ directory relative to the binary. 17 | set(CMAKE_INSTALL_RPATH "$ORIGIN/lib") 18 | 19 | # Root filesystem for cross-building. 20 | if(FLUTTER_TARGET_PLATFORM_SYSROOT) 21 | set(CMAKE_SYSROOT ${FLUTTER_TARGET_PLATFORM_SYSROOT}) 22 | set(CMAKE_FIND_ROOT_PATH ${CMAKE_SYSROOT}) 23 | set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) 24 | set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY) 25 | set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) 26 | set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) 27 | endif() 28 | 29 | # Define build configuration options. 30 | if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) 31 | set(CMAKE_BUILD_TYPE "Debug" CACHE 32 | STRING "Flutter build mode" FORCE) 33 | set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS 34 | "Debug" "Profile" "Release") 35 | endif() 36 | 37 | # Compilation settings that should be applied to most targets. 38 | # 39 | # Be cautious about adding new options here, as plugins use this function by 40 | # default. In most cases, you should add new options to specific targets instead 41 | # of modifying this function. 42 | function(APPLY_STANDARD_SETTINGS TARGET) 43 | target_compile_features(${TARGET} PUBLIC cxx_std_14) 44 | target_compile_options(${TARGET} PRIVATE -Wall -Werror) 45 | target_compile_options(${TARGET} PRIVATE "$<$>:-O3>") 46 | target_compile_definitions(${TARGET} PRIVATE "$<$>:NDEBUG>") 47 | endfunction() 48 | 49 | # Flutter library and tool build rules. 50 | set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter") 51 | add_subdirectory(${FLUTTER_MANAGED_DIR}) 52 | 53 | # System-level dependencies. 54 | find_package(PkgConfig REQUIRED) 55 | pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0) 56 | 57 | add_definitions(-DAPPLICATION_ID="${APPLICATION_ID}") 58 | 59 | # Define the application target. To change its name, change BINARY_NAME above, 60 | # not the value here, or `flutter run` will no longer work. 61 | # 62 | # Any new source files that you add to the application should be added here. 63 | add_executable(${BINARY_NAME} 64 | "main.cc" 65 | "my_application.cc" 66 | "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc" 67 | ) 68 | 69 | # Apply the standard set of build settings. This can be removed for applications 70 | # that need different build settings. 71 | apply_standard_settings(${BINARY_NAME}) 72 | 73 | # Add dependency libraries. Add any application-specific dependencies here. 74 | target_link_libraries(${BINARY_NAME} PRIVATE flutter) 75 | target_link_libraries(${BINARY_NAME} PRIVATE PkgConfig::GTK) 76 | 77 | # Run the Flutter tool portions of the build. This must not be removed. 78 | add_dependencies(${BINARY_NAME} flutter_assemble) 79 | 80 | # Only the install-generated bundle's copy of the executable will launch 81 | # correctly, since the resources must in the right relative locations. To avoid 82 | # people trying to run the unbundled copy, put it in a subdirectory instead of 83 | # the default top-level location. 84 | set_target_properties(${BINARY_NAME} 85 | PROPERTIES 86 | RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/intermediates_do_not_run" 87 | ) 88 | 89 | 90 | # Generated plugin build rules, which manage building the plugins and adding 91 | # them to the application. 92 | include(flutter/generated_plugins.cmake) 93 | 94 | 95 | # === Installation === 96 | # By default, "installing" just makes a relocatable bundle in the build 97 | # directory. 98 | set(BUILD_BUNDLE_DIR "${PROJECT_BINARY_DIR}/bundle") 99 | if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) 100 | set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE) 101 | endif() 102 | 103 | # Start with a clean build bundle directory every time. 104 | install(CODE " 105 | file(REMOVE_RECURSE \"${BUILD_BUNDLE_DIR}/\") 106 | " COMPONENT Runtime) 107 | 108 | set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data") 109 | set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}/lib") 110 | 111 | install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}" 112 | COMPONENT Runtime) 113 | 114 | install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" 115 | COMPONENT Runtime) 116 | 117 | install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" 118 | COMPONENT Runtime) 119 | 120 | foreach(bundled_library ${PLUGIN_BUNDLED_LIBRARIES}) 121 | install(FILES "${bundled_library}" 122 | DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" 123 | COMPONENT Runtime) 124 | endforeach(bundled_library) 125 | 126 | # Fully re-copy the assets directory on each build to avoid having stale files 127 | # from a previous install. 128 | set(FLUTTER_ASSET_DIR_NAME "flutter_assets") 129 | install(CODE " 130 | file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\") 131 | " COMPONENT Runtime) 132 | install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}" 133 | DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime) 134 | 135 | # Install the AOT library on non-Debug builds only. 136 | if(NOT CMAKE_BUILD_TYPE MATCHES "Debug") 137 | install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" 138 | COMPONENT Runtime) 139 | endif() 140 | -------------------------------------------------------------------------------- /shorebird_code_push/example/linux/flutter/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # This file controls Flutter-level build steps. It should not be edited. 2 | cmake_minimum_required(VERSION 3.10) 3 | 4 | set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral") 5 | 6 | # Configuration provided via flutter tool. 7 | include(${EPHEMERAL_DIR}/generated_config.cmake) 8 | 9 | # TODO: Move the rest of this into files in ephemeral. See 10 | # https://github.com/flutter/flutter/issues/57146. 11 | 12 | # Serves the same purpose as list(TRANSFORM ... PREPEND ...), 13 | # which isn't available in 3.10. 14 | function(list_prepend LIST_NAME PREFIX) 15 | set(NEW_LIST "") 16 | foreach(element ${${LIST_NAME}}) 17 | list(APPEND NEW_LIST "${PREFIX}${element}") 18 | endforeach(element) 19 | set(${LIST_NAME} "${NEW_LIST}" PARENT_SCOPE) 20 | endfunction() 21 | 22 | # === Flutter Library === 23 | # System-level dependencies. 24 | find_package(PkgConfig REQUIRED) 25 | pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0) 26 | pkg_check_modules(GLIB REQUIRED IMPORTED_TARGET glib-2.0) 27 | pkg_check_modules(GIO REQUIRED IMPORTED_TARGET gio-2.0) 28 | 29 | set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/libflutter_linux_gtk.so") 30 | 31 | # Published to parent scope for install step. 32 | set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE) 33 | set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE) 34 | set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE) 35 | set(AOT_LIBRARY "${PROJECT_DIR}/build/lib/libapp.so" PARENT_SCOPE) 36 | 37 | list(APPEND FLUTTER_LIBRARY_HEADERS 38 | "fl_basic_message_channel.h" 39 | "fl_binary_codec.h" 40 | "fl_binary_messenger.h" 41 | "fl_dart_project.h" 42 | "fl_engine.h" 43 | "fl_json_message_codec.h" 44 | "fl_json_method_codec.h" 45 | "fl_message_codec.h" 46 | "fl_method_call.h" 47 | "fl_method_channel.h" 48 | "fl_method_codec.h" 49 | "fl_method_response.h" 50 | "fl_plugin_registrar.h" 51 | "fl_plugin_registry.h" 52 | "fl_standard_message_codec.h" 53 | "fl_standard_method_codec.h" 54 | "fl_string_codec.h" 55 | "fl_value.h" 56 | "fl_view.h" 57 | "flutter_linux.h" 58 | ) 59 | list_prepend(FLUTTER_LIBRARY_HEADERS "${EPHEMERAL_DIR}/flutter_linux/") 60 | add_library(flutter INTERFACE) 61 | target_include_directories(flutter INTERFACE 62 | "${EPHEMERAL_DIR}" 63 | ) 64 | target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}") 65 | target_link_libraries(flutter INTERFACE 66 | PkgConfig::GTK 67 | PkgConfig::GLIB 68 | PkgConfig::GIO 69 | ) 70 | add_dependencies(flutter flutter_assemble) 71 | 72 | # === Flutter tool backend === 73 | # _phony_ is a non-existent file to force this command to run every time, 74 | # since currently there's no way to get a full input/output list from the 75 | # flutter tool. 76 | add_custom_command( 77 | OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS} 78 | ${CMAKE_CURRENT_BINARY_DIR}/_phony_ 79 | COMMAND ${CMAKE_COMMAND} -E env 80 | ${FLUTTER_TOOL_ENVIRONMENT} 81 | "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.sh" 82 | ${FLUTTER_TARGET_PLATFORM} ${CMAKE_BUILD_TYPE} 83 | VERBATIM 84 | ) 85 | add_custom_target(flutter_assemble DEPENDS 86 | "${FLUTTER_LIBRARY}" 87 | ${FLUTTER_LIBRARY_HEADERS} 88 | ) 89 | -------------------------------------------------------------------------------- /shorebird_code_push/example/linux/flutter/generated_plugin_registrant.cc: -------------------------------------------------------------------------------- 1 | // 2 | // Generated file. Do not edit. 3 | // 4 | 5 | // clang-format off 6 | 7 | #include "generated_plugin_registrant.h" 8 | 9 | 10 | void fl_register_plugins(FlPluginRegistry* registry) { 11 | } 12 | -------------------------------------------------------------------------------- /shorebird_code_push/example/linux/flutter/generated_plugin_registrant.h: -------------------------------------------------------------------------------- 1 | // 2 | // Generated file. Do not edit. 3 | // 4 | 5 | // clang-format off 6 | 7 | #ifndef GENERATED_PLUGIN_REGISTRANT_ 8 | #define GENERATED_PLUGIN_REGISTRANT_ 9 | 10 | #include 11 | 12 | // Registers Flutter plugins. 13 | void fl_register_plugins(FlPluginRegistry* registry); 14 | 15 | #endif // GENERATED_PLUGIN_REGISTRANT_ 16 | -------------------------------------------------------------------------------- /shorebird_code_push/example/linux/flutter/generated_plugins.cmake: -------------------------------------------------------------------------------- 1 | # 2 | # Generated file, do not edit. 3 | # 4 | 5 | list(APPEND FLUTTER_PLUGIN_LIST 6 | ) 7 | 8 | list(APPEND FLUTTER_FFI_PLUGIN_LIST 9 | ) 10 | 11 | set(PLUGIN_BUNDLED_LIBRARIES) 12 | 13 | foreach(plugin ${FLUTTER_PLUGIN_LIST}) 14 | add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/linux plugins/${plugin}) 15 | target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin) 16 | list(APPEND PLUGIN_BUNDLED_LIBRARIES $) 17 | list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) 18 | endforeach(plugin) 19 | 20 | foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) 21 | add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/linux plugins/${ffi_plugin}) 22 | list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) 23 | endforeach(ffi_plugin) 24 | -------------------------------------------------------------------------------- /shorebird_code_push/example/linux/main.cc: -------------------------------------------------------------------------------- 1 | #include "my_application.h" 2 | 3 | int main(int argc, char** argv) { 4 | g_autoptr(MyApplication) app = my_application_new(); 5 | return g_application_run(G_APPLICATION(app), argc, argv); 6 | } 7 | -------------------------------------------------------------------------------- /shorebird_code_push/example/linux/my_application.cc: -------------------------------------------------------------------------------- 1 | #include "my_application.h" 2 | 3 | #include 4 | #ifdef GDK_WINDOWING_X11 5 | #include 6 | #endif 7 | 8 | #include "flutter/generated_plugin_registrant.h" 9 | 10 | struct _MyApplication { 11 | GtkApplication parent_instance; 12 | char** dart_entrypoint_arguments; 13 | }; 14 | 15 | G_DEFINE_TYPE(MyApplication, my_application, GTK_TYPE_APPLICATION) 16 | 17 | // Implements GApplication::activate. 18 | static void my_application_activate(GApplication* application) { 19 | MyApplication* self = MY_APPLICATION(application); 20 | GtkWindow* window = 21 | GTK_WINDOW(gtk_application_window_new(GTK_APPLICATION(application))); 22 | 23 | // Use a header bar when running in GNOME as this is the common style used 24 | // by applications and is the setup most users will be using (e.g. Ubuntu 25 | // desktop). 26 | // If running on X and not using GNOME then just use a traditional title bar 27 | // in case the window manager does more exotic layout, e.g. tiling. 28 | // If running on Wayland assume the header bar will work (may need changing 29 | // if future cases occur). 30 | gboolean use_header_bar = TRUE; 31 | #ifdef GDK_WINDOWING_X11 32 | GdkScreen* screen = gtk_window_get_screen(window); 33 | if (GDK_IS_X11_SCREEN(screen)) { 34 | const gchar* wm_name = gdk_x11_screen_get_window_manager_name(screen); 35 | if (g_strcmp0(wm_name, "GNOME Shell") != 0) { 36 | use_header_bar = FALSE; 37 | } 38 | } 39 | #endif 40 | if (use_header_bar) { 41 | GtkHeaderBar* header_bar = GTK_HEADER_BAR(gtk_header_bar_new()); 42 | gtk_widget_show(GTK_WIDGET(header_bar)); 43 | gtk_header_bar_set_title(header_bar, "example"); 44 | gtk_header_bar_set_show_close_button(header_bar, TRUE); 45 | gtk_window_set_titlebar(window, GTK_WIDGET(header_bar)); 46 | } else { 47 | gtk_window_set_title(window, "example"); 48 | } 49 | 50 | gtk_window_set_default_size(window, 1280, 720); 51 | gtk_widget_show(GTK_WIDGET(window)); 52 | 53 | g_autoptr(FlDartProject) project = fl_dart_project_new(); 54 | fl_dart_project_set_dart_entrypoint_arguments(project, self->dart_entrypoint_arguments); 55 | 56 | FlView* view = fl_view_new(project); 57 | gtk_widget_show(GTK_WIDGET(view)); 58 | gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(view)); 59 | 60 | fl_register_plugins(FL_PLUGIN_REGISTRY(view)); 61 | 62 | gtk_widget_grab_focus(GTK_WIDGET(view)); 63 | } 64 | 65 | // Implements GApplication::local_command_line. 66 | static gboolean my_application_local_command_line(GApplication* application, gchar*** arguments, int* exit_status) { 67 | MyApplication* self = MY_APPLICATION(application); 68 | // Strip out the first argument as it is the binary name. 69 | self->dart_entrypoint_arguments = g_strdupv(*arguments + 1); 70 | 71 | g_autoptr(GError) error = nullptr; 72 | if (!g_application_register(application, nullptr, &error)) { 73 | g_warning("Failed to register: %s", error->message); 74 | *exit_status = 1; 75 | return TRUE; 76 | } 77 | 78 | g_application_activate(application); 79 | *exit_status = 0; 80 | 81 | return TRUE; 82 | } 83 | 84 | // Implements GObject::dispose. 85 | static void my_application_dispose(GObject* object) { 86 | MyApplication* self = MY_APPLICATION(object); 87 | g_clear_pointer(&self->dart_entrypoint_arguments, g_strfreev); 88 | G_OBJECT_CLASS(my_application_parent_class)->dispose(object); 89 | } 90 | 91 | static void my_application_class_init(MyApplicationClass* klass) { 92 | G_APPLICATION_CLASS(klass)->activate = my_application_activate; 93 | G_APPLICATION_CLASS(klass)->local_command_line = my_application_local_command_line; 94 | G_OBJECT_CLASS(klass)->dispose = my_application_dispose; 95 | } 96 | 97 | static void my_application_init(MyApplication* self) {} 98 | 99 | MyApplication* my_application_new() { 100 | return MY_APPLICATION(g_object_new(my_application_get_type(), 101 | "application-id", APPLICATION_ID, 102 | "flags", G_APPLICATION_NON_UNIQUE, 103 | nullptr)); 104 | } 105 | -------------------------------------------------------------------------------- /shorebird_code_push/example/linux/my_application.h: -------------------------------------------------------------------------------- 1 | #ifndef FLUTTER_MY_APPLICATION_H_ 2 | #define FLUTTER_MY_APPLICATION_H_ 3 | 4 | #include 5 | 6 | G_DECLARE_FINAL_TYPE(MyApplication, my_application, MY, APPLICATION, 7 | GtkApplication) 8 | 9 | /** 10 | * my_application_new: 11 | * 12 | * Creates a new Flutter-based application. 13 | * 14 | * Returns: a new #MyApplication. 15 | */ 16 | MyApplication* my_application_new(); 17 | 18 | #endif // FLUTTER_MY_APPLICATION_H_ 19 | -------------------------------------------------------------------------------- /shorebird_code_push/example/macos/.gitignore: -------------------------------------------------------------------------------- 1 | # Flutter-related 2 | **/Flutter/ephemeral/ 3 | **/Pods/ 4 | 5 | # Xcode-related 6 | **/dgph 7 | **/xcuserdata/ 8 | -------------------------------------------------------------------------------- /shorebird_code_push/example/macos/Flutter/Flutter-Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" 2 | #include "ephemeral/Flutter-Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /shorebird_code_push/example/macos/Flutter/Flutter-Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" 2 | #include "ephemeral/Flutter-Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /shorebird_code_push/example/macos/Flutter/GeneratedPluginRegistrant.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Generated file. Do not edit. 3 | // 4 | 5 | import FlutterMacOS 6 | import Foundation 7 | 8 | 9 | func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { 10 | } 11 | -------------------------------------------------------------------------------- /shorebird_code_push/example/macos/Podfile: -------------------------------------------------------------------------------- 1 | platform :osx, '10.14' 2 | 3 | # CocoaPods analytics sends network stats synchronously affecting flutter build latency. 4 | ENV['COCOAPODS_DISABLE_STATS'] = 'true' 5 | 6 | project 'Runner', { 7 | 'Debug' => :debug, 8 | 'Profile' => :release, 9 | 'Release' => :release, 10 | } 11 | 12 | def flutter_root 13 | generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'ephemeral', 'Flutter-Generated.xcconfig'), __FILE__) 14 | unless File.exist?(generated_xcode_build_settings_path) 15 | raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure \"flutter pub get\" is executed first" 16 | end 17 | 18 | File.foreach(generated_xcode_build_settings_path) do |line| 19 | matches = line.match(/FLUTTER_ROOT\=(.*)/) 20 | return matches[1].strip if matches 21 | end 22 | raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Flutter-Generated.xcconfig, then run \"flutter pub get\"" 23 | end 24 | 25 | require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) 26 | 27 | flutter_macos_podfile_setup 28 | 29 | target 'Runner' do 30 | use_frameworks! 31 | use_modular_headers! 32 | 33 | flutter_install_all_macos_pods File.dirname(File.realpath(__FILE__)) 34 | target 'RunnerTests' do 35 | inherit! :search_paths 36 | end 37 | end 38 | 39 | post_install do |installer| 40 | installer.pods_project.targets.each do |target| 41 | flutter_additional_macos_build_settings(target) 42 | end 43 | end 44 | -------------------------------------------------------------------------------- /shorebird_code_push/example/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /shorebird_code_push/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 37 | 38 | 39 | 40 | 43 | 49 | 50 | 51 | 52 | 53 | 63 | 65 | 71 | 72 | 73 | 74 | 80 | 82 | 88 | 89 | 90 | 91 | 93 | 94 | 97 | 98 | 99 | -------------------------------------------------------------------------------- /shorebird_code_push/example/macos/Runner.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /shorebird_code_push/example/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /shorebird_code_push/example/macos/Runner/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | import Cocoa 2 | import FlutterMacOS 3 | 4 | @NSApplicationMain 5 | class AppDelegate: FlutterAppDelegate { 6 | override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool { 7 | return true 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /shorebird_code_push/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "size" : "16x16", 5 | "idiom" : "mac", 6 | "filename" : "app_icon_16.png", 7 | "scale" : "1x" 8 | }, 9 | { 10 | "size" : "16x16", 11 | "idiom" : "mac", 12 | "filename" : "app_icon_32.png", 13 | "scale" : "2x" 14 | }, 15 | { 16 | "size" : "32x32", 17 | "idiom" : "mac", 18 | "filename" : "app_icon_32.png", 19 | "scale" : "1x" 20 | }, 21 | { 22 | "size" : "32x32", 23 | "idiom" : "mac", 24 | "filename" : "app_icon_64.png", 25 | "scale" : "2x" 26 | }, 27 | { 28 | "size" : "128x128", 29 | "idiom" : "mac", 30 | "filename" : "app_icon_128.png", 31 | "scale" : "1x" 32 | }, 33 | { 34 | "size" : "128x128", 35 | "idiom" : "mac", 36 | "filename" : "app_icon_256.png", 37 | "scale" : "2x" 38 | }, 39 | { 40 | "size" : "256x256", 41 | "idiom" : "mac", 42 | "filename" : "app_icon_256.png", 43 | "scale" : "1x" 44 | }, 45 | { 46 | "size" : "256x256", 47 | "idiom" : "mac", 48 | "filename" : "app_icon_512.png", 49 | "scale" : "2x" 50 | }, 51 | { 52 | "size" : "512x512", 53 | "idiom" : "mac", 54 | "filename" : "app_icon_512.png", 55 | "scale" : "1x" 56 | }, 57 | { 58 | "size" : "512x512", 59 | "idiom" : "mac", 60 | "filename" : "app_icon_1024.png", 61 | "scale" : "2x" 62 | } 63 | ], 64 | "info" : { 65 | "version" : 1, 66 | "author" : "xcode" 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /shorebird_code_push/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shorebirdtech/updater/5d7690cd37112ff093b6f1a4f1d7c4c5e47619c3/shorebird_code_push/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png -------------------------------------------------------------------------------- /shorebird_code_push/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shorebirdtech/updater/5d7690cd37112ff093b6f1a4f1d7c4c5e47619c3/shorebird_code_push/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png -------------------------------------------------------------------------------- /shorebird_code_push/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shorebirdtech/updater/5d7690cd37112ff093b6f1a4f1d7c4c5e47619c3/shorebird_code_push/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png -------------------------------------------------------------------------------- /shorebird_code_push/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shorebirdtech/updater/5d7690cd37112ff093b6f1a4f1d7c4c5e47619c3/shorebird_code_push/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png -------------------------------------------------------------------------------- /shorebird_code_push/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shorebirdtech/updater/5d7690cd37112ff093b6f1a4f1d7c4c5e47619c3/shorebird_code_push/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png -------------------------------------------------------------------------------- /shorebird_code_push/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shorebirdtech/updater/5d7690cd37112ff093b6f1a4f1d7c4c5e47619c3/shorebird_code_push/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png -------------------------------------------------------------------------------- /shorebird_code_push/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shorebirdtech/updater/5d7690cd37112ff093b6f1a4f1d7c4c5e47619c3/shorebird_code_push/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png -------------------------------------------------------------------------------- /shorebird_code_push/example/macos/Runner/Configs/AppInfo.xcconfig: -------------------------------------------------------------------------------- 1 | // Application-level settings for the Runner target. 2 | // 3 | // This may be replaced with something auto-generated from metadata (e.g., pubspec.yaml) in the 4 | // future. If not, the values below would default to using the project name when this becomes a 5 | // 'flutter create' template. 6 | 7 | // The application's name. By default this is also the title of the Flutter window. 8 | PRODUCT_NAME = example 9 | 10 | // The application's bundle identifier 11 | PRODUCT_BUNDLE_IDENTIFIER = com.example.example 12 | 13 | // The copyright displayed in application information 14 | PRODUCT_COPYRIGHT = Copyright © 2023 com.example. All rights reserved. 15 | -------------------------------------------------------------------------------- /shorebird_code_push/example/macos/Runner/Configs/Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include "../../Flutter/Flutter-Debug.xcconfig" 2 | #include "Warnings.xcconfig" 3 | -------------------------------------------------------------------------------- /shorebird_code_push/example/macos/Runner/Configs/Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include "../../Flutter/Flutter-Release.xcconfig" 2 | #include "Warnings.xcconfig" 3 | -------------------------------------------------------------------------------- /shorebird_code_push/example/macos/Runner/Configs/Warnings.xcconfig: -------------------------------------------------------------------------------- 1 | WARNING_CFLAGS = -Wall -Wconditional-uninitialized -Wnullable-to-nonnull-conversion -Wmissing-method-return-type -Woverlength-strings 2 | GCC_WARN_UNDECLARED_SELECTOR = YES 3 | CLANG_UNDEFINED_BEHAVIOR_SANITIZER_NULLABILITY = YES 4 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE 5 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES 6 | CLANG_WARN_PRAGMA_PACK = YES 7 | CLANG_WARN_STRICT_PROTOTYPES = YES 8 | CLANG_WARN_COMMA = YES 9 | GCC_WARN_STRICT_SELECTOR_MATCH = YES 10 | CLANG_WARN_OBJC_REPEATED_USE_OF_WEAK = YES 11 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES 12 | GCC_WARN_SHADOW = YES 13 | CLANG_WARN_UNREACHABLE_CODE = YES 14 | -------------------------------------------------------------------------------- /shorebird_code_push/example/macos/Runner/DebugProfile.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.app-sandbox 6 | 7 | com.apple.security.cs.allow-jit 8 | 9 | com.apple.security.network.server 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /shorebird_code_push/example/macos/Runner/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIconFile 10 | 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | $(FLUTTER_BUILD_NAME) 21 | CFBundleVersion 22 | $(FLUTTER_BUILD_NUMBER) 23 | LSMinimumSystemVersion 24 | $(MACOSX_DEPLOYMENT_TARGET) 25 | NSHumanReadableCopyright 26 | $(PRODUCT_COPYRIGHT) 27 | NSMainNibFile 28 | MainMenu 29 | NSPrincipalClass 30 | NSApplication 31 | 32 | 33 | -------------------------------------------------------------------------------- /shorebird_code_push/example/macos/Runner/MainFlutterWindow.swift: -------------------------------------------------------------------------------- 1 | import Cocoa 2 | import FlutterMacOS 3 | 4 | class MainFlutterWindow: NSWindow { 5 | override func awakeFromNib() { 6 | let flutterViewController = FlutterViewController() 7 | let windowFrame = self.frame 8 | self.contentViewController = flutterViewController 9 | self.setFrame(windowFrame, display: true) 10 | 11 | RegisterGeneratedPlugins(registry: flutterViewController) 12 | 13 | super.awakeFromNib() 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /shorebird_code_push/example/macos/Runner/Release.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.app-sandbox 6 | 7 | com.apple.security.cs.allow-unsigned-executable-memory 8 | 9 | com.apple.security.network.client 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /shorebird_code_push/example/macos/RunnerTests/RunnerTests.swift: -------------------------------------------------------------------------------- 1 | import FlutterMacOS 2 | import Cocoa 3 | import XCTest 4 | 5 | class RunnerTests: XCTestCase { 6 | 7 | func testExample() { 8 | // If you add code to the Runner application, consider adding tests here. 9 | // See https://developer.apple.com/documentation/xctest for more information about using XCTest. 10 | } 11 | 12 | } 13 | -------------------------------------------------------------------------------- /shorebird_code_push/example/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: shorebird_code_push_example 2 | description: An example demonstrating how to use the shorebird_code_push package 3 | publish_to: "none" 4 | 5 | version: 1.0.0+1 6 | 7 | environment: 8 | sdk: ">=3.5.4 <4.0.0" 9 | flutter: ">=3.24.5" 10 | 11 | dependencies: 12 | flutter: 13 | sdk: flutter 14 | shorebird_code_push: 15 | path: ../ 16 | 17 | dev_dependencies: 18 | flutter_test: 19 | sdk: flutter 20 | very_good_analysis: ^7.0.0 21 | 22 | flutter: 23 | assets: 24 | - shorebird.yaml 25 | uses-material-design: true 26 | -------------------------------------------------------------------------------- /shorebird_code_push/example/shorebird.yaml: -------------------------------------------------------------------------------- 1 | # This file is used to configure the Shorebird updater used by your app. 2 | # Learn more at https://docs.shorebird.dev 3 | # This file does not contain any sensitive information and should be checked into version control. 4 | 5 | # Your app_id is the unique identifier assigned to your app. 6 | # It is used to identify your app when requesting patches from Shorebird's servers. 7 | # It is not a secret and can be shared publicly. 8 | app_id: 1692ba14-0c8d-490e-9593-13815d2ac1cf 9 | 10 | # auto_update controls if Shorebird should automatically update in the background on launch. 11 | # If auto_update: false, you will need to use package:shorebird_code_push to trigger updates. 12 | # https://pub.dev/packages/shorebird_code_push 13 | # Uncomment the following line to disable automatic updates. 14 | auto_update: false 15 | -------------------------------------------------------------------------------- /shorebird_code_push/example/web/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shorebirdtech/updater/5d7690cd37112ff093b6f1a4f1d7c4c5e47619c3/shorebird_code_push/example/web/favicon.png -------------------------------------------------------------------------------- /shorebird_code_push/example/web/icons/Icon-192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shorebirdtech/updater/5d7690cd37112ff093b6f1a4f1d7c4c5e47619c3/shorebird_code_push/example/web/icons/Icon-192.png -------------------------------------------------------------------------------- /shorebird_code_push/example/web/icons/Icon-512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shorebirdtech/updater/5d7690cd37112ff093b6f1a4f1d7c4c5e47619c3/shorebird_code_push/example/web/icons/Icon-512.png -------------------------------------------------------------------------------- /shorebird_code_push/example/web/icons/Icon-maskable-192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shorebirdtech/updater/5d7690cd37112ff093b6f1a4f1d7c4c5e47619c3/shorebird_code_push/example/web/icons/Icon-maskable-192.png -------------------------------------------------------------------------------- /shorebird_code_push/example/web/icons/Icon-maskable-512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shorebirdtech/updater/5d7690cd37112ff093b6f1a4f1d7c4c5e47619c3/shorebird_code_push/example/web/icons/Icon-maskable-512.png -------------------------------------------------------------------------------- /shorebird_code_push/example/web/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | example 33 | 34 | 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /shorebird_code_push/example/web/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "example", 3 | "short_name": "example", 4 | "start_url": ".", 5 | "display": "standalone", 6 | "background_color": "#0175C2", 7 | "theme_color": "#0175C2", 8 | "description": "A new Flutter project.", 9 | "orientation": "portrait-primary", 10 | "prefer_related_applications": false, 11 | "icons": [ 12 | { 13 | "src": "icons/Icon-192.png", 14 | "sizes": "192x192", 15 | "type": "image/png" 16 | }, 17 | { 18 | "src": "icons/Icon-512.png", 19 | "sizes": "512x512", 20 | "type": "image/png" 21 | }, 22 | { 23 | "src": "icons/Icon-maskable-192.png", 24 | "sizes": "192x192", 25 | "type": "image/png", 26 | "purpose": "maskable" 27 | }, 28 | { 29 | "src": "icons/Icon-maskable-512.png", 30 | "sizes": "512x512", 31 | "type": "image/png", 32 | "purpose": "maskable" 33 | } 34 | ] 35 | } 36 | -------------------------------------------------------------------------------- /shorebird_code_push/example/windows/.gitignore: -------------------------------------------------------------------------------- 1 | flutter/ephemeral/ 2 | 3 | # Visual Studio user-specific files. 4 | *.suo 5 | *.user 6 | *.userosscache 7 | *.sln.docstates 8 | 9 | # Visual Studio build-related files. 10 | x64/ 11 | x86/ 12 | 13 | # Visual Studio cache files 14 | # files ending in .cache can be ignored 15 | *.[Cc]ache 16 | # but keep track of directories ending in .cache 17 | !*.[Cc]ache/ 18 | -------------------------------------------------------------------------------- /shorebird_code_push/example/windows/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Project-level configuration. 2 | cmake_minimum_required(VERSION 3.14) 3 | project(example LANGUAGES CXX) 4 | 5 | # The name of the executable created for the application. Change this to change 6 | # the on-disk name of your application. 7 | set(BINARY_NAME "example") 8 | 9 | # Explicitly opt in to modern CMake behaviors to avoid warnings with recent 10 | # versions of CMake. 11 | cmake_policy(SET CMP0063 NEW) 12 | 13 | # Define build configuration option. 14 | get_property(IS_MULTICONFIG GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG) 15 | if(IS_MULTICONFIG) 16 | set(CMAKE_CONFIGURATION_TYPES "Debug;Profile;Release" 17 | CACHE STRING "" FORCE) 18 | else() 19 | if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) 20 | set(CMAKE_BUILD_TYPE "Debug" CACHE 21 | STRING "Flutter build mode" FORCE) 22 | set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS 23 | "Debug" "Profile" "Release") 24 | endif() 25 | endif() 26 | # Define settings for the Profile build mode. 27 | set(CMAKE_EXE_LINKER_FLAGS_PROFILE "${CMAKE_EXE_LINKER_FLAGS_RELEASE}") 28 | set(CMAKE_SHARED_LINKER_FLAGS_PROFILE "${CMAKE_SHARED_LINKER_FLAGS_RELEASE}") 29 | set(CMAKE_C_FLAGS_PROFILE "${CMAKE_C_FLAGS_RELEASE}") 30 | set(CMAKE_CXX_FLAGS_PROFILE "${CMAKE_CXX_FLAGS_RELEASE}") 31 | 32 | # Use Unicode for all projects. 33 | add_definitions(-DUNICODE -D_UNICODE) 34 | 35 | # Compilation settings that should be applied to most targets. 36 | # 37 | # Be cautious about adding new options here, as plugins use this function by 38 | # default. In most cases, you should add new options to specific targets instead 39 | # of modifying this function. 40 | function(APPLY_STANDARD_SETTINGS TARGET) 41 | target_compile_features(${TARGET} PUBLIC cxx_std_17) 42 | target_compile_options(${TARGET} PRIVATE /W4 /WX /wd"4100") 43 | target_compile_options(${TARGET} PRIVATE /EHsc) 44 | target_compile_definitions(${TARGET} PRIVATE "_HAS_EXCEPTIONS=0") 45 | target_compile_definitions(${TARGET} PRIVATE "$<$:_DEBUG>") 46 | endfunction() 47 | 48 | # Flutter library and tool build rules. 49 | set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter") 50 | add_subdirectory(${FLUTTER_MANAGED_DIR}) 51 | 52 | # Application build; see runner/CMakeLists.txt. 53 | add_subdirectory("runner") 54 | 55 | 56 | # Generated plugin build rules, which manage building the plugins and adding 57 | # them to the application. 58 | include(flutter/generated_plugins.cmake) 59 | 60 | 61 | # === Installation === 62 | # Support files are copied into place next to the executable, so that it can 63 | # run in place. This is done instead of making a separate bundle (as on Linux) 64 | # so that building and running from within Visual Studio will work. 65 | set(BUILD_BUNDLE_DIR "$") 66 | # Make the "install" step default, as it's required to run. 67 | set(CMAKE_VS_INCLUDE_INSTALL_TO_DEFAULT_BUILD 1) 68 | if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) 69 | set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE) 70 | endif() 71 | 72 | set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data") 73 | set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}") 74 | 75 | install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}" 76 | COMPONENT Runtime) 77 | 78 | install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" 79 | COMPONENT Runtime) 80 | 81 | install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" 82 | COMPONENT Runtime) 83 | 84 | if(PLUGIN_BUNDLED_LIBRARIES) 85 | install(FILES "${PLUGIN_BUNDLED_LIBRARIES}" 86 | DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" 87 | COMPONENT Runtime) 88 | endif() 89 | 90 | # Fully re-copy the assets directory on each build to avoid having stale files 91 | # from a previous install. 92 | set(FLUTTER_ASSET_DIR_NAME "flutter_assets") 93 | install(CODE " 94 | file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\") 95 | " COMPONENT Runtime) 96 | install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}" 97 | DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime) 98 | 99 | # Install the AOT library on non-Debug builds only. 100 | install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" 101 | CONFIGURATIONS Profile;Release 102 | COMPONENT Runtime) 103 | -------------------------------------------------------------------------------- /shorebird_code_push/example/windows/flutter/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # This file controls Flutter-level build steps. It should not be edited. 2 | cmake_minimum_required(VERSION 3.14) 3 | 4 | set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral") 5 | 6 | # Configuration provided via flutter tool. 7 | include(${EPHEMERAL_DIR}/generated_config.cmake) 8 | 9 | # TODO: Move the rest of this into files in ephemeral. See 10 | # https://github.com/flutter/flutter/issues/57146. 11 | set(WRAPPER_ROOT "${EPHEMERAL_DIR}/cpp_client_wrapper") 12 | 13 | # === Flutter Library === 14 | set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/flutter_windows.dll") 15 | 16 | # Published to parent scope for install step. 17 | set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE) 18 | set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE) 19 | set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE) 20 | set(AOT_LIBRARY "${PROJECT_DIR}/build/windows/app.so" PARENT_SCOPE) 21 | 22 | list(APPEND FLUTTER_LIBRARY_HEADERS 23 | "flutter_export.h" 24 | "flutter_windows.h" 25 | "flutter_messenger.h" 26 | "flutter_plugin_registrar.h" 27 | "flutter_texture_registrar.h" 28 | ) 29 | list(TRANSFORM FLUTTER_LIBRARY_HEADERS PREPEND "${EPHEMERAL_DIR}/") 30 | add_library(flutter INTERFACE) 31 | target_include_directories(flutter INTERFACE 32 | "${EPHEMERAL_DIR}" 33 | ) 34 | target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}.lib") 35 | add_dependencies(flutter flutter_assemble) 36 | 37 | # === Wrapper === 38 | list(APPEND CPP_WRAPPER_SOURCES_CORE 39 | "core_implementations.cc" 40 | "standard_codec.cc" 41 | ) 42 | list(TRANSFORM CPP_WRAPPER_SOURCES_CORE PREPEND "${WRAPPER_ROOT}/") 43 | list(APPEND CPP_WRAPPER_SOURCES_PLUGIN 44 | "plugin_registrar.cc" 45 | ) 46 | list(TRANSFORM CPP_WRAPPER_SOURCES_PLUGIN PREPEND "${WRAPPER_ROOT}/") 47 | list(APPEND CPP_WRAPPER_SOURCES_APP 48 | "flutter_engine.cc" 49 | "flutter_view_controller.cc" 50 | ) 51 | list(TRANSFORM CPP_WRAPPER_SOURCES_APP PREPEND "${WRAPPER_ROOT}/") 52 | 53 | # Wrapper sources needed for a plugin. 54 | add_library(flutter_wrapper_plugin STATIC 55 | ${CPP_WRAPPER_SOURCES_CORE} 56 | ${CPP_WRAPPER_SOURCES_PLUGIN} 57 | ) 58 | apply_standard_settings(flutter_wrapper_plugin) 59 | set_target_properties(flutter_wrapper_plugin PROPERTIES 60 | POSITION_INDEPENDENT_CODE ON) 61 | set_target_properties(flutter_wrapper_plugin PROPERTIES 62 | CXX_VISIBILITY_PRESET hidden) 63 | target_link_libraries(flutter_wrapper_plugin PUBLIC flutter) 64 | target_include_directories(flutter_wrapper_plugin PUBLIC 65 | "${WRAPPER_ROOT}/include" 66 | ) 67 | add_dependencies(flutter_wrapper_plugin flutter_assemble) 68 | 69 | # Wrapper sources needed for the runner. 70 | add_library(flutter_wrapper_app STATIC 71 | ${CPP_WRAPPER_SOURCES_CORE} 72 | ${CPP_WRAPPER_SOURCES_APP} 73 | ) 74 | apply_standard_settings(flutter_wrapper_app) 75 | target_link_libraries(flutter_wrapper_app PUBLIC flutter) 76 | target_include_directories(flutter_wrapper_app PUBLIC 77 | "${WRAPPER_ROOT}/include" 78 | ) 79 | add_dependencies(flutter_wrapper_app flutter_assemble) 80 | 81 | # === Flutter tool backend === 82 | # _phony_ is a non-existent file to force this command to run every time, 83 | # since currently there's no way to get a full input/output list from the 84 | # flutter tool. 85 | set(PHONY_OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/_phony_") 86 | set_source_files_properties("${PHONY_OUTPUT}" PROPERTIES SYMBOLIC TRUE) 87 | add_custom_command( 88 | OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS} 89 | ${CPP_WRAPPER_SOURCES_CORE} ${CPP_WRAPPER_SOURCES_PLUGIN} 90 | ${CPP_WRAPPER_SOURCES_APP} 91 | ${PHONY_OUTPUT} 92 | COMMAND ${CMAKE_COMMAND} -E env 93 | ${FLUTTER_TOOL_ENVIRONMENT} 94 | "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.bat" 95 | windows-x64 $ 96 | VERBATIM 97 | ) 98 | add_custom_target(flutter_assemble DEPENDS 99 | "${FLUTTER_LIBRARY}" 100 | ${FLUTTER_LIBRARY_HEADERS} 101 | ${CPP_WRAPPER_SOURCES_CORE} 102 | ${CPP_WRAPPER_SOURCES_PLUGIN} 103 | ${CPP_WRAPPER_SOURCES_APP} 104 | ) 105 | -------------------------------------------------------------------------------- /shorebird_code_push/example/windows/flutter/generated_plugin_registrant.cc: -------------------------------------------------------------------------------- 1 | // 2 | // Generated file. Do not edit. 3 | // 4 | 5 | // clang-format off 6 | 7 | #include "generated_plugin_registrant.h" 8 | 9 | 10 | void RegisterPlugins(flutter::PluginRegistry* registry) { 11 | } 12 | -------------------------------------------------------------------------------- /shorebird_code_push/example/windows/flutter/generated_plugin_registrant.h: -------------------------------------------------------------------------------- 1 | // 2 | // Generated file. Do not edit. 3 | // 4 | 5 | // clang-format off 6 | 7 | #ifndef GENERATED_PLUGIN_REGISTRANT_ 8 | #define GENERATED_PLUGIN_REGISTRANT_ 9 | 10 | #include 11 | 12 | // Registers Flutter plugins. 13 | void RegisterPlugins(flutter::PluginRegistry* registry); 14 | 15 | #endif // GENERATED_PLUGIN_REGISTRANT_ 16 | -------------------------------------------------------------------------------- /shorebird_code_push/example/windows/flutter/generated_plugins.cmake: -------------------------------------------------------------------------------- 1 | # 2 | # Generated file, do not edit. 3 | # 4 | 5 | list(APPEND FLUTTER_PLUGIN_LIST 6 | ) 7 | 8 | list(APPEND FLUTTER_FFI_PLUGIN_LIST 9 | ) 10 | 11 | set(PLUGIN_BUNDLED_LIBRARIES) 12 | 13 | foreach(plugin ${FLUTTER_PLUGIN_LIST}) 14 | add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/windows plugins/${plugin}) 15 | target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin) 16 | list(APPEND PLUGIN_BUNDLED_LIBRARIES $) 17 | list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) 18 | endforeach(plugin) 19 | 20 | foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) 21 | add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/windows plugins/${ffi_plugin}) 22 | list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) 23 | endforeach(ffi_plugin) 24 | -------------------------------------------------------------------------------- /shorebird_code_push/example/windows/runner/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.14) 2 | project(runner LANGUAGES CXX) 3 | 4 | # Define the application target. To change its name, change BINARY_NAME in the 5 | # top-level CMakeLists.txt, not the value here, or `flutter run` will no longer 6 | # work. 7 | # 8 | # Any new source files that you add to the application should be added here. 9 | add_executable(${BINARY_NAME} WIN32 10 | "flutter_window.cpp" 11 | "main.cpp" 12 | "utils.cpp" 13 | "win32_window.cpp" 14 | "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc" 15 | "Runner.rc" 16 | "runner.exe.manifest" 17 | ) 18 | 19 | # Apply the standard set of build settings. This can be removed for applications 20 | # that need different build settings. 21 | apply_standard_settings(${BINARY_NAME}) 22 | 23 | # Add preprocessor definitions for the build version. 24 | target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION=\"${FLUTTER_VERSION}\"") 25 | target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_MAJOR=${FLUTTER_VERSION_MAJOR}") 26 | target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_MINOR=${FLUTTER_VERSION_MINOR}") 27 | target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_PATCH=${FLUTTER_VERSION_PATCH}") 28 | target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_BUILD=${FLUTTER_VERSION_BUILD}") 29 | 30 | # Disable Windows macros that collide with C++ standard library functions. 31 | target_compile_definitions(${BINARY_NAME} PRIVATE "NOMINMAX") 32 | 33 | # Add dependency libraries and include directories. Add any application-specific 34 | # dependencies here. 35 | target_link_libraries(${BINARY_NAME} PRIVATE flutter flutter_wrapper_app) 36 | target_link_libraries(${BINARY_NAME} PRIVATE "dwmapi.lib") 37 | target_include_directories(${BINARY_NAME} PRIVATE "${CMAKE_SOURCE_DIR}") 38 | 39 | # Run the Flutter tool portions of the build. This must not be removed. 40 | add_dependencies(${BINARY_NAME} flutter_assemble) 41 | -------------------------------------------------------------------------------- /shorebird_code_push/example/windows/runner/Runner.rc: -------------------------------------------------------------------------------- 1 | // Microsoft Visual C++ generated resource script. 2 | // 3 | #pragma code_page(65001) 4 | #include "resource.h" 5 | 6 | #define APSTUDIO_READONLY_SYMBOLS 7 | ///////////////////////////////////////////////////////////////////////////// 8 | // 9 | // Generated from the TEXTINCLUDE 2 resource. 10 | // 11 | #include "winres.h" 12 | 13 | ///////////////////////////////////////////////////////////////////////////// 14 | #undef APSTUDIO_READONLY_SYMBOLS 15 | 16 | ///////////////////////////////////////////////////////////////////////////// 17 | // English (United States) resources 18 | 19 | #if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) 20 | LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US 21 | 22 | #ifdef APSTUDIO_INVOKED 23 | ///////////////////////////////////////////////////////////////////////////// 24 | // 25 | // TEXTINCLUDE 26 | // 27 | 28 | 1 TEXTINCLUDE 29 | BEGIN 30 | "resource.h\0" 31 | END 32 | 33 | 2 TEXTINCLUDE 34 | BEGIN 35 | "#include ""winres.h""\r\n" 36 | "\0" 37 | END 38 | 39 | 3 TEXTINCLUDE 40 | BEGIN 41 | "\r\n" 42 | "\0" 43 | END 44 | 45 | #endif // APSTUDIO_INVOKED 46 | 47 | 48 | ///////////////////////////////////////////////////////////////////////////// 49 | // 50 | // Icon 51 | // 52 | 53 | // Icon with lowest ID value placed first to ensure application icon 54 | // remains consistent on all systems. 55 | IDI_APP_ICON ICON "resources\\app_icon.ico" 56 | 57 | 58 | ///////////////////////////////////////////////////////////////////////////// 59 | // 60 | // Version 61 | // 62 | 63 | #if defined(FLUTTER_VERSION_MAJOR) && defined(FLUTTER_VERSION_MINOR) && defined(FLUTTER_VERSION_PATCH) && defined(FLUTTER_VERSION_BUILD) 64 | #define VERSION_AS_NUMBER FLUTTER_VERSION_MAJOR,FLUTTER_VERSION_MINOR,FLUTTER_VERSION_PATCH,FLUTTER_VERSION_BUILD 65 | #else 66 | #define VERSION_AS_NUMBER 1,0,0,0 67 | #endif 68 | 69 | #if defined(FLUTTER_VERSION) 70 | #define VERSION_AS_STRING FLUTTER_VERSION 71 | #else 72 | #define VERSION_AS_STRING "1.0.0" 73 | #endif 74 | 75 | VS_VERSION_INFO VERSIONINFO 76 | FILEVERSION VERSION_AS_NUMBER 77 | PRODUCTVERSION VERSION_AS_NUMBER 78 | FILEFLAGSMASK VS_FFI_FILEFLAGSMASK 79 | #ifdef _DEBUG 80 | FILEFLAGS VS_FF_DEBUG 81 | #else 82 | FILEFLAGS 0x0L 83 | #endif 84 | FILEOS VOS__WINDOWS32 85 | FILETYPE VFT_APP 86 | FILESUBTYPE 0x0L 87 | BEGIN 88 | BLOCK "StringFileInfo" 89 | BEGIN 90 | BLOCK "040904e4" 91 | BEGIN 92 | VALUE "CompanyName", "com.example" "\0" 93 | VALUE "FileDescription", "example" "\0" 94 | VALUE "FileVersion", VERSION_AS_STRING "\0" 95 | VALUE "InternalName", "example" "\0" 96 | VALUE "LegalCopyright", "Copyright (C) 2023 com.example. All rights reserved." "\0" 97 | VALUE "OriginalFilename", "example.exe" "\0" 98 | VALUE "ProductName", "example" "\0" 99 | VALUE "ProductVersion", VERSION_AS_STRING "\0" 100 | END 101 | END 102 | BLOCK "VarFileInfo" 103 | BEGIN 104 | VALUE "Translation", 0x409, 1252 105 | END 106 | END 107 | 108 | #endif // English (United States) resources 109 | ///////////////////////////////////////////////////////////////////////////// 110 | 111 | 112 | 113 | #ifndef APSTUDIO_INVOKED 114 | ///////////////////////////////////////////////////////////////////////////// 115 | // 116 | // Generated from the TEXTINCLUDE 3 resource. 117 | // 118 | 119 | 120 | ///////////////////////////////////////////////////////////////////////////// 121 | #endif // not APSTUDIO_INVOKED 122 | -------------------------------------------------------------------------------- /shorebird_code_push/example/windows/runner/flutter_window.cpp: -------------------------------------------------------------------------------- 1 | #include "flutter_window.h" 2 | 3 | #include 4 | 5 | #include "flutter/generated_plugin_registrant.h" 6 | 7 | FlutterWindow::FlutterWindow(const flutter::DartProject& project) 8 | : project_(project) {} 9 | 10 | FlutterWindow::~FlutterWindow() {} 11 | 12 | bool FlutterWindow::OnCreate() { 13 | if (!Win32Window::OnCreate()) { 14 | return false; 15 | } 16 | 17 | RECT frame = GetClientArea(); 18 | 19 | // The size here must match the window dimensions to avoid unnecessary surface 20 | // creation / destruction in the startup path. 21 | flutter_controller_ = std::make_unique( 22 | frame.right - frame.left, frame.bottom - frame.top, project_); 23 | // Ensure that basic setup of the controller was successful. 24 | if (!flutter_controller_->engine() || !flutter_controller_->view()) { 25 | return false; 26 | } 27 | RegisterPlugins(flutter_controller_->engine()); 28 | SetChildContent(flutter_controller_->view()->GetNativeWindow()); 29 | 30 | flutter_controller_->engine()->SetNextFrameCallback([&]() { 31 | this->Show(); 32 | }); 33 | 34 | return true; 35 | } 36 | 37 | void FlutterWindow::OnDestroy() { 38 | if (flutter_controller_) { 39 | flutter_controller_ = nullptr; 40 | } 41 | 42 | Win32Window::OnDestroy(); 43 | } 44 | 45 | LRESULT 46 | FlutterWindow::MessageHandler(HWND hwnd, UINT const message, 47 | WPARAM const wparam, 48 | LPARAM const lparam) noexcept { 49 | // Give Flutter, including plugins, an opportunity to handle window messages. 50 | if (flutter_controller_) { 51 | std::optional result = 52 | flutter_controller_->HandleTopLevelWindowProc(hwnd, message, wparam, 53 | lparam); 54 | if (result) { 55 | return *result; 56 | } 57 | } 58 | 59 | switch (message) { 60 | case WM_FONTCHANGE: 61 | flutter_controller_->engine()->ReloadSystemFonts(); 62 | break; 63 | } 64 | 65 | return Win32Window::MessageHandler(hwnd, message, wparam, lparam); 66 | } 67 | -------------------------------------------------------------------------------- /shorebird_code_push/example/windows/runner/flutter_window.h: -------------------------------------------------------------------------------- 1 | #ifndef RUNNER_FLUTTER_WINDOW_H_ 2 | #define RUNNER_FLUTTER_WINDOW_H_ 3 | 4 | #include 5 | #include 6 | 7 | #include 8 | 9 | #include "win32_window.h" 10 | 11 | // A window that does nothing but host a Flutter view. 12 | class FlutterWindow : public Win32Window { 13 | public: 14 | // Creates a new FlutterWindow hosting a Flutter view running |project|. 15 | explicit FlutterWindow(const flutter::DartProject& project); 16 | virtual ~FlutterWindow(); 17 | 18 | protected: 19 | // Win32Window: 20 | bool OnCreate() override; 21 | void OnDestroy() override; 22 | LRESULT MessageHandler(HWND window, UINT const message, WPARAM const wparam, 23 | LPARAM const lparam) noexcept override; 24 | 25 | private: 26 | // The project to run. 27 | flutter::DartProject project_; 28 | 29 | // The Flutter instance hosted by this window. 30 | std::unique_ptr flutter_controller_; 31 | }; 32 | 33 | #endif // RUNNER_FLUTTER_WINDOW_H_ 34 | -------------------------------------------------------------------------------- /shorebird_code_push/example/windows/runner/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "flutter_window.h" 6 | #include "utils.h" 7 | 8 | int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev, 9 | _In_ wchar_t *command_line, _In_ int show_command) { 10 | // Attach to console when present (e.g., 'flutter run') or create a 11 | // new console when running with a debugger. 12 | if (!::AttachConsole(ATTACH_PARENT_PROCESS) && ::IsDebuggerPresent()) { 13 | CreateAndAttachConsole(); 14 | } 15 | 16 | // Initialize COM, so that it is available for use in the library and/or 17 | // plugins. 18 | ::CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED); 19 | 20 | flutter::DartProject project(L"data"); 21 | 22 | std::vector command_line_arguments = 23 | GetCommandLineArguments(); 24 | 25 | project.set_dart_entrypoint_arguments(std::move(command_line_arguments)); 26 | 27 | FlutterWindow window(project); 28 | Win32Window::Point origin(10, 10); 29 | Win32Window::Size size(1280, 720); 30 | if (!window.Create(L"example", origin, size)) { 31 | return EXIT_FAILURE; 32 | } 33 | window.SetQuitOnClose(true); 34 | 35 | ::MSG msg; 36 | while (::GetMessage(&msg, nullptr, 0, 0)) { 37 | ::TranslateMessage(&msg); 38 | ::DispatchMessage(&msg); 39 | } 40 | 41 | ::CoUninitialize(); 42 | return EXIT_SUCCESS; 43 | } 44 | -------------------------------------------------------------------------------- /shorebird_code_push/example/windows/runner/resource.h: -------------------------------------------------------------------------------- 1 | //{{NO_DEPENDENCIES}} 2 | // Microsoft Visual C++ generated include file. 3 | // Used by Runner.rc 4 | // 5 | #define IDI_APP_ICON 101 6 | 7 | // Next default values for new objects 8 | // 9 | #ifdef APSTUDIO_INVOKED 10 | #ifndef APSTUDIO_READONLY_SYMBOLS 11 | #define _APS_NEXT_RESOURCE_VALUE 102 12 | #define _APS_NEXT_COMMAND_VALUE 40001 13 | #define _APS_NEXT_CONTROL_VALUE 1001 14 | #define _APS_NEXT_SYMED_VALUE 101 15 | #endif 16 | #endif 17 | -------------------------------------------------------------------------------- /shorebird_code_push/example/windows/runner/resources/app_icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shorebirdtech/updater/5d7690cd37112ff093b6f1a4f1d7c4c5e47619c3/shorebird_code_push/example/windows/runner/resources/app_icon.ico -------------------------------------------------------------------------------- /shorebird_code_push/example/windows/runner/runner.exe.manifest: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PerMonitorV2 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /shorebird_code_push/example/windows/runner/utils.cpp: -------------------------------------------------------------------------------- 1 | #include "utils.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include 9 | 10 | void CreateAndAttachConsole() { 11 | if (::AllocConsole()) { 12 | FILE *unused; 13 | if (freopen_s(&unused, "CONOUT$", "w", stdout)) { 14 | _dup2(_fileno(stdout), 1); 15 | } 16 | if (freopen_s(&unused, "CONOUT$", "w", stderr)) { 17 | _dup2(_fileno(stdout), 2); 18 | } 19 | std::ios::sync_with_stdio(); 20 | FlutterDesktopResyncOutputStreams(); 21 | } 22 | } 23 | 24 | std::vector GetCommandLineArguments() { 25 | // Convert the UTF-16 command line arguments to UTF-8 for the Engine to use. 26 | int argc; 27 | wchar_t** argv = ::CommandLineToArgvW(::GetCommandLineW(), &argc); 28 | if (argv == nullptr) { 29 | return std::vector(); 30 | } 31 | 32 | std::vector command_line_arguments; 33 | 34 | // Skip the first argument as it's the binary name. 35 | for (int i = 1; i < argc; i++) { 36 | command_line_arguments.push_back(Utf8FromUtf16(argv[i])); 37 | } 38 | 39 | ::LocalFree(argv); 40 | 41 | return command_line_arguments; 42 | } 43 | 44 | std::string Utf8FromUtf16(const wchar_t* utf16_string) { 45 | if (utf16_string == nullptr) { 46 | return std::string(); 47 | } 48 | int target_length = ::WideCharToMultiByte( 49 | CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, 50 | -1, nullptr, 0, nullptr, nullptr) 51 | -1; // remove the trailing null character 52 | int input_length = (int)wcslen(utf16_string); 53 | std::string utf8_string; 54 | if (target_length <= 0 || target_length > utf8_string.max_size()) { 55 | return utf8_string; 56 | } 57 | utf8_string.resize(target_length); 58 | int converted_length = ::WideCharToMultiByte( 59 | CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, 60 | input_length, utf8_string.data(), target_length, nullptr, nullptr); 61 | if (converted_length == 0) { 62 | return std::string(); 63 | } 64 | return utf8_string; 65 | } 66 | -------------------------------------------------------------------------------- /shorebird_code_push/example/windows/runner/utils.h: -------------------------------------------------------------------------------- 1 | #ifndef RUNNER_UTILS_H_ 2 | #define RUNNER_UTILS_H_ 3 | 4 | #include 5 | #include 6 | 7 | // Creates a console for the process, and redirects stdout and stderr to 8 | // it for both the runner and the Flutter library. 9 | void CreateAndAttachConsole(); 10 | 11 | // Takes a null-terminated wchar_t* encoded in UTF-16 and returns a std::string 12 | // encoded in UTF-8. Returns an empty std::string on failure. 13 | std::string Utf8FromUtf16(const wchar_t* utf16_string); 14 | 15 | // Gets the command line arguments passed in as a std::vector, 16 | // encoded in UTF-8. Returns an empty std::vector on failure. 17 | std::vector GetCommandLineArguments(); 18 | 19 | #endif // RUNNER_UTILS_H_ 20 | -------------------------------------------------------------------------------- /shorebird_code_push/example/windows/runner/win32_window.h: -------------------------------------------------------------------------------- 1 | #ifndef RUNNER_WIN32_WINDOW_H_ 2 | #define RUNNER_WIN32_WINDOW_H_ 3 | 4 | #include 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | // A class abstraction for a high DPI-aware Win32 Window. Intended to be 11 | // inherited from by classes that wish to specialize with custom 12 | // rendering and input handling 13 | class Win32Window { 14 | public: 15 | struct Point { 16 | unsigned int x; 17 | unsigned int y; 18 | Point(unsigned int x, unsigned int y) : x(x), y(y) {} 19 | }; 20 | 21 | struct Size { 22 | unsigned int width; 23 | unsigned int height; 24 | Size(unsigned int width, unsigned int height) 25 | : width(width), height(height) {} 26 | }; 27 | 28 | Win32Window(); 29 | virtual ~Win32Window(); 30 | 31 | // Creates a win32 window with |title| that is positioned and sized using 32 | // |origin| and |size|. New windows are created on the default monitor. Window 33 | // sizes are specified to the OS in physical pixels, hence to ensure a 34 | // consistent size this function will scale the inputted width and height as 35 | // as appropriate for the default monitor. The window is invisible until 36 | // |Show| is called. Returns true if the window was created successfully. 37 | bool Create(const std::wstring& title, const Point& origin, const Size& size); 38 | 39 | // Show the current window. Returns true if the window was successfully shown. 40 | bool Show(); 41 | 42 | // Release OS resources associated with window. 43 | void Destroy(); 44 | 45 | // Inserts |content| into the window tree. 46 | void SetChildContent(HWND content); 47 | 48 | // Returns the backing Window handle to enable clients to set icon and other 49 | // window properties. Returns nullptr if the window has been destroyed. 50 | HWND GetHandle(); 51 | 52 | // If true, closing this window will quit the application. 53 | void SetQuitOnClose(bool quit_on_close); 54 | 55 | // Return a RECT representing the bounds of the current client area. 56 | RECT GetClientArea(); 57 | 58 | protected: 59 | // Processes and route salient window messages for mouse handling, 60 | // size change and DPI. Delegates handling of these to member overloads that 61 | // inheriting classes can handle. 62 | virtual LRESULT MessageHandler(HWND window, 63 | UINT const message, 64 | WPARAM const wparam, 65 | LPARAM const lparam) noexcept; 66 | 67 | // Called when CreateAndShow is called, allowing subclass window-related 68 | // setup. Subclasses should return false if setup fails. 69 | virtual bool OnCreate(); 70 | 71 | // Called when Destroy is called. 72 | virtual void OnDestroy(); 73 | 74 | private: 75 | friend class WindowClassRegistrar; 76 | 77 | // OS callback called by message pump. Handles the WM_NCCREATE message which 78 | // is passed when the non-client area is being created and enables automatic 79 | // non-client DPI scaling so that the non-client area automatically 80 | // responds to changes in DPI. All other messages are handled by 81 | // MessageHandler. 82 | static LRESULT CALLBACK WndProc(HWND const window, 83 | UINT const message, 84 | WPARAM const wparam, 85 | LPARAM const lparam) noexcept; 86 | 87 | // Retrieves a class instance pointer for |window| 88 | static Win32Window* GetThisFromHandle(HWND const window) noexcept; 89 | 90 | // Update the window frame's theme to match the system theme. 91 | static void UpdateTheme(HWND const window); 92 | 93 | bool quit_on_close_ = false; 94 | 95 | // window handle for top level window. 96 | HWND window_handle_ = nullptr; 97 | 98 | // window handle for hosted content. 99 | HWND child_content_ = nullptr; 100 | }; 101 | 102 | #endif // RUNNER_WIN32_WINDOW_H_ 103 | -------------------------------------------------------------------------------- /shorebird_code_push/lib/shorebird_code_push.dart: -------------------------------------------------------------------------------- 1 | export 'src/shorebird_updater.dart' 2 | show 3 | Patch, 4 | ReadPatchException, 5 | ShorebirdUpdater, 6 | UpdateException, 7 | UpdateFailureReason, 8 | UpdateStatus, 9 | UpdateTrack; 10 | -------------------------------------------------------------------------------- /shorebird_code_push/lib/src/shorebird_updater.dart: -------------------------------------------------------------------------------- 1 | import 'package:shorebird_code_push/src/shorebird_updater_io.dart' 2 | if (dart.library.js_interop) './shorebird_updater_web.dart'; 3 | 4 | /// The reason a call to [ShorebirdUpdater.update] failed. 5 | enum UpdateFailureReason { 6 | /// No update is available. 7 | noUpdate, 8 | 9 | /// The update failed because the patch could not be downloaded. 10 | downloadFailed, 11 | 12 | /// The update failed because the patch failed to install. 13 | installFailed, 14 | 15 | /// The update failed for an unknown reason. 16 | unknown, 17 | } 18 | 19 | /// {@template read_patch_exception} 20 | /// An exception thrown by [ShorebirdUpdater.readCurrentPatch] and 21 | /// [ShorebirdUpdater.readNextPatch] when the read is unsuccessful. 22 | /// {@endtemplate} 23 | class ReadPatchException implements Exception { 24 | /// {@macro update_exception} 25 | const ReadPatchException({required this.message}); 26 | 27 | /// The human-readable error message. 28 | final String message; 29 | 30 | @override 31 | String toString() => '[ShorebirdUpdater] ReadPatchException: $message'; 32 | } 33 | 34 | /// {@template update_exception} 35 | /// An exception thrown by [ShorebirdUpdater.update] when the update is 36 | /// unsuccessful. 37 | /// {@endtemplate} 38 | class UpdateException implements Exception { 39 | /// {@macro update_exception} 40 | const UpdateException({required this.message, required this.reason}); 41 | 42 | /// The human-readable error message. 43 | final String message; 44 | 45 | /// The reason the update failed. 46 | final UpdateFailureReason reason; 47 | 48 | @override 49 | String toString() { 50 | return '[ShorebirdUpdater] UpdateException: $message (${reason.name})'; 51 | } 52 | } 53 | 54 | /// Log message when the Shorebird updater is unavailable in the current 55 | /// environment. 56 | void logShorebirdEngineUnavailableMessage() { 57 | // Printing to the console is intentional here since we want it to be obvious 58 | // that the app is running in an environment where the updater is unavailable. 59 | // ignore: avoid_print 60 | print(''' 61 | ------------------------------------------------------------------------------- 62 | The Shorebird Updater is unavailable in the current environment. 63 | ------------------------------------------------------------------------------- 64 | This occurs when using pkg:shorebird_code_push in an app that does not 65 | contain the Shorebird Engine. Most commonly this is due to building with 66 | `flutter build` or `flutter run` instead of `shorebird release` or `shorebird preview`. 67 | It can also occur when running on an unsupported platform (e.g. web). 68 | '''); 69 | } 70 | 71 | /// {@template patch} 72 | /// An object representing a single patch (over-the-air update). 73 | /// {@endtemplate} 74 | class Patch { 75 | /// {@macro patch} 76 | const Patch({required this.number}); 77 | 78 | /// The patch number. 79 | final int number; 80 | } 81 | 82 | /// The current status of the app in terms of whether its up-to-date. 83 | enum UpdateStatus { 84 | /// The app is up to date (e.g. running the latest patch.) 85 | upToDate, 86 | 87 | /// A new update is available for download. 88 | outdated, 89 | 90 | /// The app is up to date, but a restart is required for the update to take 91 | /// effect. 92 | restartRequired, 93 | 94 | /// The update status is unavailable. This occurs when the updater is not 95 | /// available in the current build. 96 | /// See also: 97 | /// * [ShorebirdUpdater.isAvailable] to determine if the updater is 98 | /// available. 99 | unavailable, 100 | } 101 | 102 | /// {@template shorebird_updater} 103 | /// Manage updates for a Shorebird app. 104 | /// {@endtemplate} 105 | abstract class ShorebirdUpdater { 106 | /// {@macro shorebird_updater} 107 | factory ShorebirdUpdater() => ShorebirdUpdaterImpl(); 108 | 109 | /// Whether the updater is available on the current platform. 110 | /// The most common reasons for this returning false are: 111 | /// 1. The app is running in debug mode (Shorebird only supports release 112 | /// mode). 113 | /// 2. The app was *NOT* built using `shorebird release` and does *NOT* 114 | /// contain the Shorebird engine. 115 | bool get isAvailable; 116 | 117 | /// Returns information about the currently installed patch. 118 | /// Returns `null` if no patch has been installed. 119 | /// Returns `null` if the updater is not available. 120 | /// Throws a [ReadPatchException] if the read is unsuccessful. 121 | Future readCurrentPatch(); 122 | 123 | /// Returns information about the most recently downloaded patch. 124 | /// Returns the same patch as [readCurrentPatch] if no new patch has been 125 | /// downloaded. 126 | /// Returns `null` if the updater is not available. 127 | /// Throws a [ReadPatchException] if the read is unsuccessful. 128 | Future readNextPatch(); 129 | 130 | /// Checks for an available patch on [track] (or [UpdateTrack.stable] if no 131 | /// track is specified) and returns the [UpdateStatus]. 132 | /// This method should be used to determine the update status before calling 133 | /// [update]. 134 | /// 135 | /// If this detects that the current patch has been rolled back, the current 136 | /// patch will be uninstalled. 137 | /// A separate call to `update()` is required to install new patches. 138 | Future checkForUpdate({UpdateTrack? track}); 139 | 140 | /// Updates the app to the latest patch available on the specified track, or 141 | /// [UpdateTrack.stable] if no track is specified. 142 | /// 143 | /// If no update is available or the update fails, this method will throw an 144 | /// [UpdateException]. 145 | /// 146 | /// The returned Future will complete once the update is fully downloaded and 147 | /// ready to be used on the next app start. 148 | /// 149 | /// Note: The app must be restarted for the update to take effect. 150 | /// Note: This method does nothing if the updater is not available. 151 | /// 152 | /// See also: 153 | /// * [isAvailable], which indicates whether the updater is available. 154 | /// * [checkForUpdate], which should be called to check if an update is 155 | /// available before calling this method. 156 | Future update({UpdateTrack? track}); 157 | } 158 | 159 | /// A track to check for updates on. 160 | /// 161 | /// In addition to the predefined tracks, you can also specify your own track 162 | /// names by creating an instance of `UpdateTrack` with a custom string value. 163 | /// 164 | /// For example, if you have a custom track named "my_custom_track", you can 165 | /// create a patch on that track: 166 | /// 167 | /// ```sh 168 | /// shorebird patch android --track my_custom_track 169 | /// ``` 170 | /// 171 | /// And then check for updates on that track in your app: 172 | /// 173 | /// ```dart 174 | /// final status = checkForUpdate(track: UpdateTrack('my_custom_track')); 175 | /// ``` 176 | extension type const UpdateTrack(String value) { 177 | /// Used for internal testing. 178 | static const staging = UpdateTrack('staging'); 179 | 180 | /// Used for public testing. 181 | static const beta = UpdateTrack('beta'); 182 | 183 | /// Used for general availability. This is the default track. 184 | static const stable = UpdateTrack('stable'); 185 | 186 | /// The name of the track. 187 | String get name => value; 188 | } 189 | -------------------------------------------------------------------------------- /shorebird_code_push/lib/src/shorebird_updater_io.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | import 'dart:ffi'; 3 | import 'dart:isolate'; 4 | 5 | import 'package:ffi/ffi.dart'; 6 | import 'package:meta/meta.dart'; 7 | import 'package:shorebird_code_push/src/generated/updater_bindings.g.dart'; 8 | import 'package:shorebird_code_push/src/shorebird_updater.dart'; 9 | import 'package:shorebird_code_push/src/updater.dart'; 10 | 11 | @visibleForTesting 12 | 13 | /// Type definition for [Isolate.run]. 14 | typedef IsolateRun = Future Function( 15 | FutureOr Function(), { 16 | String? debugName, 17 | }); 18 | 19 | /// {@template shorebird_updater_io} 20 | /// The Shorebird IO Updater. 21 | /// {@endtemplate} 22 | class ShorebirdUpdaterImpl implements ShorebirdUpdater { 23 | /// {@macro shorebird_updater_io} 24 | ShorebirdUpdaterImpl({Updater? updater, IsolateRun? run}) 25 | : _updater = updater ?? const Updater(), 26 | _run = run ?? Isolate.run { 27 | try { 28 | // If the Shorebird Engine is not available, this will throw an exception. 29 | // FIXME: Run this in an isolate or refactor the updater to avoid risking 30 | // a hang. If another thread is also calling into Shorebird at the same 31 | // time the underlying Rust code could block getting the config lock. 32 | _updater.currentPatchNumber(); 33 | _isAvailable = true; 34 | // We explicitly catch all errors and exceptions to ensure we notify users 35 | // when the Shorebird Updater is unavailable. 36 | // ignore: avoid_catches_without_on_clauses 37 | } catch (_) { 38 | logShorebirdEngineUnavailableMessage(); 39 | _isAvailable = false; 40 | } 41 | } 42 | 43 | late final bool _isAvailable; 44 | 45 | final Updater _updater; 46 | 47 | final IsolateRun _run; 48 | 49 | @override 50 | bool get isAvailable => _isAvailable; 51 | 52 | @override 53 | Future readCurrentPatch() => _readPatch(_updater.currentPatchNumber); 54 | 55 | @override 56 | Future readNextPatch() => _readPatch(_updater.nextPatchNumber); 57 | 58 | Future _readPatch(int Function() fn) async { 59 | if (!_isAvailable) return null; 60 | return _run( 61 | () { 62 | try { 63 | final patchNumber = fn(); 64 | return patchNumber > 0 ? Patch(number: patchNumber) : null; 65 | } catch (error) { 66 | throw ReadPatchException(message: '$error'); 67 | } 68 | }, 69 | ); 70 | } 71 | 72 | @override 73 | Future checkForUpdate({UpdateTrack? track}) async { 74 | if (!_isAvailable) return UpdateStatus.unavailable; 75 | 76 | // First, check to see whether an update is available for download. 77 | final isUpdateAvailable = 78 | await _run(() => _updater.checkForDownloadableUpdate(track: track)); 79 | if (isUpdateAvailable) return UpdateStatus.outdated; 80 | 81 | // If no new update is available for download, see if a new patch exists 82 | // on disk that requires a restart. 83 | final (current, next) = await (readCurrentPatch(), readNextPatch()).wait; 84 | return next != null && current?.number != next.number 85 | ? UpdateStatus.restartRequired 86 | : UpdateStatus.upToDate; 87 | } 88 | 89 | @override 90 | Future update({UpdateTrack? track}) async { 91 | if (!_isAvailable) return; 92 | 93 | Pointer result = nullptr; 94 | 95 | try { 96 | result = await _run(() => _updater.update(track: track)); 97 | // Explicitly catch all errors/exceptions to ensure we gracefully fallback. 98 | // ignore: avoid_catches_without_on_clauses 99 | } catch (_) { 100 | return _legacyFallback(); 101 | } 102 | 103 | const unknownErrorMessage = 'An unknown error occurred.'; 104 | 105 | try { 106 | if (result == nullptr) { 107 | throw const UpdateException( 108 | reason: UpdateFailureReason.unknown, 109 | message: unknownErrorMessage, 110 | ); 111 | } 112 | 113 | final status = result.ref.status; 114 | 115 | if (status == SHOREBIRD_UPDATE_INSTALLED) return; 116 | 117 | final reason = status.toFailureReason(); 118 | final message = result.ref.message != nullptr 119 | ? result.ref.message.cast().toDartString() 120 | : unknownErrorMessage; 121 | throw UpdateException(message: message, reason: reason); 122 | } finally { 123 | _updater.freeUpdateResult(result); 124 | } 125 | } 126 | 127 | // Fallback to downloadUpdate if update is not available. 128 | Future _legacyFallback() async { 129 | await _run(_updater.downloadUpdate); 130 | final (current, next) = await (readCurrentPatch(), readNextPatch()).wait; 131 | final status = next != null && current?.number != next.number 132 | ? UpdateStatus.restartRequired 133 | : UpdateStatus.upToDate; 134 | if (status == UpdateStatus.restartRequired) return; 135 | throw const UpdateException( 136 | message: ''' 137 | Downloading update failed but reason is unknown due to legacy updater. 138 | Please upgrade the Shorebird Engine for improved error messages.''', 139 | reason: UpdateFailureReason.unknown, 140 | ); 141 | } 142 | } 143 | 144 | extension on int { 145 | UpdateFailureReason toFailureReason() { 146 | switch (this) { 147 | case SHOREBIRD_NO_UPDATE: 148 | return UpdateFailureReason.noUpdate; 149 | case SHOREBIRD_UPDATE_HAD_ERROR: 150 | return UpdateFailureReason.downloadFailed; 151 | case SHOREBIRD_UPDATE_IS_BAD_PATCH: 152 | return UpdateFailureReason.installFailed; 153 | case SHOREBIRD_UPDATE_ERROR: 154 | return UpdateFailureReason.unknown; 155 | default: 156 | return UpdateFailureReason.unknown; 157 | } 158 | } 159 | } 160 | -------------------------------------------------------------------------------- /shorebird_code_push/lib/src/shorebird_updater_web.dart: -------------------------------------------------------------------------------- 1 | import 'package:shorebird_code_push/src/shorebird_updater.dart'; 2 | 3 | /// {@template shorebird_updater_web} 4 | /// The Shorebird web updater. 5 | /// {@endtemplate} 6 | class ShorebirdUpdaterImpl implements ShorebirdUpdater { 7 | /// {@macro shorebird_updater_web} 8 | ShorebirdUpdaterImpl() { 9 | logShorebirdEngineUnavailableMessage(); 10 | } 11 | 12 | @override 13 | bool get isAvailable => false; 14 | 15 | @override 16 | Future readCurrentPatch() async => null; 17 | 18 | @override 19 | Future readNextPatch() async => null; 20 | 21 | @override 22 | Future checkForUpdate({UpdateTrack? track}) async => 23 | UpdateStatus.unavailable; 24 | 25 | @override 26 | Future update({UpdateTrack? track}) async {} 27 | } 28 | -------------------------------------------------------------------------------- /shorebird_code_push/lib/src/updater.dart: -------------------------------------------------------------------------------- 1 | import 'dart:ffi' as ffi; 2 | import 'dart:ffi'; 3 | 4 | import 'package:ffi/ffi.dart'; 5 | import 'package:meta/meta.dart'; 6 | import 'package:shorebird_code_push/src/generated/updater_bindings.g.dart'; 7 | import 'package:shorebird_code_push/src/shorebird_updater.dart'; 8 | 9 | /// {@template updater} 10 | /// A wrapper around the generated [UpdaterBindings] that, when necessary, 11 | /// translates ffi types into easier to use Dart types. 12 | /// {@endtemplate} 13 | class Updater { 14 | /// {@macro updater} 15 | const Updater(); 16 | 17 | /// The ffi bindings to the Updater library. 18 | @visibleForTesting 19 | static UpdaterBindings bindings = 20 | UpdaterBindings(ffi.DynamicLibrary.process()); 21 | 22 | /// The currently active patch number. 23 | int currentPatchNumber() => bindings.shorebird_current_boot_patch_number(); 24 | 25 | /// The next patch number that will be loaded. Will be the same as 26 | /// currentPatchNumber if no new patch is available. 27 | int nextPatchNumber() => bindings.shorebird_next_boot_patch_number(); 28 | 29 | /// Downloads the latest patch, if available. 30 | void downloadUpdate() => bindings.shorebird_update(); 31 | 32 | // New Methods added to support v2.0.0 of the Dart APIs // 33 | 34 | /// Whether a new patch is available for download. 35 | bool checkForDownloadableUpdate({UpdateTrack? track}) => 36 | bindings.shorebird_check_for_downloadable_update( 37 | track == null ? ffi.nullptr : track.name.toNativeUtf8().cast(), 38 | ); 39 | 40 | /// Downloads the latest patch, if available and returns an [UpdateResult] 41 | /// to indicate whether the update was successful. 42 | Pointer update({UpdateTrack? track}) => 43 | bindings.shorebird_update_with_result( 44 | track == null ? ffi.nullptr : track.name.toNativeUtf8().cast(), 45 | ); 46 | 47 | /// Frees an update result allocated by the updater. 48 | void freeUpdateResult(Pointer ptr) => 49 | bindings.shorebird_free_update_result(ptr); 50 | } 51 | -------------------------------------------------------------------------------- /shorebird_code_push/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: shorebird_code_push 2 | description: Check for and download Shorebird code push updates from your app. 3 | version: 2.0.4 4 | homepage: https://shorebird.dev 5 | repository: https://github.com/shorebirdtech/updater/tree/main/shorebird_code_push 6 | 7 | environment: 8 | sdk: ">=3.5.4 <4.0.0" 9 | flutter: ">=3.24.5" 10 | 11 | dependencies: 12 | ffi: ^2.0.2 13 | meta: ^1.9.1 14 | 15 | dev_dependencies: 16 | ffigen: ">=8.0.2 <17.0.0" 17 | mocktail: ^1.0.0 18 | test: ^1.19.2 19 | very_good_analysis: ^7.0.0 20 | 21 | ffigen: 22 | output: "lib/src/generated/updater_bindings.g.dart" 23 | name: "UpdaterBindings" 24 | headers: 25 | entry-points: 26 | - "../library/include/updater.h" 27 | preamble: | 28 | // ignore_for_file: unused_element, unused_field 29 | -------------------------------------------------------------------------------- /shorebird_code_push/test/override_print.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | void Function() overridePrint(void Function(List logs) fn) { 4 | return () { 5 | final printLogs = []; 6 | final spec = ZoneSpecification( 7 | print: (_, __, ___, String msg) { 8 | printLogs.add(msg); 9 | }, 10 | ); 11 | 12 | return Zone.current 13 | .fork(specification: spec) 14 | .run(() => fn(printLogs)); 15 | }; 16 | } 17 | -------------------------------------------------------------------------------- /shorebird_code_push/test/src/shorebird_updater_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:shorebird_code_push/shorebird_code_push.dart'; 2 | import 'package:test/test.dart'; 3 | 4 | import '../override_print.dart'; 5 | 6 | void main() { 7 | group(ShorebirdUpdater, () { 8 | test( 9 | 'can be instantiated', 10 | overridePrint((_) { 11 | expect(ShorebirdUpdater.new, returnsNormally); 12 | }), 13 | ); 14 | 15 | group(UpdateException, () { 16 | test('overrides toString', () { 17 | const message = 'message'; 18 | const reason = UpdateFailureReason.downloadFailed; 19 | const exception = UpdateException(message: message, reason: reason); 20 | expect( 21 | exception.toString(), 22 | equals( 23 | '[ShorebirdUpdater] UpdateException: $message (${reason.name})', 24 | ), 25 | ); 26 | }); 27 | }); 28 | 29 | group(ReadPatchException, () { 30 | test('overrides toString', () { 31 | const message = 'message'; 32 | const exception = ReadPatchException(message: message); 33 | expect( 34 | exception.toString(), 35 | equals('[ShorebirdUpdater] ReadPatchException: $message'), 36 | ); 37 | }); 38 | }); 39 | }); 40 | } 41 | -------------------------------------------------------------------------------- /shorebird_code_push/test/src/shorebird_updater_web_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:shorebird_code_push/src/shorebird_updater.dart'; 2 | import 'package:shorebird_code_push/src/shorebird_updater_web.dart'; 3 | import 'package:test/test.dart'; 4 | 5 | import '../override_print.dart'; 6 | 7 | void main() { 8 | group(ShorebirdUpdaterImpl, () { 9 | late ShorebirdUpdaterImpl shorebirdUpdater; 10 | 11 | test( 12 | 'logs unavailable error', 13 | overridePrint((logs) { 14 | shorebirdUpdater = ShorebirdUpdaterImpl(); 15 | expect( 16 | logs, 17 | contains( 18 | isA().having( 19 | (s) => s, 20 | 'message', 21 | contains( 22 | '''The Shorebird Updater is unavailable in the current environment.''', 23 | ), 24 | ), 25 | ), 26 | ); 27 | }), 28 | ); 29 | 30 | group('isAvailable', () { 31 | test( 32 | 'returns false', 33 | overridePrint((_) { 34 | shorebirdUpdater = ShorebirdUpdaterImpl(); 35 | expect(shorebirdUpdater.isAvailable, isFalse); 36 | }), 37 | ); 38 | }); 39 | 40 | group('readPatch', () { 41 | test( 42 | 'returns null', 43 | overridePrint((_) async { 44 | await expectLater( 45 | shorebirdUpdater.readCurrentPatch(), 46 | completion(isNull), 47 | ); 48 | await expectLater( 49 | shorebirdUpdater.readNextPatch(), 50 | completion(isNull), 51 | ); 52 | }), 53 | ); 54 | }); 55 | 56 | group('checkForUpdate', () { 57 | test( 58 | 'returns UpdateStatus.unavailable', 59 | overridePrint((_) async { 60 | await expectLater( 61 | shorebirdUpdater.checkForUpdate(), 62 | completion(equals(UpdateStatus.unavailable)), 63 | ); 64 | }), 65 | ); 66 | }); 67 | 68 | group('update', () { 69 | test( 70 | 'does nothing', 71 | overridePrint((_) async { 72 | await expectLater(shorebirdUpdater.update(), completes); 73 | }), 74 | ); 75 | }); 76 | }); 77 | } 78 | -------------------------------------------------------------------------------- /shorebird_code_push/test/src/updater_test.dart: -------------------------------------------------------------------------------- 1 | import 'dart:ffi'; 2 | 3 | import 'package:ffi/ffi.dart'; 4 | import 'package:mocktail/mocktail.dart'; 5 | import 'package:shorebird_code_push/shorebird_code_push.dart'; 6 | import 'package:shorebird_code_push/src/generated/updater_bindings.g.dart'; 7 | import 'package:shorebird_code_push/src/updater.dart'; 8 | import 'package:test/test.dart'; 9 | 10 | class _MockUpdaterBindings extends Mock implements UpdaterBindings {} 11 | 12 | void main() { 13 | group(Updater, () { 14 | late UpdaterBindings updaterBindings; 15 | late Updater updater; 16 | 17 | setUpAll(() { 18 | registerFallbackValue(Pointer.fromAddress(0)); 19 | }); 20 | 21 | setUp(() { 22 | updaterBindings = _MockUpdaterBindings(); 23 | 24 | updater = const Updater(); 25 | Updater.bindings = updaterBindings; 26 | }); 27 | 28 | test('initializes from currently loaded library', () { 29 | expect(updater, isNotNull); 30 | }); 31 | 32 | group('currentPatchNumber', () { 33 | test('forwards the result of shorebird_next_boot_patch_number', () { 34 | when( 35 | () => updaterBindings.shorebird_current_boot_patch_number(), 36 | ).thenReturn(123); 37 | final currentPatchNumber = updater.currentPatchNumber(); 38 | expect(currentPatchNumber, 123); 39 | }); 40 | }); 41 | 42 | group('checkForDownloadableUpdate', () { 43 | test('forwards the result of shorebird_check_for_update', () { 44 | when( 45 | () => updaterBindings.shorebird_check_for_downloadable_update( 46 | nullptr, 47 | ), 48 | ).thenReturn(true); 49 | expect(updater.checkForDownloadableUpdate(), isTrue); 50 | 51 | when( 52 | () => updaterBindings.shorebird_check_for_downloadable_update( 53 | nullptr, 54 | ), 55 | ).thenReturn(false); 56 | expect(updater.checkForDownloadableUpdate(), isFalse); 57 | }); 58 | 59 | group('when a track is provided', () { 60 | setUp(() { 61 | when( 62 | () => updaterBindings.shorebird_check_for_downloadable_update( 63 | any(), 64 | ), 65 | ).thenReturn(true); 66 | }); 67 | 68 | test('forwards the result of shorebird_check_for_update', () { 69 | expect( 70 | updater.checkForDownloadableUpdate(track: UpdateTrack.beta), 71 | isTrue, 72 | ); 73 | 74 | expect( 75 | updater.checkForDownloadableUpdate(track: UpdateTrack.stable), 76 | isTrue, 77 | ); 78 | 79 | final captured = verify( 80 | () => updaterBindings.shorebird_check_for_downloadable_update( 81 | captureAny(), 82 | ), 83 | ).captured; 84 | expect( 85 | captured.map( 86 | (cstr) => (cstr as Pointer).cast().toDartString(), 87 | ), 88 | equals(['beta', 'stable']), 89 | ); 90 | }); 91 | }); 92 | }); 93 | 94 | group('nextPatchNumber', () { 95 | test('forwards the result of shorebird_next_boot_patch_number', () { 96 | when( 97 | () => updaterBindings.shorebird_next_boot_patch_number(), 98 | ).thenReturn(123); 99 | final currentPatchNumber = updater.nextPatchNumber(); 100 | expect(currentPatchNumber, 123); 101 | }); 102 | }); 103 | 104 | group('downloadUpdate', () { 105 | test('calls bindings.shorebird_update', () { 106 | when(() => updaterBindings.shorebird_update()).thenReturn(null); 107 | updater.downloadUpdate(); 108 | verify(() => updaterBindings.shorebird_update()).called(1); 109 | }); 110 | }); 111 | 112 | group('update', () { 113 | test('calls bindings.shorebird_update_with_result', () { 114 | when( 115 | () => updaterBindings.shorebird_update_with_result(nullptr), 116 | ).thenReturn(nullptr); 117 | updater.update(); 118 | verify( 119 | () => updaterBindings.shorebird_update_with_result(nullptr), 120 | ).called(1); 121 | }); 122 | }); 123 | 124 | group('freeUpdateResult', () { 125 | test('calls bindings.shorebird_free_update_result', () { 126 | final result = calloc.allocate(sizeOf()); 127 | updater.freeUpdateResult(result); 128 | verify( 129 | () => updaterBindings.shorebird_free_update_result(any()), 130 | ).called(1); 131 | }); 132 | }); 133 | }); 134 | } 135 | --------------------------------------------------------------------------------