diff --git a/.github/workflows/create_automerge_pr.yml b/.github/workflows/create_automerge_pr.yml index 0c5a39c9..0a04f687 100644 --- a/.github/workflows/create_automerge_pr.yml +++ b/.github/workflows/create_automerge_pr.yml @@ -40,6 +40,10 @@ name: Create automerge PR # types: [..., ready_for_review] # ``` # Unfortunately this will also re-trigger testing evenon a normal user's PR (which may have already been tested), but skipping them causes the checks to reset so this is the best we can do for now. + +permissions: + contents: read + on: workflow_call: inputs: @@ -55,6 +59,14 @@ on: type: string description: The message that should be included in the PR created by this job default: This PR was automatically opened by a GitHub action. Review the changes included in this PR and determine if they should be included in the release branch. If yes, merge the PR. Otherwise revert changes that should not be included on this branch. + create_as_draft: + type: boolean + description: Whether to create the PR as a draft + default: true + should_test: + type: boolean + description: Whether the created PR should trigger tests + default: false jobs: create_merge_pr: @@ -89,6 +101,10 @@ jobs: env: GH_TOKEN: ${{ github.token }} run: | + # Define variables based on workflow input + if [[ "${{ inputs.create_as_draft }} == "true" ]] ; then + DRAFT_FLAG="--draft" + fi # Create a branch for the PR instead of opening a PR that merges head_branch directly so that we have a fixed # target in the PR and don't modify the PR as new commits are put on the head branch. PR_BRANCH="automerge/merge-main-$(date +%Y-%m-%d_%H-%M)" @@ -98,7 +114,11 @@ jobs: gh pr create \ --base "${{ inputs.base_branch }}" \ - --head "$PR_BRANCH" \ + --head "$PR_BRANCH" ${DRAFT_FLAG} \ --title 'Merge `${{ inputs.head_branch }}` into `${{ inputs.base_branch }}`' \ - --body '${{ inputs.pr_message }}' \ - --draft + --body '${{ inputs.pr_message }}' + + # Trigger tests is requested + if [[ "${{ inputs.should_test }}" == "true" ]] ; then + gh pr ready "$PR_BRANCH" + fi diff --git a/.github/workflows/performance_test.yml b/.github/workflows/performance_test.yml index 105e0049..dcec8325 100644 --- a/.github/workflows/performance_test.yml +++ b/.github/workflows/performance_test.yml @@ -1,5 +1,8 @@ name: Performance test +permissions: + contents: read + on: workflow_call: inputs: diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml index aea1491d..7c06909f 100644 --- a/.github/workflows/pull_request.yml +++ b/.github/workflows/pull_request.yml @@ -1,5 +1,8 @@ name: Pull request +permissions: + contents: read + on: pull_request: types: [opened, reopened, synchronize] @@ -41,6 +44,12 @@ jobs: cd MyPackage swift package init --type library enable_wasm_sdk_build: true + # Android + android_sdk_pre_build_command: | + mkdir MyPackage + cd MyPackage + swift package init --type library + enable_android_sdk_build: true # Windows windows_build_command: | mkdir MyPackage @@ -55,6 +64,12 @@ jobs: with: # Skip Linux which doesn't currently support docker-less workflow enable_linux_checks: false + # Android + android_sdk_pre_build_command: | + mkdir MyPackage + cd MyPackage + swift package init --type library + enable_android_sdk_build: true # Windows windows_build_command: | mkdir MyPackage @@ -77,10 +92,21 @@ jobs: xcrun swift package init --type library xcrun swift build + build_tests_ios: + name: Build iOS Tests + uses: ./.github/workflows/swift_package_test.yml + with: + enable_linux_checks: false + enable_windows_checks: false + # iOS + enable_ios_checks: true + ios_pre_build_command: | + pwd + xcrun swift package init --type library + soundness: name: Soundness uses: ./.github/workflows/soundness.yml with: api_breakage_check_enabled: false license_header_check_project_name: "Swift.org" - format_check_enabled: false diff --git a/.github/workflows/scripts/check-docs.sh b/.github/workflows/scripts/check-docs.sh index 5c4fb026..f170af15 100755 --- a/.github/workflows/scripts/check-docs.sh +++ b/.github/workflows/scripts/check-docs.sh @@ -23,7 +23,7 @@ if [ ! -f .spi.yml ]; then fi if ! command -v yq &> /dev/null; then - fatal "yq could not be found. Please install yq to proceed." + apt -q update && apt -yq install yq fi package_files=$(find . -maxdepth 1 -name 'Package*.swift') diff --git a/.github/workflows/scripts/check-license-header.sh b/.github/workflows/scripts/check-license-header.sh index 845eb254..fa512e24 100755 --- a/.github/workflows/scripts/check-license-header.sh +++ b/.github/workflows/scripts/check-license-header.sh @@ -91,6 +91,7 @@ while IFS= read -r file_path; do plist) continue ;; # Plists don't support line comments proto) comment_marker='//' ;; ps1) comment_marker='##' ;; + psm1) comment_marker='##' ;; py) comment_marker='##'; header_prefix=$'#!/usr/bin/env python3\n' ;; rb) comment_marker='##'; header_prefix=$'#!/usr/bin/env ruby\n' ;; sh) comment_marker='##'; header_prefix=$'#!/bin/bash\n' ;; @@ -116,7 +117,7 @@ while IFS= read -r file_path; do file_header=$(head -n "${expected_file_header_linecount}" "${file_path}") normalized_file_header=$( echo "${file_header}" \ - | sed -E -e 's/20[12][0123456789] ?- ?20[12][0123456789]/YEARS/' -e 's/20[12][0123456789]/YEARS/' \ + | sed -E -e 's/20[12][0123456789] ?[-–] ?20[12][0123456789]/YEARS/' -e 's/20[12][0123456789]/YEARS/' \ ) if ! diff -u \ diff --git a/.github/workflows/scripts/check-swift-format.sh b/.github/workflows/scripts/check-swift-format.sh index a3c32216..fb6d351b 100755 --- a/.github/workflows/scripts/check-swift-format.sh +++ b/.github/workflows/scripts/check-swift-format.sh @@ -21,19 +21,19 @@ fatal() { error "$@"; exit 1; } if [[ -f .swiftformatignore ]]; then log "Found swiftformatignore file..." - log "Running swift format format..." - tr '\n' '\0' < .swiftformatignore| xargs -0 -I% printf '":(exclude)%" '| xargs git ls-files -z '*.swift' | xargs -0 swift format format --parallel --in-place + log "Running swift-format format..." + tr '\n' '\0' < .swiftformatignore| xargs -0 -I% printf '":(exclude)%" '| xargs git ls-files -z '*.swift' | xargs -0 swift-format format --parallel --in-place - log "Running swift format lint..." + log "Running swift-format lint..." - tr '\n' '\0' < .swiftformatignore | xargs -0 -I% printf '":(exclude)%" '| xargs git ls-files -z '*.swift' | xargs -0 swift format lint --strict --parallel + tr '\n' '\0' < .swiftformatignore | xargs -0 -I% printf '":(exclude)%" '| xargs git ls-files -z '*.swift' | xargs -0 swift-format lint --strict --parallel else - log "Running swift format format..." - git ls-files -z '*.swift' | xargs -0 swift format format --parallel --in-place + log "Running swift-format format..." + git ls-files -z '*.swift' | xargs -0 swift-format format --parallel --in-place - log "Running swift format lint..." + log "Running swift-format lint..." - git ls-files -z '*.swift' | xargs -0 swift format lint --strict --parallel + git ls-files -z '*.swift' | xargs -0 swift-format lint --strict --parallel fi diff --git a/.github/workflows/scripts/cross-pr-checkout.swift b/.github/workflows/scripts/cross-pr-checkout.swift new file mode 100644 index 00000000..7a4550ab --- /dev/null +++ b/.github/workflows/scripts/cross-pr-checkout.swift @@ -0,0 +1,247 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the Swift.org open source project +// +// Copyright (c) 2024 Apple Inc. and the Swift project authors +// Licensed under Apache License v2.0 with Runtime Library Exception +// +// See https://swift.org/LICENSE.txt for license information +// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +// +//===----------------------------------------------------------------------===// + +import Foundation + +#if canImport(FoundationNetworking) +// FoundationNetworking is a separate module in swift-foundation but not swift-corelibs-foundation. +import FoundationNetworking +#endif + +#if canImport(WinSDK) +import WinSDK +#endif + +struct GenericError: Error, CustomStringConvertible { + var description: String + + init(_ description: String) { + self.description = description + } +} + +/// Escape the given command to be printed for log output. +func escapeCommand(_ executable: URL, _ arguments: [String]) -> String { + return ([executable.path] + arguments).map { + if $0.contains(" ") { + return "'\($0)'" + } + return $0 + }.joined(separator: " ") +} + +/// Launch a subprocess with the given command and wait for it to finish +func run(_ executable: URL, _ arguments: String..., workingDirectory: URL? = nil) throws { + print("Running \(escapeCommand(executable, arguments)) (working directory: \(workingDirectory?.path ?? ""))") + let process = Process() + process.executableURL = executable + process.arguments = arguments + if let workingDirectory { + process.currentDirectoryURL = workingDirectory + } + + try process.run() + process.waitUntilExit() + guard process.terminationStatus == 0 else { + throw GenericError( + "\(escapeCommand(executable, arguments)) failed with non-zero exit code: \(process.terminationStatus)" + ) + } +} + +/// Find the executable with the given name in PATH. +public func lookup(executable: String) throws -> URL { + #if os(Windows) + let pathSeparator: Character = ";" + let executable = executable + ".exe" + #else + let pathSeparator: Character = ":" + #endif + for pathVariable in ["PATH", "Path"] { + guard let pathString = ProcessInfo.processInfo.environment[pathVariable] else { + continue + } + for searchPath in pathString.split(separator: pathSeparator) { + let candidateUrl = URL(fileURLWithPath: String(searchPath)).appendingPathComponent(executable) + if FileManager.default.isExecutableFile(atPath: candidateUrl.path) { + return candidateUrl + } + } + } + throw GenericError("Did not find \(executable)") +} + +func downloadData(from url: URL) async throws -> Data { + return try await withCheckedThrowingContinuation { continuation in + URLSession.shared.dataTask(with: url) { data, _, error in + if let error { + continuation.resume(throwing: error) + return + } + guard let data else { + continuation.resume(throwing: GenericError("Received no data for \(url)")) + return + } + continuation.resume(returning: data) + } + .resume() + } +} + +/// The JSON fields of the `https://api.github.com/repos//pulls/` endpoint that we care about. +struct PRInfo: Codable { + struct Base: Codable { + /// The name of the PR's base branch. + let ref: String + } + /// The base branch of the PR + let base: Base + + /// The PR's description. + let body: String? +} + +/// - Parameters: +/// - repository: The repository's name, eg. `swiftlang/swift-syntax` +func getPRInfo(repository: String, prNumber: String) async throws -> PRInfo { + guard let prInfoUrl = URL(string: "https://api.github.com/repos/\(repository)/pulls/\(prNumber)") else { + throw GenericError("Failed to form URL for GitHub API") + } + + do { + let data = try await downloadData(from: prInfoUrl) + return try JSONDecoder().decode(PRInfo.self, from: data) + } catch { + throw GenericError("Failed to load PR info from \(prInfoUrl): \(error)") + } +} + +/// Information about a PR that should be tested with this PR. +struct CrossRepoPR { + /// The owner of the repository, eg. `swiftlang` + let repositoryOwner: String + + /// The name of the repository, eg. `swift-syntax` + let repositoryName: String + + /// The PR number that's referenced. + let prNumber: String +} + +/// Retrieve all PRs that are referenced from PR `prNumber` in `repository`. +/// `repository` is the owner and repo name joined by `/`, eg. `swiftlang/swift-syntax`. +func getCrossRepoPrs(repository: String, prNumber: String) async throws -> [CrossRepoPR] { + var result: [CrossRepoPR] = [] + let prInfo = try await getPRInfo(repository: repository, prNumber: prNumber) + for line in prInfo.body?.split(separator: "\n") ?? [] { + guard line.lowercased().starts(with: "linked pr:") else { + continue + } + // We can't use Swift's Regex here because this script needs to run on Windows with Swift 5.9, which doesn't support + // Swift Regex. + var remainder = line[...] + guard let ownerRange = remainder.firstRange(of: "swiftlang/") ?? remainder.firstRange(of: "apple/") else { + continue + } + let repositoryOwner = remainder[ownerRange].dropLast() + remainder = remainder[ownerRange.upperBound...] + let repositoryName = remainder.prefix { $0.isLetter || $0.isNumber || $0 == "-" || $0 == "_" } + if repositoryName.isEmpty { + continue + } + remainder = remainder.dropFirst(repositoryName.count) + if remainder.starts(with: "/pull/") { + remainder = remainder.dropFirst(6) + } else if remainder.starts(with: "#") { + remainder = remainder.dropFirst() + } else { + continue + } + let pullRequestNum = remainder.prefix { $0.isNumber } + if pullRequestNum.isEmpty { + continue + } + result.append( + CrossRepoPR( + repositoryOwner: String(repositoryOwner), + repositoryName: String(repositoryName), + prNumber: String(pullRequestNum) + ) + ) + } + return result +} + +func main() async throws { + guard ProcessInfo.processInfo.arguments.count >= 3 else { + throw GenericError( + """ + Expected two arguments: + - Repository name, eg. `swiftlang/swift-syntax + - PR number + """ + ) + } + let repository = ProcessInfo.processInfo.arguments[1] + let prNumber = ProcessInfo.processInfo.arguments[2] + + let crossRepoPrs = try await getCrossRepoPrs(repository: repository, prNumber: prNumber) + if !crossRepoPrs.isEmpty { + print("Detected cross-repo PRs") + for crossRepoPr in crossRepoPrs { + print(" - \(crossRepoPr.repositoryOwner)/\(crossRepoPr.repositoryName)#\(crossRepoPr.prNumber)") + } + } + + for crossRepoPr in crossRepoPrs { + let git = try lookup(executable: "git") + let swift = try lookup(executable: "swift") + let baseBranch = try await getPRInfo( + repository: "\(crossRepoPr.repositoryOwner)/\(crossRepoPr.repositoryName)", + prNumber: crossRepoPr.prNumber + ).base.ref + + let workspaceDir = URL(fileURLWithPath: "..").resolvingSymlinksInPath() + let repoDir = workspaceDir.appendingPathComponent(crossRepoPr.repositoryName) + try run( + git, + "clone", + "https://github.com/\(crossRepoPr.repositoryOwner)/\(crossRepoPr.repositoryName).git", + "\(crossRepoPr.repositoryName)", + workingDirectory: workspaceDir + ) + try run(git, "fetch", "origin", "pull/\(crossRepoPr.prNumber)/merge:pr_merge", workingDirectory: repoDir) + try run(git, "checkout", baseBranch, workingDirectory: repoDir) + try run(git, "reset", "--hard", "pr_merge", workingDirectory: repoDir) + try run( + swift, + "package", + "config", + "set-mirror", + "--package-url", + "https://github.com/\(crossRepoPr.repositoryOwner)/\(crossRepoPr.repositoryName).git", + "--mirror-url", + repoDir.path + ) + } +} + +do { + try await main() +} catch { + print(error) + #if os(Windows) + _Exit(1) + #else + exit(1) + #endif +} diff --git a/.github/workflows/scripts/install-and-build-with-sdk.sh b/.github/workflows/scripts/install-and-build-with-sdk.sh old mode 100644 new mode 100755 index 6111a631..e866ca14 --- a/.github/workflows/scripts/install-and-build-with-sdk.sh +++ b/.github/workflows/scripts/install-and-build-with-sdk.sh @@ -18,15 +18,29 @@ error() { printf -- "** ERROR: %s\n" "$*" >&2; } fatal() { error "$@"; exit 1; } # Parse command line options +INSTALL_ANDROID=false INSTALL_STATIC_LINUX=false INSTALL_WASM=false BUILD_EMBEDDED_WASM=false SWIFT_VERSION_INPUT="" SWIFT_BUILD_FLAGS="" SWIFT_BUILD_COMMAND="swift build" +ANDROID_SDK_TRIPLES=() while [[ $# -gt 0 ]]; do case $1 in + --android) + INSTALL_ANDROID=true + shift + ;; + --android-ndk-version=*) + ANDROID_NDK_VERSION="${1#*=}" + shift + ;; + --android-sdk-triple=*) + ANDROID_SDK_TRIPLES+=("${1#*=}") + shift + ;; --static) INSTALL_STATIC_LINUX=true shift @@ -64,32 +78,32 @@ done # Validate arguments if [[ -z "$SWIFT_VERSION_INPUT" ]]; then - fatal "Usage: $0 [--static] [--wasm] [--flags=\"\"] [--build-command=\"\"] " + fatal "Usage: $0 [--android] [--static] [--wasm] [--flags=\"\"] [--build-command=\"\"] " fi -if [[ "$INSTALL_STATIC_LINUX" == false && "$INSTALL_WASM" == false ]]; then - fatal "At least one of --static or --wasm must be specified" +if [[ "$INSTALL_ANDROID" == false && "$INSTALL_STATIC_LINUX" == false && "$INSTALL_WASM" == false ]]; then + fatal "At least one of --android or --static or --wasm must be specified" fi log "Requested Swift version: $SWIFT_VERSION_INPUT" +log "Install Android Swift SDK: $INSTALL_ANDROID" log "Install Static Linux Swift SDK: $INSTALL_STATIC_LINUX" log "Install Wasm Swift SDK: $INSTALL_WASM" if [[ -n "$SWIFT_BUILD_FLAGS" ]]; then log "Additional build flags: $SWIFT_BUILD_FLAGS" fi -# Detect package manager -if command -v apt >/dev/null 2>&1; then - INSTALL_PACKAGE_COMMAND="apt update -q && apt install -yq" -elif command -v dnf >/dev/null 2>&1; then - INSTALL_PACKAGE_COMMAND="dnf install -y" -elif command -v yum >/dev/null 2>&1; then - INSTALL_PACKAGE_COMMAND="yum install -y" -else - fatal "No supported package manager found" -fi - install_package() { + # Detect package manager + if command -v apt >/dev/null 2>&1; then + INSTALL_PACKAGE_COMMAND="apt update -q && apt install -yq" + elif command -v dnf >/dev/null 2>&1; then + INSTALL_PACKAGE_COMMAND="dnf install -y" + elif command -v yum >/dev/null 2>&1; then + INSTALL_PACKAGE_COMMAND="yum install -y" + else + fatal "No supported package manager found" + fi eval "$INSTALL_PACKAGE_COMMAND $1" } @@ -103,7 +117,7 @@ SWIFT_API_INSTALL_ROOT="https://www.swift.org/api/v1/install" # and gets the checksum for the patch version's Static Linux and/or Wasm Swift SDK. # # $1 (string): A minor Swift version, e.g. "6.1" -# Output: A string of the form "|| +# Output: A string of the form "||| find_latest_swift_version() { local minor_version="$1" @@ -128,6 +142,23 @@ find_latest_swift_version() { log "Found latest patch version: $latest_version" + local android_sdk_checksum="" + if [[ "$INSTALL_ANDROID" == true ]]; then + android_sdk_checksum=$(echo "$releases_json" | jq -r --arg version "$latest_version" ' + .[] + | select(.name == $version) + | .platforms[] + | select(.platform == "android") + | .checksum + ') + + if [[ -z "$android_sdk_checksum" ]]; then + fatal "No Android Swift SDK checksum found for Swift $latest_version" + fi + + log "Found Android Swift SDK checksum: ${android_sdk_checksum:0:12}..." + fi + local static_linux_sdk_checksum="" if [[ "$INSTALL_STATIC_LINUX" == true ]]; then static_linux_sdk_checksum=$(echo "$releases_json" | jq -r --arg version "$latest_version" ' @@ -162,14 +193,15 @@ find_latest_swift_version() { log "Found Swift SDK for Wasm checksum: ${wasm_sdk_checksum:0:12}..." fi - echo "${latest_version}|${static_linux_sdk_checksum}|${wasm_sdk_checksum}" + echo "${latest_version}|${android_sdk_checksum}|${static_linux_sdk_checksum}|${wasm_sdk_checksum}" } -# Finds the latest Static Linux or Wasm Swift SDK development snapshot -# for the inputted Swift version and its checksum. +# Finds the latest Android or Static Linux or Wasm +# Swift SDK development snapshot for the inputted +# Swift version and its checksum. # # $1 (string): Nightly Swift version, e.g. "6.2" or "main" -# $2 (string): "static" or "wasm" +# $2 (string): "android" or "static" or "wasm" # Output: A string of the form "|", # e.g. "swift-6.2-DEVELOPMENT-SNAPSHOT-2025-07-29-a|" find_latest_sdk_snapshot() { @@ -206,6 +238,12 @@ find_latest_sdk_snapshot() { } SWIFT_VERSION_BRANCH="" +ANDROID_SDK_TAG="" +ANDROID_SDK_CHECKSUM="" +# TODO: we will be removing the "-0.1" suffix in a future nightly +ANDROID_SDK_PATH_SUFFIX="-0.1" +ANDROID_SDK_PATH_SEP="-" + STATIC_LINUX_SDK_TAG="" STATIC_LINUX_SDK_CHECKSUM="" WASM_SDK_TAG="" @@ -220,6 +258,13 @@ if [[ "$SWIFT_VERSION_INPUT" == nightly-* ]]; then SWIFT_VERSION_BRANCH="swift-${version}-branch" fi + if [[ "$INSTALL_ANDROID" == true ]]; then + android_sdk_info=$(find_latest_sdk_snapshot "$version" "android") + + ANDROID_SDK_TAG=$(echo "$android_sdk_info" | cut -d'|' -f1) + ANDROID_SDK_CHECKSUM=$(echo "$android_sdk_info" | cut -d'|' -f2) + fi + if [[ "$INSTALL_STATIC_LINUX" == true ]]; then static_linux_sdk_info=$(find_latest_sdk_snapshot "$version" "static") @@ -239,14 +284,21 @@ else latest_version=$(echo "$latest_version_info" | cut -d'|' -f1) SWIFT_VERSION_BRANCH="swift-${latest_version}-release" + ANDROID_SDK_TAG="swift-${latest_version}-RELEASE" + ANDROID_SDK_CHECKSUM=$(echo "$latest_version_info" | cut -d'|' -f2) + STATIC_LINUX_SDK_TAG="swift-${latest_version}-RELEASE" - STATIC_LINUX_SDK_CHECKSUM=$(echo "$latest_version_info" | cut -d'|' -f2) + STATIC_LINUX_SDK_CHECKSUM=$(echo "$latest_version_info" | cut -d'|' -f3) WASM_SDK_TAG="swift-${latest_version}-RELEASE" - WASM_SDK_CHECKSUM=$(echo "$latest_version_info" | cut -d'|' -f3) + WASM_SDK_CHECKSUM=$(echo "$latest_version_info" | cut -d'|' -f4) fi # Validate that required Swift SDK tags are set +if [[ "$INSTALL_ANDROID" == true && -z "$ANDROID_SDK_TAG" ]]; then + fatal "ANDROID_SDK_TAG is not set but Android Swift SDK installation was requested" +fi + if [[ "$INSTALL_STATIC_LINUX" == true && -z "$STATIC_LINUX_SDK_TAG" ]]; then fatal "STATIC_LINUX_SDK_TAG is not set but Static Linux Swift SDK installation was requested" fi @@ -439,9 +491,25 @@ download_and_extract_toolchain() { } INSTALLED_SWIFT_TAG=$(get_installed_swift_tag) +SWIFT_EXECUTABLE_FOR_ANDROID_SDK="" SWIFT_EXECUTABLE_FOR_STATIC_LINUX_SDK="" SWIFT_EXECUTABLE_FOR_WASM_SDK="" +if [[ "$INSTALL_ANDROID" == true ]]; then + if [[ "$INSTALLED_SWIFT_TAG" == "$ANDROID_SDK_TAG" ]]; then + log "Current toolchain matches Android Swift SDK snapshot: $ANDROID_SDK_TAG" + SWIFT_EXECUTABLE_FOR_ANDROID_SDK="swift" + else + log "Installing Swift toolchain to match Android Swift SDK snapshot: $ANDROID_SDK_TAG" + initialize_os_info + SWIFT_EXECUTABLE_FOR_ANDROID_SDK=$(download_and_extract_toolchain "$ANDROID_SDK_TAG") + if [[ $? -eq $EXIT_TOOLCHAIN_NOT_FOUND ]]; then + # Don't fail the workflow if we can't find the right toolchain + exit 0 + fi + fi +fi + if [[ "$INSTALL_STATIC_LINUX" == true ]]; then if [[ "$INSTALLED_SWIFT_TAG" == "$STATIC_LINUX_SDK_TAG" ]]; then log "Current toolchain matches Static Linux Swift SDK snapshot: $STATIC_LINUX_SDK_TAG" @@ -472,9 +540,60 @@ if [[ "$INSTALL_WASM" == true ]]; then fi fi +ANDROID_SDK_DOWNLOAD_ROOT="${SWIFT_DOWNLOAD_ROOT}/${SWIFT_VERSION_BRANCH}/android-sdk" STATIC_LINUX_SDK_DOWNLOAD_ROOT="${SWIFT_DOWNLOAD_ROOT}/${SWIFT_VERSION_BRANCH}/static-sdk" WASM_SDK_DOWNLOAD_ROOT="${SWIFT_DOWNLOAD_ROOT}/${SWIFT_VERSION_BRANCH}/wasm-sdk" +install_android_sdk() { + # Check if the Android Swift SDK is already installed + if "$SWIFT_EXECUTABLE_FOR_ANDROID_SDK" sdk list 2>/dev/null | grep -q "^${ANDROID_SDK_TAG}_android"; then + log "✅ Android Swift SDK ${ANDROID_SDK_TAG} is already installed, skipping installation" + return 0 + fi + + log "Installing Android Swift SDK: $ANDROID_SDK_TAG" + + local android_sdk_name="${ANDROID_SDK_TAG}_android${ANDROID_SDK_PATH_SUFFIX}" + local android_sdk_bundle_name="${android_sdk_name}.artifactbundle" + # TODO: remove once the next nightly changes the "-" to "_" in the name + local android_sdk_bundle_dir="${android_sdk_bundle_name//_android/${ANDROID_SDK_PATH_SEP}android}" + local android_sdk_filename="${android_sdk_bundle_name}.tar.gz" + local sdk_url="${ANDROID_SDK_DOWNLOAD_ROOT}/${ANDROID_SDK_TAG}/${android_sdk_filename}" + + log "Running: ${SWIFT_EXECUTABLE_FOR_ANDROID_SDK} sdk install ${sdk_url} --checksum ${ANDROID_SDK_CHECKSUM}" + + if "$SWIFT_EXECUTABLE_FOR_ANDROID_SDK" sdk install "$sdk_url" --checksum "$ANDROID_SDK_CHECKSUM"; then + log "✅ Android Swift SDK installed successfully" + else + fatal "Failed to install Android Swift SDK" + fi + + rm -f "${sdk_url}" + + # now setup the link to the local ANDROID_NDK_HOME + swift sdk configure --show-configuration "$(swift sdk list | grep android | tail -n 1)" + + # guess some common places where the swift-sdks file lives + cd ~/Library/org.swift.swiftpm || cd ~/.config/swiftpm || cd ~/.local/swiftpm || cd ~/.swiftpm || cd /root/.swiftpm + + # Download and install the Android NDK. + # Note that we could use the system package manager, but it is + # named different things for different distributions + # (e.g., "google-android-ndk-r26-installer" on Debian) + if [[ ! -d "${ANDROID_NDK_HOME:-}" ]]; then + # permit the "--android-ndk" flag to override the default + local android_ndk_version="${ANDROID_NDK_VERSION:-r27d}" + curl -fsSL -o ndk.zip --retry 3 https://dl.google.com/android/repository/android-ndk-"${android_ndk_version}"-"$(uname -s)".zip + command -v unzip >/dev/null || install_package unzip + unzip -q ndk.zip + rm ndk.zip + export ANDROID_NDK_HOME="${PWD}"/android-ndk-"${android_ndk_version}" + fi + + ./swift-sdks/"${android_sdk_bundle_dir}"/swift-android/scripts/setup-android-sdk.sh + cd - +} + install_static_linux_sdk() { # Check if the Static Linux Swift SDK is already installed if "$SWIFT_EXECUTABLE_FOR_STATIC_LINUX_SDK" sdk list 2>/dev/null | grep -q "^${STATIC_LINUX_SDK_TAG}_static-linux-0.0.1"; then @@ -494,6 +613,8 @@ install_static_linux_sdk() { else fatal "Failed to install Static Linux Swift SDK" fi + + rm -f "${sdk_url}" } install_wasm_sdk() { @@ -515,9 +636,16 @@ install_wasm_sdk() { else fatal "Failed to install Swift SDK for Wasm" fi + + rm -f "${sdk_url}" } install_sdks() { + if [[ "$INSTALL_ANDROID" == true ]]; then + log "Starting install of Swift ${SWIFT_VERSION_INPUT} Android Swift SDK" + install_android_sdk + fi + if [[ "$INSTALL_STATIC_LINUX" == true ]]; then log "Starting install of Swift ${SWIFT_VERSION_INPUT} Static Linux Swift SDK" install_static_linux_sdk @@ -533,6 +661,35 @@ build() { # Enable alias expansion to use a 'swift' alias for the executable path shopt -s expand_aliases + if [[ "$INSTALL_ANDROID" == true ]]; then + log "Running Swift build with Android Swift SDK" + + local sdk_name="${ANDROID_SDK_TAG}${ANDROID_SDK_PATH_SEP}android${ANDROID_SDK_PATH_SUFFIX}" + + alias swift='$SWIFT_EXECUTABLE_FOR_ANDROID_SDK' + + # This can become a single invocation in the future when `swift build` supports multiple Android triples at once + for android_sdk_triple in "${ANDROID_SDK_TRIPLES[@]}" ; do + local build_command="$SWIFT_BUILD_COMMAND --swift-sdk ${android_sdk_triple}" + if [[ -n "$SWIFT_BUILD_FLAGS" ]]; then + build_command="$build_command $SWIFT_BUILD_FLAGS" + fi + + log "Running: $build_command" + + # clear the ANDROID_NDK_ROOT environment variable if it is set + # due to https://github.com/swiftlang/swift-driver/pull/1879 + # otherwise build error: missing required module 'SwiftAndroid' + export ANDROID_NDK_ROOT="" + + if eval "$build_command"; then + log "✅ Swift build with Android Swift SDK completed successfully" + else + fatal "Swift build with Android Swift SDK failed" + fi + done + fi + if [[ "$INSTALL_STATIC_LINUX" == true ]]; then log "Running Swift build with Static Linux Swift SDK" diff --git a/.github/workflows/scripts/windows/swift/install-swift-6.2.ps1 b/.github/workflows/scripts/windows/swift/install-swift-6.2.ps1 new file mode 100644 index 00000000..2827cb9d --- /dev/null +++ b/.github/workflows/scripts/windows/swift/install-swift-6.2.ps1 @@ -0,0 +1,17 @@ +##===----------------------------------------------------------------------===## +## +## This source file is part of the Swift.org open source project +## +## Copyright (c) 2025 Apple Inc. and the Swift project authors +## Licensed under Apache License v2.0 with Runtime Library Exception +## +## See https://swift.org/LICENSE.txt for license information +## See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors +## +##===----------------------------------------------------------------------===## +. $PSScriptRoot\install-swift.ps1 + +$SWIFT='https://download.swift.org/swift-6.2.1-release/windows10/swift-6.2.1-RELEASE/swift-6.2.1-RELEASE-windows10.exe' +$SWIFT_SHA256='FD1209AC3E008152924E0409E5590F2FE41644132E532D4526B8641339E88000' + +Install-Swift -Url $SWIFT -Sha256 $SWIFT_SHA256 diff --git a/.github/workflows/soundness.yml b/.github/workflows/soundness.yml index 6c7d187b..ce8f63f0 100644 --- a/.github/workflows/soundness.yml +++ b/.github/workflows/soundness.yml @@ -18,7 +18,7 @@ on: api_breakage_check_container_image: type: string description: "Container image for the API breakage check job. Defaults to latest Swift Ubuntu image." - default: "swift:6.0-noble" + default: "swift:6.2-noble" docs_check_enabled: type: boolean description: "Boolean to enable the docs check job. Defaults to true." @@ -26,7 +26,7 @@ on: docs_check_container_image: type: string description: "Container image for the docs check job. Defaults to latest Swift Ubuntu image." - default: "swift:6.0-noble" + default: "swift:6.2-noble" docs_check_additional_arguments: type: string description: "Additional arguments that should be passed to docc" @@ -58,15 +58,15 @@ on: format_check_container_image: type: string description: "Container image for the format check job. Defaults to latest Swift Ubuntu image." - default: "swift:6.0-noble" + default: "swift:6.2-noble" shell_check_enabled: type: boolean description: "Boolean to enable the shell check job. Defaults to true." default: true shell_check_container_image: type: string - description: "Container image for the shell check job. Defaults to latest Swift Ubuntu image." - default: "swift:6.0-noble" + description: "Container image for the shell check job. Defaults to latest Ubuntu 24.04 Noble image." + default: "ubuntu:noble" yamllint_check_enabled: type: boolean description: "Boolean to enable the YAML lint check job. Defaults to true." @@ -80,6 +80,9 @@ on: description: "Linux command to execute before building the Swift package" default: "" +permissions: + contents: read + ## We are cancelling previously triggered workflow runs concurrency: group: ${{ github.workflow }}-${{ github.ref }}-soundness @@ -92,7 +95,7 @@ jobs: runs-on: ubuntu-latest container: image: ${{ inputs.api_breakage_check_container_image }} - timeout-minutes: 20 + timeout-minutes: 40 steps: - name: Checkout repository uses: actions/checkout@v4 @@ -139,15 +142,27 @@ jobs: with: persist-credentials: false submodules: true + - name: Checkout swiftlang/github-workflows repository + if: ${{ github.repository != 'swiftlang/github-workflows' }} + uses: actions/checkout@v4 + with: + repository: swiftlang/github-workflows + path: github-workflows + - name: Determine script-root path + id: script_path + run: | + if [ "${{ github.repository }}" = "swiftlang/github-workflows" ]; then + echo "root=$GITHUB_WORKSPACE" >> $GITHUB_OUTPUT + else + echo "root=$GITHUB_WORKSPACE/github-workflows" >> $GITHUB_OUTPUT + fi - name: Pre-build if: ${{ inputs.linux_pre_build_command }} run: ${{ inputs.linux_pre_build_command }} - name: Run documentation check env: ADDITIONAL_DOCC_ARGUMENTS: ${{ inputs.docs_check_additional_arguments }} - run: | - which curl yq || (apt -q update && apt -yq install curl yq) - curl -s --retry 3 https://raw.githubusercontent.com/swiftlang/github-workflows/refs/heads/main/.github/workflows/scripts/check-docs.sh | bash + run: ${{ steps.script_path.outputs.root }}/.github/workflows/scripts/check-docs.sh unacceptable-language-check: name: Unacceptable language check @@ -160,10 +175,24 @@ jobs: with: persist-credentials: false submodules: true + - name: Checkout swiftlang/github-workflows repository + if: ${{ github.repository != 'swiftlang/github-workflows' }} + uses: actions/checkout@v4 + with: + repository: swiftlang/github-workflows + path: github-workflows + - name: Determine script-root path + id: script_path + run: | + if [ "${{ github.repository }}" = "swiftlang/github-workflows" ]; then + echo "root=$GITHUB_WORKSPACE" >> $GITHUB_OUTPUT + else + echo "root=$GITHUB_WORKSPACE/github-workflows" >> $GITHUB_OUTPUT + fi - name: Run unacceptable language check env: UNACCEPTABLE_WORD_LIST: ${{ inputs.unacceptable_language_check_word_list}} - run: curl -s --retry 3 https://raw.githubusercontent.com/swiftlang/github-workflows/refs/heads/main/.github/workflows/scripts/check-unacceptable-language.sh | bash + run: ${{ steps.script_path.outputs.root }}/.github/workflows/scripts/check-unacceptable-language.sh license-header-check: name: License headers check @@ -176,10 +205,24 @@ jobs: with: persist-credentials: false submodules: true + - name: Checkout swiftlang/github-workflows repository + if: ${{ github.repository != 'swiftlang/github-workflows' }} + uses: actions/checkout@v4 + with: + repository: swiftlang/github-workflows + path: github-workflows + - name: Determine script-root path + id: script_path + run: | + if [ "${{ github.repository }}" = "swiftlang/github-workflows" ]; then + echo "root=$GITHUB_WORKSPACE" >> $GITHUB_OUTPUT + else + echo "root=$GITHUB_WORKSPACE/github-workflows" >> $GITHUB_OUTPUT + fi - name: Run license header check env: PROJECT_NAME: ${{ inputs.license_header_check_project_name }} - run: curl -s --retry 3 https://raw.githubusercontent.com/swiftlang/github-workflows/refs/heads/main/.github/workflows/scripts/check-license-header.sh | bash + run: ${{ steps.script_path.outputs.root }}/.github/workflows/scripts/check-license-header.sh broken-symlink-check: name: Broken symlinks check @@ -192,8 +235,22 @@ jobs: with: persist-credentials: false submodules: true + - name: Checkout swiftlang/github-workflows repository + if: ${{ github.repository != 'swiftlang/github-workflows' }} + uses: actions/checkout@v4 + with: + repository: swiftlang/github-workflows + path: github-workflows + - name: Determine script-root path + id: script_path + run: | + if [ "${{ github.repository }}" = "swiftlang/github-workflows" ]; then + echo "root=$GITHUB_WORKSPACE" >> $GITHUB_OUTPUT + else + echo "root=$GITHUB_WORKSPACE/github-workflows" >> $GITHUB_OUTPUT + fi - name: Run broken symlinks check - run: curl -s --retry 3 https://raw.githubusercontent.com/swiftlang/github-workflows/refs/heads/main/.github/workflows/scripts/check-broken-symlinks.sh | bash + run: ${{ steps.script_path.outputs.root }}/.github/workflows/scripts/check-broken-symlinks.sh format-check: name: Format check @@ -208,13 +265,25 @@ jobs: with: persist-credentials: false submodules: true + - name: Checkout swiftlang/github-workflows repository + if: ${{ github.repository != 'swiftlang/github-workflows' }} + uses: actions/checkout@v4 + with: + repository: swiftlang/github-workflows + path: github-workflows + - name: Determine script-root path + id: script_path + run: | + if [ "${{ github.repository }}" = "swiftlang/github-workflows" ]; then + echo "root=$GITHUB_WORKSPACE" >> $GITHUB_OUTPUT + else + echo "root=$GITHUB_WORKSPACE/github-workflows" >> $GITHUB_OUTPUT + fi - name: Mark the workspace as safe # https://github.com/actions/checkout/issues/766 run: git config --global --add safe.directory ${GITHUB_WORKSPACE} - name: Run format check - run: | - which curl || (apt -q update && apt -yq install curl) - curl -s --retry 3 https://raw.githubusercontent.com/swiftlang/github-workflows/refs/heads/main/.github/workflows/scripts/check-swift-format.sh | bash + run: ${{ steps.script_path.outputs.root }}/.github/workflows/scripts/check-swift-format.sh shell-check: name: Shell check @@ -224,6 +293,8 @@ jobs: image: ${{ inputs.shell_check_container_image }} timeout-minutes: 5 steps: + - name: Install git + run: which git || (apt -q update && apt -yq install git) - name: Checkout repository uses: actions/checkout@v4 with: @@ -248,13 +319,27 @@ jobs: with: persist-credentials: false submodules: true + - name: Checkout swiftlang/github-workflows repository + if: ${{ github.repository != 'swiftlang/github-workflows' }} + uses: actions/checkout@v4 + with: + repository: swiftlang/github-workflows + path: github-workflows + - name: Determine script-root path + id: script_path + run: | + if [ "${{ github.repository }}" = "swiftlang/github-workflows" ]; then + echo "root=$GITHUB_WORKSPACE" >> $GITHUB_OUTPUT + else + echo "root=$GITHUB_WORKSPACE/github-workflows" >> $GITHUB_OUTPUT + fi - name: Run yamllint run: | which yamllint || (apt -q update && apt install -yq yamllint) cd ${GITHUB_WORKSPACE} if [ ! -f ".yamllint.yml" ]; then echo "Downloading default yamllint config file" - curl -s --retry 3 https://raw.githubusercontent.com/swiftlang/github-workflows/refs/heads/main/.github/workflows/configs/yamllint.yml > .yamllint.yml + cat ${{ steps.script_path.outputs.root }}/.github/workflows/configs/yamllint.yml > .yamllint.yml fi yamllint --strict --config-file .yamllint.yml . @@ -269,12 +354,26 @@ jobs: with: persist-credentials: false submodules: true + - name: Checkout swiftlang/github-workflows repository + if: ${{ github.repository != 'swiftlang/github-workflows' }} + uses: actions/checkout@v4 + with: + repository: swiftlang/github-workflows + path: github-workflows + - name: Determine script-root path + id: script_path + run: | + if [ "${{ github.repository }}" = "swiftlang/github-workflows" ]; then + echo "root=$GITHUB_WORKSPACE" >> $GITHUB_OUTPUT + else + echo "root=$GITHUB_WORKSPACE/github-workflows" >> $GITHUB_OUTPUT + fi - name: Run flake8 run: | pip3 install flake8 flake8-import-order cd ${GITHUB_WORKSPACE} if [ ! -f ".flake8" ]; then echo "Downloading default flake8 config file" - curl -s --retry 3 https://raw.githubusercontent.com/swiftlang/github-workflows/refs/heads/main/.github/workflows/configs/.flake8 > .flake8 + cat ${{ steps.script_path.outputs.root }}/.github/workflows/configs/.flake8 > .flake8 fi flake8 diff --git a/.github/workflows/swift_package_test.yml b/.github/workflows/swift_package_test.yml index aaf06692..8637a1c2 100644 --- a/.github/workflows/swift_package_test.yml +++ b/.github/workflows/swift_package_test.yml @@ -1,12 +1,15 @@ name: Swift Matrix +permissions: + contents: read + on: workflow_call: inputs: macos_xcode_versions: type: string description: "Xcode version list (JSON)" - default: "[\"16.2\", \"16.3\"]" + default: "[\"16.3\", \"16.4\", \"26.0\"]" macos_exclude_xcode_versions: type: string description: "Exclude Xcode version list (JSON)" @@ -19,10 +22,26 @@ on: type: string description: "macOS arch list (JSON)" default: "[\"ARM64\"]" + ios_host_xcode_versions: + type: string + description: "Xcode version list (JSON)" + default: null + ios_host_exclude_xcode_versions: + type: string + description: "Exclude Xcode version list (JSON)" + default: null + ios_host_versions: + type: string + description: "iOS host (macOS) version list (JSON)" + default: null + ios_host_archs: + type: string + description: "iOS host (macOS) arch list (JSON)" + default: null linux_swift_versions: type: string description: "Include Linux Swift version list (JSON)" - default: "[ \"5.9\", \"5.10\", \"6.0\", \"6.1\", \"nightly-main\", \"nightly-6.2\"]" + default: "[ \"5.9\", \"5.10\", \"6.0\", \"6.1\", \"6.2\", \"nightly-main\", \"nightly-6.2\"]" linux_exclude_swift_versions: type: string description: "Exclude Linux Swift version list (JSON)" @@ -34,20 +53,32 @@ on: linux_static_sdk_versions: type: string description: "Static Linux Swift SDK version list (JSON)" - default: "[\"nightly-6.2\"]" + default: "[\"nightly-main\", \"nightly-6.2\", \"6.2\"]" + linux_static_sdk_exclude_swift_versions: + type: string + description: "Exclude Static Linux Swift SDK version list (JSON)" + default: "[{\"swift_version\": \"\"}]" wasm_sdk_versions: type: string description: "Wasm Swift SDK version list (JSON)" - default: "[\"nightly-main\", \"nightly-6.2\"]" + default: "[\"nightly-main\", \"nightly-6.2\", \"6.2\"]" wasm_exclude_swift_versions: type: string description: "Exclude Wasm Swift SDK version list (JSON)" default: "[{\"swift_version\": \"\"}]" + android_sdk_versions: + type: string + description: "Android Swift SDK version list (JSON)" + default: "[\"nightly-main\"]" + android_exclude_swift_versions: + type: string + description: "Exclude Android Swift SDK version list (JSON)" + default: "[{\"swift_version\": \"\"}]" windows_swift_versions: type: string description: "Include Windows Swift version list (JSON)" # "5.10" is omitted for Windows because the container image is broken. - default: "[\"5.9\", \"6.0\", \"6.1\", \"nightly\", \"nightly-6.2\"]" + default: "[\"5.9\", \"6.0\", \"6.1\", \"6.2\", \"nightly\", \"nightly-6.2\"]" windows_exclude_swift_versions: type: string description: "Exclude Windows Swift version list (JSON)" @@ -72,6 +103,10 @@ on: type: string description: "Linux command to execute before building the Swift package with the Embedded Swift SDK for Wasm" default: "" + android_sdk_pre_build_command: + type: string + description: "Linux command to execute before building the Swift package with the Embedded Swift SDK for Android" + default: "" macos_pre_build_command: type: string description: "macOS command to execute before building the Swift package" @@ -80,6 +115,14 @@ on: type: string description: "macOS command to build and test the package" default: "xcrun swift test" + ios_pre_build_command: + type: string + description: "macOS command to execute before building the Swift package for iOS" + default: "" + ios_build_command: + type: string + description: "macOS command to build the package for iOS" + default: "xcrun swift build --build-tests --sdk \"$(xcrun --sdk iphoneos --show-sdk-path)\" --triple arm64-apple-ios" linux_build_command: type: string description: "Linux command to build and test the package" @@ -92,6 +135,18 @@ on: type: string description: "Command to use when building the package with the Swift SDK for Wasm" default: "swift build" + android_sdk_build_command: + type: string + description: "Command to use when building the package with the Swift SDK for Android" + default: "swift build" + android_sdk_triples: + type: string + description: "The triples to use when building with the Swift SDK for Android" + default: "[\"aarch64-unknown-linux-android28\", \"x86_64-unknown-linux-android28\"]" + android_ndk_versions: + type: string + description: "The NDK versions to use when building with the Swift SDK for Android" + default: "[\"r27d\"]" windows_pre_build_command: type: string description: "Windows Command Prompt command to execute before building the Swift package" @@ -106,6 +161,9 @@ on: macos_env_vars: description: "Newline separated list of environment variables" type: string + ios_host_env_vars: + description: "Newline separated list of environment variables" + type: string linux_env_vars: description: "Newline separated list of environment variables" type: string @@ -128,10 +186,18 @@ on: type: boolean description: "Boolean to enable building with the Embedded Swift SDK for Wasm. Defaults to false" default: false + enable_android_sdk_build: + type: boolean + description: "Boolean to enable building with the Swift SDK for Android. Defaults to false" + default: false enable_macos_checks: type: boolean description: "Boolean to enable macOS testing. Defaults to false" default: false + enable_ios_checks: + type: boolean + description: "Boolean to enable iOS testing. Defaults to false" + default: false enable_windows_checks: type: boolean description: "Boolean to enable windows testing. Defaults to true" @@ -144,6 +210,10 @@ on: type: boolean description: "Boolean to enable providing the GITHUB_TOKEN to downstream job." default: false + enable_cross_pr_testing: + type: boolean + description: "Whether PRs can be tested in combination with other PRs by mentioning them as `Linked PR: ` in the PR description" + default: false jobs: macos-build: @@ -176,12 +246,50 @@ jobs: run: echo "DEVELOPER_DIR=/Applications/Xcode_${{ matrix.xcode_version }}.app" >> $GITHUB_ENV - name: Swift version run: xcrun swift --version + - name: Clang version + run: xcrun clang --version - name: Pre-build run: ${{ inputs.macos_pre_build_command }} - name: Build / Test run: ${{ inputs.macos_build_command }} ${{ (contains(matrix.swift_version, 'nightly') && inputs.swift_nightly_flags) || inputs.swift_flags }} timeout-minutes: 60 + ios-build: + name: iOS (Build Only, Xcode ${{ matrix.xcode_version }} - ${{ matrix.os_version }} - ${{ matrix.arch }}) + if: ${{ inputs.enable_ios_checks }} + runs-on: [self-hosted, macos, "${{ matrix.os_version }}", "${{ matrix.arch }}"] + strategy: + fail-fast: false + matrix: + xcode_version: ${{ fromJson(inputs.ios_host_xcode_versions || inputs.macos_xcode_versions) }} + os_version: ${{ fromJson(inputs.ios_host_versions || inputs.macos_versions) }} + arch: ${{ fromJson(inputs.ios_host_archs || inputs.macos_archs) }} + exclude: + - ${{ fromJson(inputs.ios_host_exclude_xcode_versions || inputs.macos_exclude_xcode_versions) }} + steps: + - name: Checkout repository + uses: actions/checkout@v4 + - name: Provide token + if: ${{ inputs.needs_token }} + run: | + echo "GITHUB_TOKEN=${{ secrets.GITHUB_TOKEN }}" >> $GITHUB_ENV + - name: Set environment variables + if: ${{ inputs.ios_host_env_vars }} + run: | + for i in "${{ inputs.ios_host_env_vars }}" + do + printf "%s\n" $i >> $GITHUB_ENV + done + - name: Select Xcode + run: echo "DEVELOPER_DIR=/Applications/Xcode_${{ matrix.xcode_version }}.app" >> $GITHUB_ENV + - name: Swift version + run: xcrun swift --version + - name: Pre-build + run: ${{ inputs.ios_pre_build_command }} + - name: Build + run: ${{ inputs.ios_build_command }} ${{ (contains(matrix.swift_version, 'nightly') && inputs.swift_nightly_flags) || inputs.swift_flags }} + timeout-minutes: 60 + linux-build: name: Linux (${{ matrix.swift_version }} - ${{ matrix.os_version }}) if: ${{ inputs.enable_linux_checks }} @@ -198,16 +306,44 @@ jobs: steps: - name: Swift version run: swift --version + - name: Clang version + run: clang --version - name: Checkout repository uses: actions/checkout@v4 if: ${{ matrix.os_version != 'amazonlinux2' }} - name: Checkout repository uses: actions/checkout@v1 if: ${{ matrix.os_version == 'amazonlinux2' }} + - name: Checkout swiftlang/github-workflows repository + if: ${{ matrix.os_version != 'amazonlinux2' && github.repository != 'swiftlang/github-workflows' }} + uses: actions/checkout@v4 + with: + repository: swiftlang/github-workflows + path: github-workflows + - name: Checkout swiftlang/github-workflows repository + if: ${{ matrix.os_version == 'amazonlinux2' && github.repository != 'swiftlang/github-workflows' }} + uses: actions/checkout@v1 + with: + repository: swiftlang/github-workflows + path: ${{ github.event.repository.name }}/github-workflows + ref: main + - name: Determine script-root path + id: script_path + run: | + if [ "${{ github.repository }}" = "swiftlang/github-workflows" ]; then + echo "root=$GITHUB_WORKSPACE" >> $GITHUB_OUTPUT + else + echo "root=$GITHUB_WORKSPACE/github-workflows" >> $GITHUB_OUTPUT + fi - name: Provide token if: ${{ inputs.needs_token }} run: | echo "GITHUB_TOKEN=${{ secrets.GITHUB_TOKEN }}" >> $GITHUB_ENV + - name: Check out related PRs + if: ${{ inputs.enable_cross_pr_testing && github.event_name == 'pull_request' }} + run: | + cat ${{ steps.script_path.outputs.root }}/.github/workflows/scripts/cross-pr-checkout.swift > /tmp/cross-pr-checkout.swift + swift /tmp/cross-pr-checkout.swift "${{ github.repository }}" "${{ github.event.number }}" - name: Set environment variables if: ${{ inputs.linux_env_vars }} run: | @@ -229,17 +365,42 @@ jobs: matrix: swift_version: ${{ fromJson(inputs.linux_static_sdk_versions) }} os_version: ${{ fromJson(inputs.linux_os_versions) }} + exclude: + - ${{ fromJson(inputs.linux_static_sdk_exclude_swift_versions) }} container: image: ${{ (contains(matrix.swift_version, 'nightly') && 'swiftlang/swift') || 'swift' }}:${{ matrix.swift_version }}-${{ matrix.os_version }} steps: - name: Swift version run: swift --version + - name: Clang version + run: clang --version - name: Checkout repository uses: actions/checkout@v4 if: ${{ matrix.os_version != 'amazonlinux2' }} - name: Checkout repository uses: actions/checkout@v1 if: ${{ matrix.os_version == 'amazonlinux2' }} + - name: Checkout swiftlang/github-workflows repository + if: ${{ matrix.os_version != 'amazonlinux2' && github.repository != 'swiftlang/github-workflows' }} + uses: actions/checkout@v4 + with: + repository: swiftlang/github-workflows + path: github-workflows + - name: Checkout swiftlang/github-workflows repository + if: ${{ matrix.os_version == 'amazonlinux2' && github.repository != 'swiftlang/github-workflows' }} + uses: actions/checkout@v1 + with: + repository: swiftlang/github-workflows + path: ${{ github.event.repository.name }}/github-workflows + ref: main + - name: Determine script-root path + id: script_path + run: | + if [ "${{ github.repository }}" = "swiftlang/github-workflows" ]; then + echo "root=$GITHUB_WORKSPACE" >> $GITHUB_OUTPUT + else + echo "root=$GITHUB_WORKSPACE/github-workflows" >> $GITHUB_OUTPUT + fi - name: Provide token if: ${{ inputs.needs_token }} run: | @@ -258,20 +419,7 @@ jobs: BUILD_FLAGS: ${{ (contains(matrix.swift_version, 'nightly') && inputs.swift_nightly_flags) || inputs.swift_flags }} run: | ${{ inputs.linux_static_sdk_pre_build_command }} - if command -v apt-get >/dev/null 2>&1 ; then # bookworm, noble, jammy, focal - apt-get -q update && apt-get -yq install curl - elif command -v dnf >/dev/null 2>&1 ; then # rhel-ubi9 - dnf -y update - dnf -y install curl-minimal - elif command -v yum >/dev/null 2>&1 ; then # amazonlinux2 - yum -y update - yum -y install curl - else - echo "Unknown package manager (tried apt-get, dnf, yum)" >&2 - exit 1 - fi - curl -s --retry 3 https://raw.githubusercontent.com/swiftlang/github-workflows/refs/heads/main/.github/workflows/scripts/install-and-build-with-sdk.sh | \ - bash -s -- --static --flags="$BUILD_FLAGS" --build-command="${{ inputs.linux_static_sdk_build_command }}" ${{ matrix.swift_version }} + ${{ steps.script_path.outputs.root }}/.github/workflows/scripts/install-and-build-with-sdk.sh --static --flags="$BUILD_FLAGS" --build-command="${{ inputs.linux_static_sdk_build_command }}" ${{ matrix.swift_version }} wasm-sdk-build: name: Swift SDK for Wasm Build (${{ matrix.swift_version }} - ${{ matrix.os_version }}) @@ -289,12 +437,35 @@ jobs: steps: - name: Swift version run: swift --version + - name: Clang version + run: clang --version - name: Checkout repository uses: actions/checkout@v4 if: ${{ matrix.os_version != 'amazonlinux2' }} - name: Checkout repository uses: actions/checkout@v1 if: ${{ matrix.os_version == 'amazonlinux2' }} + - name: Checkout swiftlang/github-workflows repository + if: ${{ matrix.os_version != 'amazonlinux2' && github.repository != 'swiftlang/github-workflows' }} + uses: actions/checkout@v4 + with: + repository: swiftlang/github-workflows + path: github-workflows + - name: Checkout swiftlang/github-workflows repository + if: ${{ matrix.os_version == 'amazonlinux2' && github.repository != 'swiftlang/github-workflows' }} + uses: actions/checkout@v1 + with: + repository: swiftlang/github-workflows + path: ${{ github.event.repository.name }}/github-workflows + ref: main + - name: Determine script-root path + id: script_path + run: | + if [ "${{ github.repository }}" = "swiftlang/github-workflows" ]; then + echo "root=$GITHUB_WORKSPACE" >> $GITHUB_OUTPUT + else + echo "root=$GITHUB_WORKSPACE/github-workflows" >> $GITHUB_OUTPUT + fi - name: Provide token if: ${{ inputs.needs_token }} run: | @@ -313,20 +484,7 @@ jobs: BUILD_FLAGS: ${{ (contains(matrix.swift_version, 'nightly') && inputs.swift_nightly_flags) || inputs.swift_flags }} run: | ${{ inputs.wasm_sdk_pre_build_command }} - if command -v apt-get >/dev/null 2>&1 ; then # bookworm, noble, jammy, focal - apt-get -q update && apt-get -yq install curl - elif command -v dnf >/dev/null 2>&1 ; then # rhel-ubi9 - dnf -y update - dnf -y install curl-minimal - elif command -v yum >/dev/null 2>&1 ; then # amazonlinux2 - yum -y update - yum -y install curl - else - echo "Unknown package manager (tried apt-get, dnf, yum)" >&2 - exit 1 - fi - curl -s --retry 3 https://raw.githubusercontent.com/swiftlang/github-workflows/refs/heads/main/.github/workflows/scripts/install-and-build-with-sdk.sh | \ - bash -s -- --wasm --flags="$BUILD_FLAGS" --build-command="${{ inputs.wasm_sdk_build_command }}" ${{ matrix.swift_version }} + ${{ steps.script_path.outputs.root }}/.github/workflows/scripts/install-and-build-with-sdk.sh --wasm --flags="$BUILD_FLAGS" --build-command="${{ inputs.wasm_sdk_build_command }}" ${{ matrix.swift_version }} embedded-wasm-sdk-build: name: Embedded Swift SDK for Wasm Build (${{ matrix.swift_version }} - ${{ matrix.os_version }}) @@ -344,8 +502,35 @@ jobs: steps: - name: Swift version run: swift --version + - name: Clang version + run: clang --version - name: Checkout repository uses: actions/checkout@v4 + if: ${{ matrix.os_version != 'amazonlinux2' }} + - name: Checkout repository + uses: actions/checkout@v1 + if: ${{ matrix.os_version == 'amazonlinux2' }} + - name: Checkout swiftlang/github-workflows repository + if: ${{ matrix.os_version != 'amazonlinux2' && github.repository != 'swiftlang/github-workflows' }} + uses: actions/checkout@v4 + with: + repository: swiftlang/github-workflows + path: github-workflows + - name: Checkout swiftlang/github-workflows repository + if: ${{ matrix.os_version == 'amazonlinux2' && github.repository != 'swiftlang/github-workflows' }} + uses: actions/checkout@v1 + with: + repository: swiftlang/github-workflows + path: ${{ github.event.repository.name }}/github-workflows + ref: main + - name: Determine script-root path + id: script_path + run: | + if [ "${{ github.repository }}" = "swiftlang/github-workflows" ]; then + echo "root=$GITHUB_WORKSPACE" >> $GITHUB_OUTPUT + else + echo "root=$GITHUB_WORKSPACE/github-workflows" >> $GITHUB_OUTPUT + fi - name: Provide token if: ${{ inputs.needs_token }} run: | @@ -364,20 +549,73 @@ jobs: BUILD_FLAGS: ${{ (contains(matrix.swift_version, 'nightly') && inputs.swift_nightly_flags) || inputs.swift_flags }} run: | ${{ inputs.wasm_sdk_pre_build_command }} - if command -v apt-get >/dev/null 2>&1 ; then # bookworm, noble, jammy, focal - apt-get -q update && apt-get -yq install curl - elif command -v dnf >/dev/null 2>&1 ; then # rhel-ubi9 - dnf -y update - dnf -y install curl-minimal - elif command -v yum >/dev/null 2>&1 ; then # amazonlinux2 - yum -y update - yum -y install curl + ${{ steps.script_path.outputs.root }}/.github/workflows/scripts/install-and-build-with-sdk.sh --embedded-wasm --flags="$BUILD_FLAGS" ${{ matrix.swift_version }} + + android-sdk-build: + name: Swift SDK for Android Build (${{ matrix.swift_version }} - ${{ matrix.os_version }} - NDK ${{ matrix.ndk_version }}) + if: ${{ inputs.enable_android_sdk_build }} + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + swift_version: ${{ fromJson(inputs.android_sdk_versions) }} + ndk_version: ${{ fromJson(inputs.android_ndk_versions) }} + os_version: ${{ fromJson(inputs.linux_os_versions) }} + exclude: + - ${{ fromJson(inputs.android_exclude_swift_versions) }} + container: + image: ${{ (contains(matrix.swift_version, 'nightly') && 'swiftlang/swift') || 'swift' }}:${{ matrix.swift_version }}-${{ matrix.os_version }} + steps: + - name: Swift version + run: swift --version + - name: Clang version + run: clang --version + - name: Checkout repository + uses: actions/checkout@v4 + if: ${{ matrix.os_version != 'amazonlinux2' }} + - name: Checkout repository + uses: actions/checkout@v1 + if: ${{ matrix.os_version == 'amazonlinux2' }} + - name: Checkout swiftlang/github-workflows repository + if: ${{ matrix.os_version != 'amazonlinux2' && github.repository != 'swiftlang/github-workflows' }} + uses: actions/checkout@v4 + with: + repository: swiftlang/github-workflows + path: github-workflows + - name: Checkout swiftlang/github-workflows repository + if: ${{ matrix.os_version == 'amazonlinux2' && github.repository != 'swiftlang/github-workflows' }} + uses: actions/checkout@v1 + with: + repository: swiftlang/github-workflows + path: ${{ github.event.repository.name }}/github-workflows + ref: main + - name: Determine script-root path + id: script_path + run: | + if [ "${{ github.repository }}" = "swiftlang/github-workflows" ]; then + echo "root=$GITHUB_WORKSPACE" >> $GITHUB_OUTPUT else - echo "Unknown package manager (tried apt-get, dnf, yum)" >&2 - exit 1 + echo "root=$GITHUB_WORKSPACE/github-workflows" >> $GITHUB_OUTPUT fi - curl -s --retry 3 https://raw.githubusercontent.com/swiftlang/github-workflows/refs/heads/main/.github/workflows/scripts/install-and-build-with-sdk.sh | \ - bash -s -- --embedded-wasm --flags="$BUILD_FLAGS" ${{ matrix.swift_version }} + - name: Provide token + if: ${{ inputs.needs_token }} + run: | + echo "GITHUB_TOKEN=${{ secrets.GITHUB_TOKEN }}" >> $GITHUB_ENV + - name: Set environment variables + if: ${{ inputs.linux_env_vars }} + run: | + for i in "${{ inputs.linux_env_vars }}" + do + printf "%s\n" $i >> $GITHUB_ENV + done + - name: Pre-build + run: ${{ inputs.linux_pre_build_command }} + - name: Install Swift SDK for Android and build + env: + BUILD_FLAGS: ${{ (contains(matrix.swift_version, 'nightly') && inputs.swift_nightly_flags) || inputs.swift_flags }} + run: | + ${{ inputs.android_sdk_pre_build_command }} + ${{ steps.script_path.outputs.root }}/.github/workflows/scripts/install-and-build-with-sdk.sh --android --flags="$BUILD_FLAGS" --build-command="${{ inputs.android_sdk_build_command }}" --android-sdk-triple=${{ join(fromJson(inputs.android_sdk_triples), ' --android-sdk-triple=') }} --android-ndk-version="${{ matrix.ndk_version }}" ${{ matrix.swift_version }} windows-build: name: Windows (${{ matrix.swift_version }} - windows-2022) @@ -392,6 +630,26 @@ jobs: steps: - name: Checkout repository uses: actions/checkout@v4 + - name: Checkout swiftlang/github-workflows repository + if: ${{ github.repository != 'swiftlang/github-workflows' }} + uses: actions/checkout@v4 + with: + repository: swiftlang/github-workflows + path: github-workflows + - name: Determine script-root path + id: script_path + run: | + if ("${{ inputs.enable_windows_docker }}" -eq "true") { + $source = "C:\source" + } else { + $source = $env:GITHUB_WORKSPACE + } + + if ("${{ github.repository }}" -eq "swiftlang/github-workflows") { + echo "root=$source" >> $env:GITHUB_OUTPUT + } else { + echo "root=$source/github-workflows" >> $env:GITHUB_OUTPUT + } - name: Provide token if: ${{ inputs.needs_token }} run: | @@ -416,17 +674,10 @@ jobs: echo "image=$Image" >> "$env:GITHUB_OUTPUT" - name: Install Visual Studio Build Tools if: ${{ !inputs.enable_windows_docker }} - run: | - Invoke-WebRequest -Uri https://raw.githubusercontent.com/swiftlang/github-workflows/refs/heads/main/.github/workflows/scripts/windows/install-vsb.ps1 -OutFile $env:TEMP\install-vsb.ps1 - . $env:TEMP\install-vsb.ps1 - del $env:TEMP\install-vsb.ps1 + run: . ${{ steps.script_path.outputs.root }}/.github/workflows/scripts/windows/install-vsb.ps1 - name: Install Swift if: ${{ !inputs.enable_windows_docker }} - run: | - Invoke-WebRequest -Uri https://raw.githubusercontent.com/swiftlang/github-workflows/refs/heads/main/.github/workflows/scripts/windows/swift/install-swift.ps1 -OutFile $env:TEMP\install-swift.ps1 - Invoke-WebRequest -Uri https://raw.githubusercontent.com/swiftlang/github-workflows/refs/heads/main/.github/workflows/scripts/windows/swift/install-swift-${{ matrix.swift_version }}.ps1 -OutFile $env:TEMP\install-swift-${{ matrix.swift_version }}.ps1 - . $env:TEMP\install-swift-${{ matrix.swift_version }}.ps1 - del $env:TEMP\install-swift*.ps1 + run: . ${{ steps.script_path.outputs.root }}/.github/workflows/scripts/windows/swift/install-swift-${{ matrix.swift_version }}.ps1 - name: Create test script run: | mkdir $env:TEMP\test-script @@ -448,7 +699,19 @@ jobs: } Invoke-Program swift --version Invoke-Program swift test --version + Invoke-Program clang --version Invoke-Program cd $Source + '@ >> $env:TEMP\test-script\run.ps1 + + if ("${{ inputs.enable_cross_pr_testing && github.event_name == 'pull_request' }}" -eq "true") { + echo @' + # Running in script mode fails on Windows (https://github.com/swiftlang/swift/issues/77263), compile and run the script. + Invoke-Program swiftc -sdk $env:SDKROOT ${{ steps.script_path.outputs.root }}/.github/workflows/scripts/cross-pr-checkout.swift -o $env:TEMP\cross-pr-checkout.exe + Invoke-Program $env:TEMP\cross-pr-checkout.exe "${{ github.repository }}" "${{ github.event.number }}" + '@ >> $env:TEMP\test-script\run.ps1 + } + + echo @' ${{ inputs.windows_pre_build_command }} ${{ inputs.windows_build_command }} ${{ (contains(matrix.swift_version, 'nightly') && inputs.swift_nightly_flags) || inputs.swift_flags }} '@ >> $env:TEMP\test-script\run.ps1 diff --git a/.swift-format b/.swift-format new file mode 100644 index 00000000..41a022f2 --- /dev/null +++ b/.swift-format @@ -0,0 +1,18 @@ +{ + "version": 1, + "lineLength": 120, + "indentation": { + "spaces": 2 + }, + "lineBreakBeforeEachArgument": true, + "indentConditionalCompilationBlocks": false, + "prioritizeKeepingFunctionOutputTogether": true, + "rules": { + "AlwaysUseLowerCamelCase": false, + "AmbiguousTrailingClosureOverload": false, + "NoBlockComments": false, + "OrderedImports": true, + "UseLetInEveryBoundCaseVariable": false, + "UseSynthesizedInitializer": false + } +} diff --git a/README.md b/README.md index bb394483..8006682a 100644 --- a/README.md +++ b/README.md @@ -75,6 +75,25 @@ pre_build_command: "which example || (apt update -q && apt install -yq example" macOS platform support will be available soon. +#### Cross-PR testing + +To support testing of PRs together with PRs for one of the package’s dependencies, set add the following to your PR job. + +```yaml +with: + enable_cross_pr_testing: true +``` + +To reference a linked PR, add `Linked PR: ` to the PR description, eg. + +``` +Linked PR: https://github.com/swiftlang/swift-syntax/pull/2859 +// or alternatively +Linked PR: swiftlang/swift-syntax#2859 +``` + +Enabling cross-PR testing will add about 10s to PR testing time. + ## Running workflows locally You can run the Github Actions workflows locally using