diff --git a/.github/workflows/desktop_macos_make.yml b/.github/workflows/desktop_macos_make.yml deleted file mode 100644 index dd4618444..000000000 --- a/.github/workflows/desktop_macos_make.yml +++ /dev/null @@ -1,33 +0,0 @@ -name: Desktop package apps -on: workflow_dispatch - -jobs: - package: - name: Package desktop apps - runs-on: ${{ matrix.os }} - strategy: - matrix: - os: [ubuntu-latest, macos-latest, windows-latest] - steps: - - uses: actions/checkout@v4 - - - name: Setup - uses: ./.github/actions/setup - - - name: Package app(macOS) - if: runner.os == 'macOS' - run: ./gradlew copyBrandingToCommonResources packageDmg -Porganization=ooni - - - name: Package Exe(Windows) - if: runner.os == 'Windows' - run: ./gradlew copyBrandingToCommonResources packageExe -Porganization=ooni - - - name: Package Exe(Linux) - if: runner.os == 'Linux' - run: ./gradlew copyBrandingToCommonResources packageDeb packageAppImage -Porganization=ooni - - - name: Upload artifacts - uses: actions/upload-artifact@v4 - with: - name: desktopApps - path: composeApp/build/compose/binaries diff --git a/.github/workflows/desktop_make.yml b/.github/workflows/desktop_make.yml new file mode 100644 index 000000000..cbd3faa27 --- /dev/null +++ b/.github/workflows/desktop_make.yml @@ -0,0 +1,128 @@ +name: Desktop package apps +on: + workflow_dispatch: + push: + +concurrency: + group: desktop-packaging-${{ github.ref }} + cancel-in-progress: false + +jobs: + package-macos: + name: Package macOS app + runs-on: macos-latest + env: + ORGANIZATION: ooni + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup (repo action) + uses: ./.github/actions/setup + + - name: Cache Gradle + uses: actions/cache@v4 + with: + path: | + ~/.gradle/caches + ~/.gradle/wrapper + key: ${{ runner.os }}-gradle-${{ hashFiles('gradle/wrapper/gradle-wrapper.properties') }}-${{ hashFiles('**/gradle.properties','**/*.gradle','**/*.gradle.kts') }} + restore-keys: | + ${{ runner.os }}-gradle- + + - name: Setup signing and notarization + run: | + echo "Details are not clear" # Placeholder for actual setup steps + + - name: Package DMG + run: ./gradlew copyBrandingToCommonResources packageDmg -Porganization=${{ env.ORGANIZATION }} + + - name: Sparkle appcast generation + run: ./gradlew generateSparkleAppCast -Porganization=${{ env.ORGANIZATION }} + + - name: Upload artifacts + uses: actions/upload-artifact@v4 + with: + name: desktopApps-macos-${{ github.run_id }} + path: | + composeApp/build/compose/binaries/main/dmg/OONI Probe-*.dmg + composeApp/build/compose/binaries/main/app/OONI Probe.app + composeApp/macos-appcast.xml + retention-days: 7 + + package-windows: + name: Package Windows app + runs-on: windows-latest + env: + ORGANIZATION: ooni + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup (repo action) + uses: ./.github/actions/setup + + - name: Cache Gradle + uses: actions/cache@v4 + with: + path: | + %USERPROFILE%\\.gradle\\caches + %USERPROFILE%\\.gradle\\wrapper + key: ${{ runner.os }}-gradle-${{ hashFiles('gradle/wrapper/gradle-wrapper.properties') }}-${{ hashFiles('**/gradle.properties','**/*.gradle','**/*.gradle.kts') }} + restore-keys: | + ${{ runner.os }}-gradle- + + - name: Package Exe + shell: pwsh + run: .\gradlew.bat copyBrandingToCommonResources packageExe -Porganization=${{ env.ORGANIZATION }} + + - name: Upload artifacts + uses: actions/upload-artifact@v4 + with: + name: desktopApps-windows-${{ github.run_id }} + path: | + composeApp/build/compose/binaries/main/exe/OONI Probe-*.exe + retention-days: 7 + + package-linux: + name: Package Linux app + runs-on: ubuntu-latest + env: + ORGANIZATION: ooni + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup (repo action) + uses: ./.github/actions/setup + + - name: Cache Gradle + uses: actions/cache@v4 + with: + path: | + ~/.gradle/caches + ~/.gradle/wrapper + key: ${{ runner.os }}-gradle-${{ hashFiles('gradle/wrapper/gradle-wrapper.properties') }}-${{ hashFiles('**/gradle.properties','**/*.gradle','**/*.gradle.kts') }} + restore-keys: | + ${{ runner.os }}-gradle- + + - name: Package Deb + run: ./gradlew copyBrandingToCommonResources packageDeb -Porganization=${{ env.ORGANIZATION }} + + - name: Install libfuse2 + run: | + sudo add-apt-repository universe + sudo apt-get update + sudo apt-get install -y libfuse2 + + - name: Package AppImage + run: ./gradlew copyBrandingToCommonResources packageAppImage -Porganization=${{ env.ORGANIZATION }} + + - name: Upload artifacts + uses: actions/upload-artifact@v4 + with: + name: desktopApps-linux-${{ github.run_id }} + path: | + composeApp/build/compose/binaries/main/deb/ooni-probe_*_amd64.deb + composeApp/build/compose/binaries/main/appimage-workspace/OONI-Probe-*-x86_64.AppImage + retention-days: 7 diff --git a/.gitignore b/.gitignore index 93656fe16..6fc08ccd4 100644 --- a/.gitignore +++ b/.gitignore @@ -25,5 +25,6 @@ output composeApp/src/desktopMain/resources/windows/WinSparkle.* /composeApp/src/desktopMain/frameworks/ +composeApp/macos-appcast.xml certificates/*.pem diff --git a/buildSrc/src/main/kotlin/TaskRegistration.kt b/buildSrc/src/main/kotlin/TaskRegistration.kt index 3f44a157c..9e9817d66 100644 --- a/buildSrc/src/main/kotlin/TaskRegistration.kt +++ b/buildSrc/src/main/kotlin/TaskRegistration.kt @@ -1,13 +1,13 @@ +import ooni.appimage.PackageAppImageTask +import ooni.sparkle.GenerateSparkleAppCastTask +import ooni.sparkle.SetupSparkleTask +import ooni.sparkle.WinSparkleSetupTask import org.gradle.api.Project import org.gradle.api.tasks.Exec import org.gradle.api.tasks.JavaExec import org.gradle.kotlin.dsl.register import org.gradle.kotlin.dsl.withType import java.io.File -import kotlin.let -import ooni.sparkle.SetupSparkleTask -import ooni.sparkle.WinSparkleSetupTask -import ooni.appimage.PackageAppImageTask private fun isMac() = System.getProperty("os.name").lowercase().contains("mac") @@ -79,6 +79,19 @@ private fun Project.registerSparkleTask() { .orElse(layout.buildDirectory.dir("processedResources/desktop/main/macos/")), ) } + + tasks.register("generateSparkleAppCast", GenerateSparkleAppCastTask::class) { + group = "setup" + description = "Generates Sparkle appcast using the specified DMG file." + onlyIf { isMac() } + dependsOn("setupSparkle") + + sparkleVersion.set(providers.gradleProperty("sparkleVersion").orElse("2.8.0")) + edKeyFile.set(rootProject.file("certificates/sparkle_eddsa_private.pem")) + appCastFile.set("macos-appcast.xml") + downloadUrlPrefix.set("https://distribution.ooni.org") + + } } private fun Project.registerWinSparkleTask() { diff --git a/buildSrc/src/main/kotlin/ooni/sparkle/GenerateSparkleAppCastTask.kt b/buildSrc/src/main/kotlin/ooni/sparkle/GenerateSparkleAppCastTask.kt new file mode 100644 index 000000000..18f9f0647 --- /dev/null +++ b/buildSrc/src/main/kotlin/ooni/sparkle/GenerateSparkleAppCastTask.kt @@ -0,0 +1,53 @@ +package ooni.sparkle + +import org.gradle.api.DefaultTask +import org.gradle.api.file.RegularFileProperty +import org.gradle.api.provider.Property +import org.gradle.api.tasks.Input +import org.gradle.api.tasks.InputFile +import org.gradle.api.tasks.TaskAction +import org.gradle.process.ExecOperations +import javax.inject.Inject + +abstract class GenerateSparkleAppCastTask : DefaultTask() { + @get:Inject + abstract val execOperations: ExecOperations + + @get:Input + abstract val sparkleVersion: Property + + @get:InputFile + abstract val edKeyFile: RegularFileProperty + + @get:Input + abstract val appCastFile: Property + + @get:Input + abstract val downloadUrlPrefix: Property + + @TaskAction + fun run() { + val sparkleTool = sparkleVersion + .map { project.layout.buildDirectory.file("sparkle/extracted-$it/bin/generate_appcast") } + .get() + + val dmgFile = project.file("build/compose/binaries/main/dmg") + + val command = listOf( + sparkleTool.get().asFile.absolutePath, + "-o", + appCastFile.get(), + "--ed-key-file", + edKeyFile.get().asFile.absolutePath, + "--download-url-prefix", + downloadUrlPrefix.get(), + dmgFile.absolutePath + ) + + project.logger.lifecycle("Running Sparkle generate_appcast with command: ${command.joinToString(" ")}") + + execOperations.exec { + commandLine(command) + }.assertNormalExitValue() + } +} diff --git a/docs/Release.md b/docs/Release.md index 6d5525fc8..c28a5b0f2 100644 --- a/docs/Release.md +++ b/docs/Release.md @@ -135,8 +135,8 @@ successfully and download the generated apps (zipped artifact). ##### 2.7.2 Sign windows app -We need to sign both the windows `.exe` and `.msix` files using our Extended Validation certificate. -Follow the steps on our internal process to do so. +- We need to sign the windows `.exe` file using our Extended Validation certificate. Follow the steps on our internal process to do so. +- Generate the WinSparkle appcast for the signed `.exe` file. #### 2.8 Create Release @@ -146,8 +146,7 @@ based on the new tag. **2.8.2** Write our manual release notes and add at the bottom the automatic changelog using the `Generate release notes` button. -**2.8.3** Upload all the desktop files downloaded during step *2.7.1*, except the `download.html` -file, and swapping the windows `.exe` and `.msix` files for their signed versions (step *2.7.2*). +**2.8.3** Upload all the desktop files downloaded during step *2.7.1*, and swapping the windows `.exe` files for their signed versions (step *2.7.2*). **2.8.4** Publish release