From 8539a63f48b65ef3124854585a20526741aa54ce Mon Sep 17 00:00:00 2001 From: Viktor De Pasquale Date: Wed, 18 Dec 2024 11:36:20 +0100 Subject: [PATCH] chore: Added CI Added workflows for: - Creating releases - Running checks - Publishing the app - Translating resources - Performing health checks - Updating the Gradle wrapper Also added a Dependabot configuration file and a release-please configuration file. --- .github/actions/android/action.yml | 25 ++++++ .github/actions/translate/Dockerfile | 14 ++++ .github/actions/translate/action.yml | 12 +++ .github/actions/translate/main.py | 95 ++++++++++++++++++++++ .github/actions/translate/requirements.txt | 28 +++++++ .github/dependabot.yml | 10 +++ .github/workflows/checks.yml | 43 ++++------ .github/workflows/create-release.yml | 23 ++++++ .github/workflows/healthcheck.yml | 26 ++++++ .github/workflows/i18n.yml | 27 ++++++ .github/workflows/publish.yml | 9 +- .github/workflows/update-gradle.yml | 22 +++++ .release-please-manifest.json | 3 + release-please-config.json | 11 +++ 14 files changed, 315 insertions(+), 33 deletions(-) create mode 100644 .github/actions/android/action.yml create mode 100644 .github/actions/translate/Dockerfile create mode 100644 .github/actions/translate/action.yml create mode 100644 .github/actions/translate/main.py create mode 100644 .github/actions/translate/requirements.txt create mode 100644 .github/workflows/create-release.yml create mode 100644 .github/workflows/healthcheck.yml create mode 100644 .github/workflows/i18n.yml create mode 100644 .github/workflows/update-gradle.yml create mode 100644 .release-please-manifest.json create mode 100644 release-please-config.json diff --git a/.github/actions/android/action.yml b/.github/actions/android/action.yml new file mode 100644 index 00000000..110be0d6 --- /dev/null +++ b/.github/actions/android/action.yml @@ -0,0 +1,25 @@ +name: Android Action +description: Prepares Android runtime (ie. installs java and sets everything up) + +runs: + using: composite + steps: + - name: Set Java + uses: actions/setup-java@v4 + with: + java-version: 17 + distribution: temurin + + - name: Enable cache + uses: actions/cache@v4 + id: cache-android + with: + path: | + ~/.android + key: ${{ runner.os }}-android + + - name: Setup Android + uses: android-actions/setup-android@v3 + + - name: Setup Gradle + uses: gradle/actions/setup-gradle@v4 \ No newline at end of file diff --git a/.github/actions/translate/Dockerfile b/.github/actions/translate/Dockerfile new file mode 100644 index 00000000..32c4dd82 --- /dev/null +++ b/.github/actions/translate/Dockerfile @@ -0,0 +1,14 @@ +FROM python:3.11-slim AS builder +ADD . /app +WORKDIR /app + +# We are installing a dependency here directly into our app source dir +RUN pip install --target=/app -r requirements.txt + +# A distroless container image with Python and some basics like SSL certificates +# https://github.com/GoogleContainerTools/distroless +FROM gcr.io/distroless/python3-debian12 +COPY --from=builder /app /app +WORKDIR /app +ENV PYTHONPATH /app +CMD ["/app/main.py"] \ No newline at end of file diff --git a/.github/actions/translate/action.yml b/.github/actions/translate/action.yml new file mode 100644 index 00000000..068648e6 --- /dev/null +++ b/.github/actions/translate/action.yml @@ -0,0 +1,12 @@ +name: Translate Android Resources +description: Uses Gemini LLM to use default english resources and translates them to all languages +inputs: + geminiApiKey: + required: true + filePath: + required: true + description: "Path to the originating file without any affix. Preferably this would be an English file" + +runs: + using: "docker" + image: "Dockerfile" \ No newline at end of file diff --git a/.github/actions/translate/main.py b/.github/actions/translate/main.py new file mode 100644 index 00000000..720eea95 --- /dev/null +++ b/.github/actions/translate/main.py @@ -0,0 +1,95 @@ +import os +import pathlib +import re +from time import sleep + +import google.generativeai as genai + +# === STATIC === +QUOTA = 10 # per minute + + +# === STATIC === + +def set_github_action_output(output_name, output_value): + f = open(os.path.abspath(os.environ["GITHUB_OUTPUT"]), "a") + f.write(f'{output_name}={output_value}') + f.close() + + +def translate(lang: str, path: str, model): + segments = path.split("/") + resdir = segments[:-2] + values = segments[-2:] + resdir.append(f"{values[0]}-{lang}") + resdir.append(values[1]) + output = "/".join(resdir) + print(f"Preparing output file:\n{output}") + pathlib.Path(output).parent.mkdir(parents=True, exist_ok=True) + + with open(path) as f: + content = f.read() + + intro = f"""Use the following xml code block for android resources as a source for translation. I will request translating the source to various languages, I need output of a xml code block with the same keys as in the original in the specified languages without any explanation or any other such thing. You will need to prefix character `'` with a single `\\` if it is not prefixed already or replace `'` with `'`. Ensure that the first `xml` tag definition is identical to the source, including version and encoding. Optionally use a "context" attribute of a string resource to properly translate the resource to the desired language. Use the "context" property as a context where the translation is not straightforward and use it to find similar expression in the desired languages. Do not translate "context" property and do include the property in translated strings. + + ``` + {content} + ``` + """ + + print(f"Generating output… ", end="") + response = model.generate_content(f"{intro}\ntranslate to '{lang}'") + pattern = r"^```(?:\w+)?\s*\n(.*?)(?=^```)```" + block: str = re.findall(pattern, response.text, re.DOTALL | re.MULTILINE)[0] + + with open(output, "w") as f: + f.write(block.replace("'", "\\'")) + + print("DONE") + + +def find_strings_xml(directory): + matching_files = [] + + for root, dirs, files in os.walk(directory): + for file in files: + if file == "strings.xml": + file_path = os.path.join(root, file) + if file_path.endswith("values/strings.xml"): + matching_files.append(file_path) + + return matching_files + + +def main(): + print("Configuring Gemini… ", end="") + genai.configure(api_key=os.environ["INPUT_GEMINIAPIKEY"]) + model = genai.GenerativeModel("gemini-1.5-flash") + print("DONE") + + languages = ["af", "am", "ar", "as", "az", "be", "bg", "bn", "bs", "ca", "cs", "da", "de", "el", "es", "et", "eu", + "fa", + "fi", "fil", "fr", "gl", "gu", "hi", "hr", "hu", "hy", "in", "is", "it", "iw", "ja", "ka", "kk", "km", + "kn", "ko", "ky", "lo", "lt", "lv", "mk", "ml", "mn", "mr", "ms", "my", "nb", "ne", "nl", "or", "pa", + "pl", + "pt", "ro", "ru", "si", "sk", "sl", "sq", "sr", "sv", "sw", "ta", "te", "th", "tr", "uk", "ur", "uz", + "vi", + "zh", "zu"] + + root_path = os.environ["INPUT_PATH"] + result = find_strings_xml(root_path if root_path else ".") + + print(f"Found {len(result)} files named 'strings.xml':") + for path in result: + print(path) + + print(f"""Translation will be performed for {len(languages)} languages. + This process will take approximately {len(languages) / QUOTA} minutes""") + + for l in languages: + translate(l, path, model) + sleep(1 / QUOTA) + + +if __name__ == "__main__": + main() diff --git a/.github/actions/translate/requirements.txt b/.github/actions/translate/requirements.txt new file mode 100644 index 00000000..4f930554 --- /dev/null +++ b/.github/actions/translate/requirements.txt @@ -0,0 +1,28 @@ +annotated-types==0.7.0 +cachetools==5.5.0 +certifi==2024.12.14 +charset-normalizer==3.4.0 +google-ai-generativelanguage==0.6.10 +google-api-core==2.24.0 +google-api-python-client==2.155.0 +google-auth==2.37.0 +google-auth-httplib2==0.2.0 +google-generativeai==0.8.3 +googleapis-common-protos==1.66.0 +grpcio==1.68.1 +grpcio-status==1.68.1 +httplib2==0.22.0 +idna==3.10 +proto-plus==1.25.0 +protobuf==5.29.1 +pyasn1==0.6.1 +pyasn1_modules==0.4.1 +pydantic==2.10.3 +pydantic_core==2.27.1 +pyparsing==3.2.0 +requests==2.32.3 +rsa==4.9 +tqdm==4.67.1 +typing_extensions==4.12.2 +uritemplate==4.1.1 +urllib3==2.2.3 diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 10ef8311..bd6aa3a0 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -4,7 +4,17 @@ updates: directory: "/" schedule: interval: "weekly" + commit-message: + prefix: "chore" + include: "scope" + reviewers: + - "diareuse" - package-ecosystem: "github-actions" directory: "/" schedule: interval: "weekly" + commit-message: + prefix: "chore" + include: "scope" + reviewers: + - "diareuse" \ No newline at end of file diff --git a/.github/workflows/checks.yml b/.github/workflows/checks.yml index 2f04ece3..a9a94de7 100644 --- a/.github/workflows/checks.yml +++ b/.github/workflows/checks.yml @@ -7,41 +7,34 @@ on: jobs: test: runs-on: ubuntu-latest - permissions: checks: write pull-requests: write - steps: - uses: actions/checkout@v4 - - - run: | - chmod +x setup.sh - ./setup.sh - - - name: Set Java - uses: actions/setup-java@v4 - with: - java-version: 17 - distribution: temurin - - - name: Setup Android SDK - uses: android-actions/setup-android@v3 - - - name: Build with Gradle - uses: gradle/gradle-build-action@v3 + - uses: diareuse/movie-metropolis/.github/actions/android@master + - uses: gradle/gradle-build-action@v3 with: - arguments: test - - - name: Publish Test Results - uses: EnricoMi/publish-unit-test-result-action@v2 + arguments: check + - uses: dorny/test-reporter@v1 if: always() with: - junit_files: "**/test-results/**/*.xml" + name: Tests + path: "**/test-results/**/*.xml" + reporter: java-junit + fail-on-empty: false + builds: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: diareuse/movie-metropolis/.github/actions/android@master + - uses: gradle/gradle-build-action@v3 + with: + arguments: assemble - dependabot: + merge: runs-on: ubuntu-latest - needs: [ test ] + needs: [ check, builds ] permissions: pull-requests: write issues: write diff --git a/.github/workflows/create-release.yml b/.github/workflows/create-release.yml new file mode 100644 index 00000000..f4a0f863 --- /dev/null +++ b/.github/workflows/create-release.yml @@ -0,0 +1,23 @@ +name: Create Release +on: + push: + branches: + - master + +permissions: + contents: write + pull-requests: write + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + create-release: + runs-on: ubuntu-latest + steps: + - uses: googleapis/release-please-action@v4 + with: + token: ${{ github.token }} + config-file: release-please-config.json + manifest-file: .release-please-manifest.json \ No newline at end of file diff --git a/.github/workflows/healthcheck.yml b/.github/workflows/healthcheck.yml new file mode 100644 index 00000000..0f5f31f7 --- /dev/null +++ b/.github/workflows/healthcheck.yml @@ -0,0 +1,26 @@ +name: Health Check + +on: + workflow_dispatch: + push: + branches: + - master + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + perform-health-check: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: diareuse/movie-metropolis/.github/actions/android@master + - uses: gradle/actions/dependency-submission@v4 + with: + dependency-graph-exclude-projects: ':buildSrc', 'movie-metropolis' + dependency-graph-exclude-configurations: '.*[Tt]est(Compile|Runtime)Classpath' + dependency-resolution-task: assemble + build-scan-publish: true + build-scan-terms-of-use-url: "https://gradle.com/help/legal-terms-of-use" + build-scan-terms-of-use-agree: "yes" \ No newline at end of file diff --git a/.github/workflows/i18n.yml b/.github/workflows/i18n.yml new file mode 100644 index 00000000..d06b2a10 --- /dev/null +++ b/.github/workflows/i18n.yml @@ -0,0 +1,27 @@ +name: i18n + +on: + workflow_dispatch: + push: + branches: + - master + paths: + - '**/values/strings.xml' + +permissions: + contents: write + pull-requests: write + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + translate: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: diareuse/movie-metropolis/.github/actions/translate@master + secrets: inherit + with: + filePath: "app/src/main/res/values/strings.xml" \ No newline at end of file diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index c2cfaa50..39552783 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -44,14 +44,7 @@ jobs: versionNumber=$(($versionNumber / 10 + 1)) sed -i "s/versionCode 1/versionCode $versionNumber/" buildSrc/src/main/groovy/movie.metropolis.app.gradle - - name: Set up JDK - uses: actions/setup-java@v4 - with: - java-version: '17' - distribution: 'temurin' - - - name: Setup Android SDK - uses: android-actions/setup-android@v3 + - uses: diareuse/movie-metropolis/.github/actions/android@master - name: Build with Gradle uses: gradle/gradle-build-action@v3 diff --git a/.github/workflows/update-gradle.yml b/.github/workflows/update-gradle.yml new file mode 100644 index 00000000..d1b44056 --- /dev/null +++ b/.github/workflows/update-gradle.yml @@ -0,0 +1,22 @@ +name: Update Gradle Wrapper + +on: + workflow_dispatch: + schedule: + - cron: "0 0 * * *" + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + update-gradle-wrapper: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: diareuse/movie-metropolis/.github/actions/android@master + + - name: Update Gradle Wrapper + uses: gradle-update/update-gradle-wrapper-action@v2 + with: + commit-message-template: 'chore(gradle): Bump Gradle Wrapper from %sourceVersion% to %targetVersion%' \ No newline at end of file diff --git a/.release-please-manifest.json b/.release-please-manifest.json new file mode 100644 index 00000000..46b1b67c --- /dev/null +++ b/.release-please-manifest.json @@ -0,0 +1,3 @@ +{ + ".": "0.0.0" +} \ No newline at end of file diff --git a/release-please-config.json b/release-please-config.json new file mode 100644 index 00000000..03e840c3 --- /dev/null +++ b/release-please-config.json @@ -0,0 +1,11 @@ +{ + "always-update": true, + "include-v-in-tag": false, + "release-type": "simple", + "packages": { + ".": { + "changelog-path": "CHANGELOG.md" + } + }, + "$schema": "https://raw.githubusercontent.com/googleapis/release-please/main/schemas/config.json" +}