diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index b0dd56f2e..06381ede2 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -73,10 +73,9 @@ jobs: CHANGE_NOW: ${{ secrets.CHANGE_NOW }} run: echo "$CHANGE_NOW" | base64 --decode > lib/external_api_keys.dart - - name: Generate app config - run: dart run build_runner build --delete-conflicting-outputs - - name: Build + env: + USE_SYSTEM_SECURE_STORAGE_DEPS: "1" run: flutter build linux --release - name: Package @@ -145,9 +144,6 @@ jobs: CHANGE_NOW: ${{ secrets.CHANGE_NOW }} run: echo "$CHANGE_NOW" | base64 --decode > lib/external_api_keys.dart - - name: Generate app config - run: dart run build_runner build --delete-conflicting-outputs - - name: Set up Android local.properties run: | cat > android/local.properties < lib/external_api_keys.dart - - name: Generate app config - run: dart run build_runner build --delete-conflicting-outputs - - name: Build run: flutter build windows --release @@ -321,9 +314,6 @@ jobs: CHANGE_NOW: ${{ secrets.CHANGE_NOW }} run: echo "$CHANGE_NOW" | base64 --decode > lib/external_api_keys.dart - - name: Generate app config - run: dart run build_runner build --delete-conflicting-outputs - - name: Build run: flutter build macos --release @@ -399,9 +389,6 @@ jobs: CHANGE_NOW: ${{ secrets.CHANGE_NOW }} run: echo "$CHANGE_NOW" | base64 --decode > lib/external_api_keys.dart - - name: Generate app config - run: dart run build_runner build --delete-conflicting-outputs - - name: Build run: flutter build ios --release --no-codesign diff --git a/.github/workflows/release-mwebd-windows.yaml b/.github/workflows/release-mwebd-windows.yaml new file mode 100644 index 000000000..c826ac878 --- /dev/null +++ b/.github/workflows/release-mwebd-windows.yaml @@ -0,0 +1,50 @@ +name: Release mwebd Windows binary + +on: + workflow_dispatch: + inputs: + mwebd_version: + description: 'mwebd tag to build (must match _mwebdVersion in tool/build_standalone_mwebd_windows.dart)' + required: true + default: 'v0.1.8' + +permissions: + contents: write + +jobs: + build-and-release: + runs-on: windows-latest + defaults: + run: + shell: bash + steps: + - uses: actions/setup-go@v5 + with: + go-version: '1.22' + + - name: Clone mwebd + run: git clone https://github.com/ltcmweb/mwebd.git --branch "${{ inputs.mwebd_version }}" mwebd + + - name: Build mwebd.exe + working-directory: mwebd + env: + CGO_ENABLED: '1' + run: go build -v -o ../mwebd.exe github.com/ltcmweb/mwebd/cmd/mwebd + + - name: Compute sha256 + run: sha256sum mwebd.exe | awk '{print $1}' > mwebd.exe.sha256 + + - name: Publish release + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GH_REPO: ${{ github.repository }} + run: | + TAG="mwebd-${{ inputs.mwebd_version }}" + if gh release view "$TAG" >/dev/null 2>&1; then + gh release upload "$TAG" mwebd.exe mwebd.exe.sha256 --clobber + else + gh release create "$TAG" \ + --title "mwebd ${{ inputs.mwebd_version }} (windows-amd64)" \ + --notes "Pre-built Windows binary for ltcmweb/mwebd ${{ inputs.mwebd_version }}, built with native Go on windows-latest. Used by the Stack Wallet Windows build via tool/build_standalone_mwebd_windows.dart --fetch." \ + mwebd.exe mwebd.exe.sha256 + fi diff --git a/crypto_plugins/frostdart b/crypto_plugins/frostdart index 7a19f7dff..38fd03cb5 160000 --- a/crypto_plugins/frostdart +++ b/crypto_plugins/frostdart @@ -1 +1 @@ -Subproject commit 7a19f7dff54d222b191bdbe10d1e3e873bf6ed82 +Subproject commit 38fd03cb57e16baf2b3d2ce1743f7a745a6416c3 diff --git a/scripts/app_config/configure_stack_wallet.sh b/scripts/app_config/configure_stack_wallet.sh index 3de44a6aa..c68de3f3e 100755 --- a/scripts/app_config/configure_stack_wallet.sh +++ b/scripts/app_config/configure_stack_wallet.sh @@ -53,7 +53,11 @@ dart "${APP_PROJECT_ROOT_DIR}/tool/gen_interfaces.dart" \ MWEBD_EXE_SHA256="" if [[ "$1" == "windows" ]]; then - dart "${APP_PROJECT_ROOT_DIR}/tool/build_standalone_mwebd_windows.dart" + if [[ "${MWEBD_FETCH:-0}" == "1" ]]; then + dart "${APP_PROJECT_ROOT_DIR}/tool/build_standalone_mwebd_windows.dart" --fetch + else + dart "${APP_PROJECT_ROOT_DIR}/tool/build_standalone_mwebd_windows.dart" + fi MWEBD_EXE_SHA256="$(sha256sum "${APP_PROJECT_ROOT_DIR}/assets/windows/mwebd.exe" | awk '{print $1}')" dart "${APP_PROJECT_ROOT_DIR}/tool/process_pubspec_deps.dart" \ "${PUBSPEC_FILE}" MWEBDEXE diff --git a/scripts/app_config/templates/android/app/build.gradle b/scripts/app_config/templates/android/app/build.gradle index 6a98be40f..8fee78399 100644 --- a/scripts/app_config/templates/android/app/build.gradle +++ b/scripts/app_config/templates/android/app/build.gradle @@ -45,9 +45,9 @@ android { coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:2.0.4") } - ndk { - abiFilters "x86_64","armeabi-v7a", "arm64-v8a" - } + // No ndk.abiFilters here: AGP rejects it alongside the abi splits set + // up by `flutter build apk --split-per-abi`. Flutter defaults to + // android-arm,android-arm64,android-x64 which is the same set we want. // externalNativeBuild { // cmake { diff --git a/scripts/app_config/templates/linux/CMakeLists.txt b/scripts/app_config/templates/linux/CMakeLists.txt index cd44acde4..675a23f25 100644 --- a/scripts/app_config/templates/linux/CMakeLists.txt +++ b/scripts/app_config/templates/linux/CMakeLists.txt @@ -53,24 +53,37 @@ endfunction() set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter") add_subdirectory(${FLUTTER_MANAGED_DIR}) -# build libjsoncpp and libsecret for flutter_secure_storage -set(ENV{PKG_CONFIG_PATH} "$ENV{PKG_CONFIG_PATH}:${CMAKE_CURRENT_SOURCE_DIR}/../scripts/linux/build/jsoncpp/build/pkg-config") -set(ENV{PKG_CONFIG_PATH} "$ENV{PKG_CONFIG_PATH}:${CMAKE_CURRENT_SOURCE_DIR}/../scripts/linux/pc") - -include_directories(${CMAKE_CURRENT_SOURCE_DIR}/../scripts/linux/build/jsoncpp/include) - -include_directories(${CMAKE_CURRENT_SOURCE_DIR}/../scripts/linux/build/libsecret) -include_directories(${CMAKE_CURRENT_SOURCE_DIR}/../scripts/linux/build/libsecret/_build) - -add_library(jsoncpp SHARED IMPORTED) -set_target_properties(jsoncpp PROPERTIES IMPORTED_LOCATION "${CMAKE_CURRENT_SOURCE_DIR}/../scripts/linux/build/jsoncpp/build/src/lib_json/libjsoncpp.so") -add_library(secret-1 SHARED IMPORTED) -set_target_properties(secret-1 PROPERTIES IMPORTED_LOCATION "${CMAKE_CURRENT_SOURCE_DIR}/../scripts/linux/build/libsecret/_build/libsecret/libsecret-1.so") - # System-level dependencies. find_package(PkgConfig REQUIRED) pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0) +# jsoncpp and libsecret for flutter_secure_storage. When +# USE_SYSTEM_SECURE_STORAGE_DEPS is set, link and bundle the system-installed +# copies; otherwise use the artifacts built by scripts/linux/build_secure_storage_deps.sh. +option(USE_SYSTEM_SECURE_STORAGE_DEPS "Link against system-installed jsoncpp and libsecret" OFF) +if(DEFINED ENV{USE_SYSTEM_SECURE_STORAGE_DEPS} AND "$ENV{USE_SYSTEM_SECURE_STORAGE_DEPS}" STREQUAL "1") + set(USE_SYSTEM_SECURE_STORAGE_DEPS ON) +endif() + +if(USE_SYSTEM_SECURE_STORAGE_DEPS) + pkg_check_modules(JSONCPP REQUIRED IMPORTED_TARGET jsoncpp) + pkg_check_modules(LIBSECRET REQUIRED IMPORTED_TARGET libsecret-1) + pkg_get_variable(JSONCPP_LIBDIR jsoncpp libdir) + pkg_get_variable(LIBSECRET_LIBDIR libsecret-1 libdir) +else() + set(ENV{PKG_CONFIG_PATH} "$ENV{PKG_CONFIG_PATH}:${CMAKE_CURRENT_SOURCE_DIR}/../scripts/linux/build/jsoncpp/build/pkg-config") + set(ENV{PKG_CONFIG_PATH} "$ENV{PKG_CONFIG_PATH}:${CMAKE_CURRENT_SOURCE_DIR}/../scripts/linux/pc") + + include_directories(${CMAKE_CURRENT_SOURCE_DIR}/../scripts/linux/build/jsoncpp/include) + include_directories(${CMAKE_CURRENT_SOURCE_DIR}/../scripts/linux/build/libsecret) + include_directories(${CMAKE_CURRENT_SOURCE_DIR}/../scripts/linux/build/libsecret/_build) + + add_library(jsoncpp SHARED IMPORTED) + set_target_properties(jsoncpp PROPERTIES IMPORTED_LOCATION "${CMAKE_CURRENT_SOURCE_DIR}/../scripts/linux/build/jsoncpp/build/src/lib_json/libjsoncpp.so") + add_library(secret-1 SHARED IMPORTED) + set_target_properties(secret-1 PROPERTIES IMPORTED_LOCATION "${CMAKE_CURRENT_SOURCE_DIR}/../scripts/linux/build/libsecret/_build/libsecret/libsecret-1.so") +endif() + add_definitions(-DAPPLICATION_ID="${APPLICATION_ID}") # Define the application target. To change its name, change BINARY_NAME above, @@ -91,8 +104,12 @@ apply_standard_settings(${BINARY_NAME}) target_link_libraries(${BINARY_NAME} PRIVATE -static-libgcc -static-libstdc++) target_link_libraries(${BINARY_NAME} PRIVATE flutter) target_link_libraries(${BINARY_NAME} PRIVATE PkgConfig::GTK) -target_link_libraries(${BINARY_NAME} PRIVATE jsoncpp) -target_link_libraries(${BINARY_NAME} PRIVATE secret-1) +if(USE_SYSTEM_SECURE_STORAGE_DEPS) + target_link_libraries(${BINARY_NAME} PRIVATE PkgConfig::JSONCPP PkgConfig::LIBSECRET) +else() + target_link_libraries(${BINARY_NAME} PRIVATE jsoncpp) + target_link_libraries(${BINARY_NAME} PRIVATE secret-1) +endif() # Run the Flutter tool portions of the build. This must not be removed. @@ -147,19 +164,28 @@ if(INCLUDE_MWC_SO) COMPONENT Runtime) endif() -install(FILES "${CMAKE_CURRENT_SOURCE_DIR}/../scripts/linux/build/jsoncpp/build/src/lib_json/libjsoncpp.so.1.7.4" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" - COMPONENT Runtime) -install(FILES "${CMAKE_CURRENT_SOURCE_DIR}/../scripts/linux/build/jsoncpp/build/src/lib_json/libjsoncpp.so.1" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" - COMPONENT Runtime) -install(FILES "${CMAKE_CURRENT_SOURCE_DIR}/../scripts/linux/build/jsoncpp/build/src/lib_json/libjsoncpp.so" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" - COMPONENT Runtime) +if(USE_SYSTEM_SECURE_STORAGE_DEPS) + file(GLOB JSONCPP_SO_FILES "${JSONCPP_LIBDIR}/libjsoncpp.so*") + install(FILES ${JSONCPP_SO_FILES} DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) + file(GLOB LIBSECRET_SO_FILES "${LIBSECRET_LIBDIR}/libsecret-1.so*") + install(FILES ${LIBSECRET_SO_FILES} DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) +else() + install(FILES "${CMAKE_CURRENT_SOURCE_DIR}/../scripts/linux/build/jsoncpp/build/src/lib_json/libjsoncpp.so.1.7.4" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) + install(FILES "${CMAKE_CURRENT_SOURCE_DIR}/../scripts/linux/build/jsoncpp/build/src/lib_json/libjsoncpp.so.1" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) + install(FILES "${CMAKE_CURRENT_SOURCE_DIR}/../scripts/linux/build/jsoncpp/build/src/lib_json/libjsoncpp.so" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) -install(FILES "${CMAKE_CURRENT_SOURCE_DIR}/../scripts/linux/build/libsecret/_build/libsecret/libsecret-1.so.0.0.0" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" - COMPONENT Runtime) -install(FILES "${CMAKE_CURRENT_SOURCE_DIR}/../scripts/linux/build/libsecret/_build/libsecret/libsecret-1.so.0" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" - COMPONENT Runtime) -install(FILES "${CMAKE_CURRENT_SOURCE_DIR}/../scripts/linux/build/libsecret/_build/libsecret/libsecret-1.so" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" - COMPONENT Runtime) + install(FILES "${CMAKE_CURRENT_SOURCE_DIR}/../scripts/linux/build/libsecret/_build/libsecret/libsecret-1.so.0.0.0" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) + install(FILES "${CMAKE_CURRENT_SOURCE_DIR}/../scripts/linux/build/libsecret/_build/libsecret/libsecret-1.so.0" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) + install(FILES "${CMAKE_CURRENT_SOURCE_DIR}/../scripts/linux/build/libsecret/_build/libsecret/libsecret-1.so" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) +endif() foreach(bundled_library ${PLUGIN_BUNDLED_LIBRARIES}) install(FILES "${bundled_library}" diff --git a/tool/build_standalone_mwebd_windows.dart b/tool/build_standalone_mwebd_windows.dart index b956e4f5e..3f8c1785f 100644 --- a/tool/build_standalone_mwebd_windows.dart +++ b/tool/build_standalone_mwebd_windows.dart @@ -1,16 +1,68 @@ import 'dart:io'; -Future main() async { - final projectToolDir = File(() { - String path = Platform.script.path; - if (Platform.isWindows) { - while (!path.startsWith("C:")) { - path = path.substring(1); - } - } - return path; - }()).parent; +const _mwebdVersion = "v0.1.8"; +const _defaultFetchBaseUrl = + "https://github.com/cypherstack/stack_wallet/releases/download"; +Future main(List args) async { + final projectToolDir = File(Platform.script.toFilePath()).parent; + + if (args.contains("--fetch")) { + await _fetchPrebuilt(projectToolDir); + } else { + await _buildFromSource(projectToolDir); + } +} + +Future _fetchPrebuilt(Directory projectToolDir) async { + final baseUrl = + Platform.environment["MWEBD_FETCH_BASE_URL"] ?? _defaultFetchBaseUrl; + final tag = "mwebd-$_mwebdVersion"; + + final winAssetsDir = Directory( + "${projectToolDir.parent.path}" + "${Platform.pathSeparator}assets" + "${Platform.pathSeparator}windows", + ); + if (!(await winAssetsDir.exists())) { + await winAssetsDir.create(recursive: true); + } + final exePath = "${winAssetsDir.path}${Platform.pathSeparator}mwebd.exe"; + final shaPath = "$exePath.sha256"; + + await _waitForProcess( + await Process.start( + "curl", + ["-fL", "-o", exePath, "$baseUrl/$tag/mwebd.exe"], + runInShell: true, + mode: ProcessStartMode.inheritStdio, + ), + ); + await _waitForProcess( + await Process.start( + "curl", + ["-fL", "-o", shaPath, "$baseUrl/$tag/mwebd.exe.sha256"], + runInShell: true, + mode: ProcessStartMode.inheritStdio, + ), + ); + + final expected = (await File( + shaPath, + ).readAsString()).trim().split(RegExp(r"\s+")).first; + final actual = (await Process.run("sha256sum", [ + exePath, + ], runInShell: true)).stdout.toString().trim().split(RegExp(r"\s+")).first; + if (expected.toLowerCase() != actual.toLowerCase()) { + stderr.writeln( + "mwebd.exe sha256 mismatch: expected $expected, got $actual", + ); + exit(1); + } + await File(shaPath).delete(); +} + +Future _buildFromSource(Directory projectToolDir) async { // setup temp build dir final tempBuildDir = Directory( "${projectToolDir.path}" @@ -23,12 +75,17 @@ Future main() async { // change working dir and clone mwebd Directory.current = tempBuildDir; - final clone = await Process.start("git", [ - "clone", - "https://www.github.com/ltcmweb/mwebd.git", - "--branch", - "v0.1.8", - ], runInShell: true); + final clone = await Process.start( + "git", + [ + "clone", + "https://www.github.com/ltcmweb/mwebd.git", + "--branch", + _mwebdVersion, + ], + runInShell: true, + mode: ProcessStartMode.inheritStdio, + ); await _waitForProcess(clone); // change working dir and build mwebd.exe @@ -36,26 +93,56 @@ Future main() async { "${tempBuildDir.path}" "${Platform.pathSeparator}mwebd", ); - final wslBuild = Platform.isWindows - ? await Process.start("wsl", [ - "bash", - "-l", - "-c", - "GOOS=windows GOARCH=amd64 CGO_ENABLED=1 CC=x86_64-w64-mingw32-gcc " - "go build -o ../mwebd.exe github.com/ltcmweb/mwebd/cmd/mwebd", - ], runInShell: true) - : await Process.start( - "go", - ["build", "-o", "../mwebd.exe", "github.com/ltcmweb/mwebd/cmd/mwebd"], - environment: { - "GOOS": "windows", - "GOARCH": "amd64", - "CGO_ENABLED": "1", - "CC": "x86_64-w64-mingw32-gcc", - }, - runInShell: true, - ); - await _waitForProcess(wslBuild); + final isCI = Platform.environment['CI'] == 'true'; + final Process build; + if (Platform.isWindows && isCI) { + build = await Process.start( + "go", + [ + "build", + "-v", + "-o", + "../mwebd.exe", + "github.com/ltcmweb/mwebd/cmd/mwebd", + ], + environment: {"CGO_ENABLED": "1"}, + runInShell: true, + mode: ProcessStartMode.inheritStdio, + ); + } else if (Platform.isWindows) { + build = await Process.start( + "wsl", + [ + "bash", + "-l", + "-c", + "GOOS=windows GOARCH=amd64 CGO_ENABLED=1 CC=x86_64-w64-mingw32-gcc " + "go build -v -o ../mwebd.exe github.com/ltcmweb/mwebd/cmd/mwebd", + ], + runInShell: true, + mode: ProcessStartMode.inheritStdio, + ); + } else { + build = await Process.start( + "go", + [ + "build", + "-v", + "-o", + "../mwebd.exe", + "github.com/ltcmweb/mwebd/cmd/mwebd", + ], + environment: { + "GOOS": "windows", + "GOARCH": "amd64", + "CGO_ENABLED": "1", + "CC": "x86_64-w64-mingw32-gcc", + }, + runInShell: true, + mode: ProcessStartMode.inheritStdio, + ); + } + await _waitForProcess(build); // create assets/windows dir if needed final winAssetsDir = Directory( @@ -76,13 +163,13 @@ Future main() async { "${Platform.pathSeparator}mwebd.exe", "${winAssetsDir.path}" "${Platform.pathSeparator}mwebd.exe", - ]) + ], mode: ProcessStartMode.inheritStdio) : await Process.start("cp", [ "${Directory.current.parent.path}" "${Platform.pathSeparator}mwebd.exe", "${winAssetsDir.path}" "${Platform.pathSeparator}mwebd.exe", - ]); + ], mode: ProcessStartMode.inheritStdio); await _waitForProcess(copy); // cleanup