tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

commit 6cb60358701289240ec6849d21b8768ef4de4215
parent 3d53db66e8166d817968c9c4b7d6f496b44ec595
Author: Beatriz Rizental <beatriz.rizental@gmail.com>
Date:   Wed, 19 Jun 2024 09:46:19 +0200

Add CI for Base Browser

Diffstat:
A.gitlab-ci.yml | 14++++++++++++++
A.gitlab/ci/jobs/helpers.py | 129+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
A.gitlab/ci/jobs/lint/lint.yml | 25+++++++++++++++++++++++++
A.gitlab/ci/jobs/test/python-test.yml | 24++++++++++++++++++++++++
A.gitlab/ci/jobs/update-translations.yml | 52++++++++++++++++++++++++++++++++++++++++++++++++++++
A.gitlab/ci/mixins.yml | 91+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Meslint.config.mjs | 2+-
7 files changed, 336 insertions(+), 1 deletion(-)

diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml @@ -0,0 +1,14 @@ +stages: + - lint + - test + - update-translations + +variables: + IMAGE_PATH: containers.torproject.org/tpo/applications/tor-browser/base:latest + LOCAL_REPO_PATH: /srv/apps-repos/tor-browser.git + +include: + - local: '.gitlab/ci/mixins.yml' + - local: '.gitlab/ci/jobs/lint/lint.yml' + - local: '.gitlab/ci/jobs/test/python-test.yml' + - local: '.gitlab/ci/jobs/update-translations.yml' diff --git a/.gitlab/ci/jobs/helpers.py b/.gitlab/ci/jobs/helpers.py @@ -0,0 +1,129 @@ +#!/usr/bin/env python3 + +import argparse +import os +import re +import shlex +import subprocess + + +def git(command): + result = subprocess.run( + ["git"] + shlex.split(command), check=True, capture_output=True, text=True + ) + return result.stdout.strip() + + +def get_firefox_tag(reference): + """Extracts the Firefox tag associated with a branch or tag name. + + The "firefox tag" is the tag that marks + the end of the Mozilla commits and the start of the Tor Project commits. + + Know issue: If ever there is more than one tag per Firefox ESR version, + this function may return the incorrect reference number. + + Args: + reference: The branch or tag name to extract the Firefox tag from. + Expected format is tor-browser-91.2.0esr-11.0-1, + where 91.2.0esr is the Firefox version. + + Returns: + The reference specifier of the matching Firefox tag. + An exception will be raised if anything goes wrong. + """ + + # Extracts the version number from a branch or tag name. + firefox_version = "" + match = re.search(r"(?<=browser-)([^-]+)", reference) + if match: + # TODO: Validate that what we got is actually a valid semver string? + firefox_version = match.group(1) + else: + raise ValueError(f"Failed to extract version from reference '{reference}'.") + + major_version = firefox_version.split(".")[0] + minor_patch_version = "_".join(firefox_version.split(".")[1:]) + + remote_tags = git("ls-remote --tags origin") + + # Each line looks like: + # 9edd658bfd03a6b4743ecb75fd4a9ad968603715 refs/tags/FIREFOX_91_9_0esr_BUILD1 + pattern = ( + rf"(.*)FIREFOX_{re.escape(major_version)}_{re.escape(minor_patch_version)}(.*)$" + ) + match = re.search(pattern, remote_tags, flags=re.MULTILINE) + if not match: + # Attempt to match with a nightly tag, in case the ESR tag is not found + pattern = rf"(.*)FIREFOX_NIGHTLY_{re.escape(major_version)}(.*)$" + match = re.search(pattern, remote_tags, flags=re.MULTILINE) + + if match: + return match.group(0).split()[0] + else: + raise ValueError( + f"Failed to find reference specifier for Firefox tag of version '{firefox_version}' from '{reference}'." + ) + + +def get_list_of_changed_files(): + """Gets a list of files changed in the working directory. + + This function is meant to be run inside the Gitlab CI environment. + + When running in a default branch, get the list of changed files since the last Firefox tag. + When running for a new MR commit, get a list of changed files in the current MR. + + Returns: + A list of filenames of changed files (excluding deleted files). + An exception wil be raised if anything goes wrong. + """ + + base_reference = "" + + if os.getenv("CI_PIPELINE_SOURCE") == "merge_request_event": + # For merge requests, the base_reference is the common ancestor between the MR and the target branch + base_reference = os.getenv("CI_MERGE_REQUEST_DIFF_BASE_SHA") + if not base_reference: + # Probably because there has been no overall change. + # See gitlab.com/gitlab-org/gitlab/-/issues/375047#note_2648459916 + return [] + else: + # When not in merge requests, the base reference is the Firefox tag + base_reference = get_firefox_tag(os.getenv("CI_COMMIT_BRANCH")) + + if not base_reference: + raise RuntimeError("No base reference found. There might be more errors above.") + + # Fetch the tag reference + git(f"fetch origin {base_reference} --depth=1 --filter=blob:none") + # Return but filter the issue_templates files because those file names have spaces which can cause issues + return git("diff --diff-filter=d --name-only FETCH_HEAD HEAD").split("\n") + + +if __name__ == "__main__": + parser = argparse.ArgumentParser(description="") + + parser.add_argument( + "--get-firefox-tag", + help="Get the Firefox tag related to a given (tor-mullvad-base)-browser tag or branch name.", + type=str, + ) + parser.add_argument( + "--get-changed-files", + help="Get list of changed files." + "When running from a merge request gets the list of changed files since the merge-base of the current branch." + "When running from a protected branch i.e. any branch that starts with <something>-browser-, gets the list of files changed since the FIREFOX_ tag.", + action="store_true", + ) + + args = parser.parse_args() + + if args.get_firefox_tag: + print(get_firefox_tag(args.get_firefox_tag)) + elif args.get_changed_files: + # Separate the file names with a 0 byte to be parsed by xargs -0. Also + # drop the trailing '\n'. + print("\0".join(get_list_of_changed_files()), end="") + else: + print("No valid option provided.") diff --git a/.gitlab/ci/jobs/lint/lint.yml b/.gitlab/ci/jobs/lint/lint.yml @@ -0,0 +1,25 @@ +lint-all: + extends: .with-local-repo-bash + stage: lint + image: $IMAGE_PATH + interruptible: true + variables: + # Has to be the same as defined in `containers/base/Containerfile` + MOZBUILD_STATE_PATH: "/var/tmp/mozbuild" + cache: + paths: + - node_modules + # Store the cache regardless on job outcome + when: 'always' + # Share the cache throughout all pipelines running for a given branch + key: $CI_COMMIT_REF_SLUG + tags: + # Run these jobs in the browser dedicated runners. + - firefox + script: + - ./mach configure --with-base-browser-version=0.0.0 + - .gitlab/ci/jobs/helpers.py --get-changed-files | xargs -0 --no-run-if-empty ./mach lint -v + rules: + - if: $CI_PIPELINE_SOURCE == 'merge_request_event' + # Run job whenever a commit is merged to a protected branch + - if: ($CI_COMMIT_BRANCH && $CI_COMMIT_REF_PROTECTED == 'true' && $CI_PIPELINE_SOURCE == 'push') diff --git a/.gitlab/ci/jobs/test/python-test.yml b/.gitlab/ci/jobs/test/python-test.yml @@ -0,0 +1,24 @@ +python-test: + extends: .with-local-repo-bash + stage: test + image: $IMAGE_PATH + interruptible: true + variables: + MOZBUILD_STATE_PATH: "/var/tmp/mozbuild" + cache: + paths: + - node_modules + # Store the cache regardless on job outcome + when: 'always' + # Share the cache throughout all pipelines running for a given branch + key: $CI_COMMIT_REF_SLUG + tags: + # Run these jobs in the browser dedicated runners. + - firefox + script: + - ./mach configure --with-base-browser-version=0.0.0 + - ./mach python-test --subsuite base-browser + rules: + - if: $CI_PIPELINE_SOURCE == 'merge_request_event' || ($CI_COMMIT_BRANCH && $CI_COMMIT_REF_PROTECTED == 'true' && $CI_PIPELINE_SOURCE == 'push') + changes: + - "**/test_*.py" diff --git a/.gitlab/ci/jobs/update-translations.yml b/.gitlab/ci/jobs/update-translations.yml @@ -0,0 +1,52 @@ +.update-translation-base: + stage: update-translations + rules: + - if: ($TRANSLATION_FILES != "" && $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH && $CI_PIPELINE_SOURCE == "push") + changes: + - "**/*.ftl" + - "**/*.properties" + - "**/*.dtd" + - "**/*strings.xml" + - "**/update-translations.yml" + - "**/l10n/combine/combine.py" + - "**/l10n/combine-translation-versions.py" + - if: ($TRANSLATION_FILES != "" && $FORCE_UPDATE_TRANSLATIONS == "true") + variables: + COMBINED_FILES_JSON: "combined-translation-files.json" + TRANSLATION_FILES: '' + + +combine-en-US-translations: + extends: + - .with-local-repo-bash + - .update-translation-base + needs: [] + image: $IMAGE_PATH + # Artifact is for translation project job + artifacts: + paths: + - "$COMBINED_FILES_JSON" + expire_in: "60 min" + reports: + dotenv: job_id.env + # Don't load artifacts for this job. + dependencies: [] + tags: + # Run these jobs in the browser dedicated runners. + - firefox + script: + # Save this CI_JOB_ID to the dotenv file to be used in the variables for the + # push-en-US-translations job. + - echo 'COMBINE_TRANSLATIONS_JOB_ID='"$CI_JOB_ID" >job_id.env + - ./mach python ./tools/base_browser/l10n/combine-translation-versions.py "$CI_COMMIT_BRANCH" "$TRANSLATION_FILES" "$COMBINED_FILES_JSON" + +push-en-US-translations: + extends: .update-translation-base + needs: + - job: combine-en-US-translations + variables: + COMBINED_FILES_JSON_URL: "${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/jobs/${COMBINE_TRANSLATIONS_JOB_ID}/artifacts/${COMBINED_FILES_JSON}" + trigger: + strategy: depend + project: tor-browser-translation-bot/translation + branch: tor-browser-ci diff --git a/.gitlab/ci/mixins.yml b/.gitlab/ci/mixins.yml @@ -0,0 +1,91 @@ +.with-local-repo-bash: + variables: + GIT_STRATEGY: "none" + FETCH_TIMEOUT: 180 # 3 minutes + before_script: + - git init + - git remote add local "$LOCAL_REPO_PATH" + - | + # Determine the reference of the target branch in the local repository copy. + # + # 1. List all references in the local repository + # 2. Filter the references to the target branch + # 3. Remove tags + # 4. Keep a single line, in case there are too many matches + # 5. Clean up the output + # 6. Remove everything before the last two slashes, because the output is like `refs/heads/...` or `refs/remotes/...` + TARGET_BRANCH=$(git ls-remote local | grep ${CI_COMMIT_BRANCH:-$CI_MERGE_REQUEST_TARGET_BRANCH_NAME} | grep -v 'refs/tags/' | awk '{print $2}' | tail -1 | sed 's|[^/]*/[^/]*/||') + if [ -z "$TARGET_BRANCH" ]; then + echo "Target branch $TARGET_BRANCH is not yet in local repository. Stopping the pipeline." + exit 1 + fi + - git fetch --depth 500 local $TARGET_BRANCH + - git --no-pager log FETCH_HEAD --oneline -n 5 + - git remote add origin "$CI_REPOSITORY_URL" + - | + if [ -z "${CI_COMMIT_BRANCH:-$CI_MERGE_REQUEST_SOURCE_BRANCH_NAME}" ]; then + echo "No branch specified. Stopping the pipeline." + exit 1 + fi + - echo "Fetching from remote branch ${CI_COMMIT_BRANCH:-$CI_MERGE_REQUEST_SOURCE_BRANCH_NAME} with a ${FETCH_TIMEOUT}s timeout." + - | + fetch_with_timeout() { + local remote=$1 + local branch=$2 + + set +e + timeout ${FETCH_TIMEOUT} git fetch "$remote" "$branch" + local fetch_exit=$? + set -e + + if [ "$fetch_exit" -eq 124 ]; then + echo "Fetching failed for branch ${remote}/${branch} due to a timeout. Try again later." + echo "Gitlab may be experiencing slowness or the local copy of the repository on the CI server may be oudated." + return 1 + fi + + return $fetch_exit + } + + if ! fetch_with_timeout origin "${CI_COMMIT_BRANCH:-$CI_MERGE_REQUEST_SOURCE_BRANCH_NAME}"; then + echo "Fetching failed for branch ${CI_COMMIT_BRANCH:-$CI_MERGE_REQUEST_SOURCE_BRANCH_NAME}." + echo "Attempting to fetch the merge request branch, assuming this pipeline is not running in a fork." + + fetch_with_timeout origin "merge-requests/${CI_MERGE_REQUEST_IID}/head" || exit 1 + fi + - git checkout FETCH_HEAD + +.with-local-repo-pwsh: + variables: + GIT_STRATEGY: "none" + before_script: + - git init + - git remote add local $env:LOCAL_REPO_PATH + - | + $branchName = $env:CI_COMMIT_BRANCH + if ([string]::IsNullOrEmpty($branchName)) { + $branchName = $env:CI_MERGE_REQUEST_TARGET_BRANCH_NAME + } + $TARGET_BRANCH = git ls-remote local | Select-String -Pattern $branchName | Select-String -Pattern -NotMatch 'refs/tags/' | Select-Object -Last 1 | ForEach-Object { $_.ToString().Split()[1] -replace '^[^/]*/[^/]*/', '' } + if ([string]::IsNullOrEmpty($TARGET_BRANCH)) { + Write-Output "Target branch $TARGET_BRANCH is not yet in local repository. Stopping the pipeline." + exit 1 + } + - git remote add origin $env:CI_REPOSITORY_URL + - | + $branchName = $env:CI_COMMIT_BRANCH + if ([string]::IsNullOrEmpty($branchName)) { + $branchName = $env:CI_MERGE_REQUEST_SOURCE_BRANCH_NAME + } + if ([string]::IsNullOrEmpty($branchName)) { + Write-Output "No branch specified. Stopping the pipeline." + exit 1 + } + - Write-Output "Fetching from remote branch $branchName" + - | + if (! git fetch origin $branchName) { + Write-Output "Fetching failed for branch $branchName from $env:CI_REPOSITORY_URL." + Write-Output "Attempting to fetch the merge request branch, assuming this pipeline is not running in a fork." + git fetch origin "merge-requests/$env:CI_MERGE_REQUEST_IID/head" + } + - git checkout FETCH_HEAD diff --git a/eslint.config.mjs b/eslint.config.mjs @@ -434,7 +434,7 @@ let config = [ ignores: ["toolkit/**/test/**", "toolkit/**/tests/**"], plugins: { mozilla }, rules: { - "mozilla/no-browser-refs-in-toolkit": "error", + "mozilla/no-browser-refs-in-toolkit": "warn", }, }, {