From c0690af75e568aa60e556d9da0f27b555f0334e4 Mon Sep 17 00:00:00 2001 From: map Date: Wed, 29 Jan 2025 10:50:59 +0100 Subject: [PATCH] run ci pipeline on jenkins --- .gitignore | 1 + README.md | 2 - ci/merge.groovy | 352 ++++++++++++++++++ eslint.config.mjs | 28 +- package.json | 4 +- .../calendar/view/EventDetailsView.ts | 2 +- 6 files changed, 377 insertions(+), 12 deletions(-) create mode 100644 ci/merge.groovy diff --git a/.gitignore b/.gitignore index d1cf152bd524..4ea539087301 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ build +cache target build-calendar-app dist diff --git a/README.md b/README.md index 23b3b2043e86..ac9657255389 100644 --- a/README.md +++ b/README.md @@ -30,5 +30,3 @@ See [BUILDING.md](doc/BUILDING.md). ### Developing Tuta Mail See [HACKING.md](doc/HACKING.md). - - diff --git a/ci/merge.groovy b/ci/merge.groovy new file mode 100644 index 000000000000..4a802bd8c7b2 --- /dev/null +++ b/ci/merge.groovy @@ -0,0 +1,352 @@ +import groovy.json.JsonSlurper + +def targetBranch = "master" +def targetMac = "mac-intel" + +HashSet changedPaths = new HashSet<>() + +pipeline { + options { + parallelsAlwaysFailFast() + } + environment { + VERSION = sh(returnStdout: true, script: "${env.NODE_PATH}/node -p -e \"require('./package.json').version\" | tr -d \"\n\"") + APK_SIGN_STORE = '/opt/android-keystore/android.jks' + PATH = "${env.NODE_PATH}:${env.PATH}:/home/jenkins/emsdk/upstream/bin/:/home/jenkins/emsdk/:/home/jenkins/emsdk/upstream/emscripten" + ANDROID_SDK_ROOT = "/opt/android-sdk-linux" + ANDROID_HOME = "/opt/android-sdk-linux" + RUSTFLAGS = "--cfg ci" + } + + agent { + label 'linux' + } + + tools { + jdk 'jdk-21.0.2' + } + + parameters { + string(name: 'BRANCH_NAME', defaultValue: 'test-jenkins-merge', description: 'Branch to merge to master') + } + + stages { + stage("git") { + parallel { + stage("git checkout mac") { + when { + expression { + return true + } + } + agent { + label targetMac + } + steps { + git( + url: "git@github.com:tutao/tutanota.git", + branch: params.BRANCH_NAME, + changelog: true, + poll: true + ) + } + } + + stage("git fetch and switch to branch") { + + steps { + git( + url: "git@github.com:tutao/tutanota.git", + branch: params.BRANCH_NAME, + changelog: true, + poll: true + ) + + } + } + } + } + stage("sync submodules") { + steps { + sh ''' + git submodule init + git submodule sync --recursive + git submodule update + ''' + script { + def successfulMasterFetch = sh(returnStatus: true, script: "git fetch origin master:master").toInteger() + if (successfulMasterFetch != 0) { + abortJob("Failed to fetch master. Please wipe jenkins workspace and try again.") + } + + def isOnTopOfTarget = sh(returnStatus: true, script: "git merge-base --is-ancestor ${targetBranch} ${params.BRANCH_NAME}").toInteger() + if (isOnTopOfTarget == 1) { + abortJob("branch ${params.BRANCH_NAME} is not rebased on top of ${targetBranch}. Please rebase and run again...") + } + initialize(changedPaths, targetBranch) + } + } + } + + stage("Source code checks") { + parallel { + stage("npm ci") { + when { + expression { + return shouldRunNpmCi() + } + } + steps { + sh 'npm ci' + } + } + stage("find FIXMEs") { + steps { + sh ''' + if grep "FIXME\\|[fF]ixme" -r src buildSrc test/tests packages/*/lib app-android/app/src app-ios/tutanota/Sources; then + echo 'FIXMEs in src'; + exit 1; + else + echo 'No FIXMEs in src'; + fi + ''' + } + + } + } + } + stage("Lint and Style") { + parallel { + stage("lint:check") { + steps { + sh 'npm run lint:check' + } + } + stage("style:check") { + steps { + sh 'npm run style:check' + } + } + stage("build-packages") { + steps { + sh 'npm run build-packages' + } + } + stage("prepare/lint mac") { + agent { + label targetMac + } + when { + expression { + return shouldRunOnMac(changedPaths) + } + } + steps { + sh ''' + mkdir -p ./build-calendar-app + mkdir -p ./build + + cd app-ios + #./lint.sh lint:check + ./lint.sh style:check + + # xcodegen is not installed on m1 + /usr/local/bin/xcodegen --spec calendar-project.yml + /usr/local/bin/xcodegen --spec mail-project.yml + + cd ../tuta-sdk/ios + /usr/local/bin/xcodegen + + ''' + } + } + + } + } + stage("Parallelize everything") { + parallel { + stage("packages test") { + when { + expression { + return runByDefault() + } + } + steps { + sh 'npm run --if-present test -ws' + } + } + stage("node tests") { + when { + expression { + return runByDefault() + } + } + steps { + sh 'cd test && node test' + } + } + stage("browser tests") { + when { + expression { + return runByDefault() + } + } + steps { + sh 'npm run test:app -- --no-run --browser --browser-cmd \'$(which chromium) --no-sandbox --enable-logging=stderr --headless=new --disable-gpu\'' + } + } + stage("build web app") { + when { + expression { + return runByDefault() + } + } + steps { + sh 'node webapp --disable-minify' + } + } + stage("build web app calendar") { + when { + expression { + return runByDefault() + } + } + steps { + sh 'node webapp --disable-minify --app calendar' + } + } + stage("mac mail tests") { + agent { + label targetMac + } + when { + expression { + return shouldRunOnMac(changedPaths) + } + } + environment { + LC_ALL = "en_US.UTF-8" + LANG = "en_US.UTF-8" + } + steps { + dir("app-ios") { + sh 'fastlane test_github' + } + } + } + stage("mac calendar tests") { + agent { + label targetMac + } + when { + expression { + return shouldRunOnMac(changedPaths) + } + } + environment { + LC_ALL = "en_US.UTF-8" + LANG = "en_US.UTF-8" + } + steps { + dir("app-ios") { + sh 'fastlane test_calendar_github' + } + } + } + stage("android tests") { +// when { +// expression { +// return runByDefault() +// } +// } + environment { + TZ = "Europe/Berlin" // We have some tests for same day alarms that depends on this TimeZone + } + steps { + sh ''' +mkdir -p build +mkdir -p build-calendar-app +cd app-android +./gradlew lint --quiet +./gradlew test +''' + } + } + } + } + } +} + +void abortJob(String errorMsg) { + print(errorMsg) + error(errorMsg) +} + +boolean shouldRunNpmCi() { + def current = readFile(file: 'package.json') + def old + try { + old = readFile(file: 'cache/package.json') + } catch (e) { + print e + return true + } finally { + writeFile(file: 'cache/package.json', text: current) + } + def json = new JsonSlurper() + def oldJson = json.parseText(old) + def currentJson = json.parseText(current) + def oldVersion = oldJson.version + def currentVersion = currentJson.version + def oldWithUpdatedVersion = old.replaceAll(oldVersion, currentVersion) + def expectedJSON = json.parseText(oldWithUpdatedVersion) + if (expectedJSON.equals(currentJson)) { + print "skipping npm ci as package.json is unchanged" + return false + } else { + return true + } +} + + +void initialize(HashSet changedPaths, String targetBranch) { + def out = sh(returnStdout: true, script: "git diff --name-status ${targetBranch}") + def lines = out.split('\n') + for (String line : lines) { + def split = line.split("\t") + // lines with moved files contain three parts + if (split.length > 3) { + abortJob("unexpected line: " + line) + } + changedPaths.add(split[split.length - 1].trim()) + } +} + +boolean hasChangeForPath(HashSet changedPaths, String pathPrefix, String ignoredSuffix) { + for (String p : changedPaths) { + if (p.startsWith(pathPrefix) && !p.endsWith(ignoredSuffix)) { + return true + } + } + return false +} + +boolean shouldRunOnMac(HashSet changedPaths) { + return hasChangeForPath(changedPaths, "app-ios", "Info.plist") + return true +} + +boolean runByDefault() { +// return true + return false +} + +//TODO +// why do we build web app and web app calendar? Is running tsc enough? -> willow? + +// swift and kotlin tests +// why does cargo compiles every run + + + + diff --git a/eslint.config.mjs b/eslint.config.mjs index cc0bf25cb18c..81a17bd72204 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -3,9 +3,9 @@ import unicorn from "eslint-plugin-unicorn" import globals from "globals" import tsParser from "@typescript-eslint/parser" import path from "node:path" -import { fileURLToPath } from "node:url" +import {fileURLToPath} from "node:url" import js from "@eslint/js" -import { FlatCompat } from "@eslint/eslintrc" +import {FlatCompat} from "@eslint/eslintrc" const __filename = fileURLToPath(import.meta.url) const __dirname = path.dirname(__filename) @@ -18,6 +18,25 @@ const compat = new FlatCompat({ export default [ { ignores: [ + "buildSrc/", + ".github/", + ".rollup.cache/", + ".run", + "app-android/", + "app-ios/", + "artifacts/", + "cache/", + "ci/", + "doc", + "fdroid-metadata-workaround/", + "githooks/", + "native-cache/", + "packages/node-mimimi/", + "packages/tutanota-crypto/lib/internal/", + "resources/", + "schemas/", + "tuta-sdk/", + "**/entities/", "**/translations/", "**/node_modules/", @@ -25,11 +44,6 @@ export default [ "**/build-calendar-app/", "**/dist/", "**/libs/", - "**/app-android/", - "**/app-ios/", - "packages/tutanota-crypto/lib/internal/", - "**/fdroid-metadata-workaround/", - "buildSrc/", ], }, ...compat.extends("eslint:recommended", "plugin:@typescript-eslint/recommended", "prettier"), diff --git a/package.json b/package.json index e7d8802eb77c..b040036b48fb 100644 --- a/package.json +++ b/package.json @@ -24,9 +24,9 @@ "postinstall": "node buildSrc/postinstall.js", "bump-version": "node buildSrc/bump-version.js", "generate-ipc": "npm run build -w @tutao/licc && licc ./ipc-schema", - "style:check": "prettier -c \"**/*.(ts|js|json|json5)\"", + "style:check": "prettier -c \"**/*.(ts|js|json|json5)\" --cache --cache-location cache/prettier", "style:fix": "prettier -w \"**/*.(ts|js|json|json5)\"", - "lint:check": "eslint .", + "lint:check": "eslint . --cache --cache-location cache/eslint", "lint:fix": "eslint --fix .", "check": "npm run style:check && npm run lint:check", "fix": "npm run style:fix && npm run lint:fix" diff --git a/src/calendar-app/calendar/view/EventDetailsView.ts b/src/calendar-app/calendar/view/EventDetailsView.ts index 1475cc484c32..5b15a14f4487 100644 --- a/src/calendar-app/calendar/view/EventDetailsView.ts +++ b/src/calendar-app/calendar/view/EventDetailsView.ts @@ -92,7 +92,7 @@ export function handleEventEditButtonClick(previewModel: CalendarEventPreviewVie label: "updateAllCalendarEvents_action", click: () => { // noinspection JSIgnoredPromiseFromCall - previewModel?.editAll() + previewModel?.editAll().catch() }, }, ]),