Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore: kickoff release #3786

Merged
merged 2 commits into from
Jul 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .github/CODEOWNERS
Validating CODEOWNERS rules …
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,7 @@

# Changes to Xcode / OS runtime versions run in CI/CD requires admin approval.
/.github/composite_actions/get_platform_parameters/action.yml @aws-amplify/amplify-ios-admins

# Changes to files in the api-dump or api-dump-test folder require admin approval.
api-dump/* @aws-amplify/amplify-ios-admins
api-dump-test/* @aws-amplify/amplify-ios-admins
223 changes: 223 additions & 0 deletions .github/workflows/api-breaking-changes-detection.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,223 @@
name: Public Interface Breakage Detection

on:
pull_request:

permissions:
contents: write
pull-requests: write

jobs:
build-and-check-api-breakage:
name: Build and Check API Breakage
runs-on: macos-latest

steps:
- name: Checkout repository
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 #v4.1.1
with:
ref: ${{ github.head_ref }} # Checkout the PR branch
fetch-depth: 1

- name: Fetch the branchs
run: |
git fetch origin ${{ github.sha }}


- name: Setup and Run Swift API Diff
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
# Define the list of exceptions to filter out
exceptions=(
'has been added as a new enum case$'
'is now with @_spi$'
)

# Define the mandatory patterns to filter out
mandatory_patterns=(
'^/\*'
'^$'
)

# Function to apply patterns with grep
apply_patterns() {
local input="$1"
local output="$input"

# Apply mandatory patterns
for pattern in "${mandatory_patterns[@]}"; do
output=$(echo "$output" | grep -v "$pattern")
done

# Apply exceptions
for exception in "${exceptions[@]}"; do
output=$(echo "$output" | grep -v "$exception")
done

echo "$output"
}

echo "Swift version: $(swift --version)"
echo "Swift package manager version: $(swift package --version)"
swift package resolve

# Ensure we are in the correct directory
cd $GITHUB_WORKSPACE

# Run swift-api-diff commands here directly
NEW_API_DIR=$(mktemp -d)
OLD_API_DIR=$(mktemp -d)
SDK_PATH=$(xcrun --show-sdk-path)

# Get all library module names
# Moduels with aws-crt-swift as dependency are not listed due to swift-api-digester's issue with analyzing C dependencies
modules=$(swift package dump-package | jq -r '.products | map(select(.name == "Amplify" or .name == "CoreMLPredictionsPlugin" or .name == "AWSDataStorePlugin" or .name == "AWSPluginsCore")) | map(.name) | .[]')
echo "Modules: $modules"

echo "Fetching old version..."
git fetch origin ${{ github.event.pull_request.base.sha }}
git checkout ${{ github.event.pull_request.base.sha }}
built=false
for module in $modules; do
# If file doesn't exits in the old directory
if [ ! -f api-dump/${module}.json ]; then
echo "Old API file does not exist in the base branch. Generating it..."
# Check if the project has been built
if ! $built; then
echo "Building project..."
swift build > /dev/null 2>&1 || { echo "Failed to build project"; exit 1; }
built=true
fi

# Generate the API file using api-digester
swift api-digester -sdk "$SDK_PATH" -dump-sdk -module "$module" -o "$OLD_API_DIR/${module}.json" -I .build/debug || { echo "Failed to dump new SDK for module $module"; exit 1; }
else
# Use the api-dump/${module}.json file from the base branch directly
cp "api-dump/${module}.json" "$OLD_API_DIR/${module}.json"
fi
done

echo "Fetching new version..."
git checkout ${{ github.sha }}
git log -1 # Print the commit info for debugging
swift build> /dev/null 2>&1 || { echo "Failed to build new version"; exit 1; }
for module in $modules; do
swift api-digester -sdk "$SDK_PATH" -dump-sdk -module "$module" -o "$NEW_API_DIR/${module}.json" -I .build/debug || { echo "Failed to dump new SDK for module $module"; exit 1; }
done

# Compare APIs for each module and capture the output
api_diff_output=""
for module in $modules; do
swift api-digester -sdk "$SDK_PATH" -diagnose-sdk --input-paths "$OLD_API_DIR/${module}.json" --input-paths "$NEW_API_DIR/${module}.json" >> "api-diff-report-${module}.txt" 2>&1
module_diff_output=$(apply_patterns "$(cat "api-diff-report-${module}.txt")")
if [ -n "$module_diff_output" ]; then
api_diff_output="${api_diff_output}\n**Module: ${module}**\n${module_diff_output}\n"

# Check if there are lines containing "has been renamed to Func"
if echo "$module_diff_output" | grep -q 'has been renamed to Func'; then
# Capture the line containing "has been renamed to Func"
renamed_line=$(echo "$module_diff_output" | grep 'has been renamed to Func')

# Append a message to the module_diff_output
api_diff_output="${api_diff_output}👉🏻 _Note: If you're just adding optional parameters to existing methods, neglect the line:_\n_${renamed_line}_\n"
fi
fi
done

echo "API_DIFF_OUTPUT<<EOF" >> $GITHUB_ENV
if [ -n "$api_diff_output" ]; then
echo "### 💔 Public API Breaking Change detected:" >> $GITHUB_ENV
echo -e "$api_diff_output" >> $GITHUB_ENV
echo "EOF" >> $GITHUB_ENV
else
echo "### ✅ No Public API Breaking Change detected" >> $GITHUB_ENV
echo "EOF" >> $GITHUB_ENV
fi

# Checkout to the branch associated with the pull request
git stash --include-untracked
git checkout ${{ github.head_ref }}

if [ ! -d "api-dump" ]; then
echo "api-dump folder does not exist. Creating it..."
mkdir -p "api-dump"
fi

# Update the api-dump folder of the new version by making a commit if there are changes
for module in $modules; do
if [ ! -f api-dump/${module}.json ]; then
echo "API file does not exist in api-dump folder. Creating it..."
echo "{}" > "api-dump/${module}.json"
fi
if ! diff "$NEW_API_DIR/${module}.json" "api-dump/${module}.json" > /dev/null; then
echo "Updating API Dumps..."
mv "$NEW_API_DIR/${module}.json" "api-dump/${module}.json"
fi
done

git config --global user.name "aws-amplify-ops"
git config --global user.email "[email protected]"

git add api-dump/*.json

if ! git diff --cached --quiet --exit-code; then
# Get the file names that have changes
changed_files=$(git diff --cached --name-only)

push_changes=false
for file in $changed_files; do
if [[ $file == api-dump/* ]]; then
# Get the number of lines in the file
total_lines=$(wc -l < "$file")
# Get the line numbers of the changes
changed_lines=$(git diff --cached -U0 "$file" | grep -o '@@ [^ ]* [^ ]* @@' | awk '{print $3}' | cut -d ',' -f1 | sed 's/[^0-9]//g')
echo "Changed lines in $file: $changed_lines"
# Check if any change is not within the last 10 lines
for line in $changed_lines; do
if [ "$line" -le "$((total_lines - 10))" ]; then
push_changes=true
break
fi
done

# If any file should be pushed, break out of the loop
if [ "$push_changes" = true ]; then
break
fi
fi
done

if [ "$push_changes" = true ]; then
git commit -m "Update API dumps for new version"
git push origin HEAD:${{ github.head_ref }}
else
echo "No changes to commit in the api-dump folder."
fi
else
echo "No changes to commit in the api-dump folder."
fi

git stash pop || true

- name: Comment on PR with API Diff
uses: actions/github-script@d7906e4ad0b1822421a7e6a35d5ca353c962f410 # v6.4.1
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
script: |
const apiDiffOutput = process.env.API_DIFF_OUTPUT;
const issueNumber = context.payload.pull_request.number;
const owner = context.repo.owner;
const repo = context.repo.repo;

if (apiDiffOutput && apiDiffOutput.trim().length > 0) {
github.rest.issues.createComment({
owner: owner,
repo: repo,
issue_number: issueNumber,
body: `## API Breakage Report\n${apiDiffOutput}\n`
});
} else {
console.log("No API diff output found.");
}

39 changes: 39 additions & 0 deletions .github/workflows/api_digester_check.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
name: Swift API Digester Functionality Check

on:
schedule:
- cron: '0 15 * * *' # This will run the action every day at 3:00 pm UTC (8:00 am PDT)
workflow_dispatch: # Allows manual triggering

jobs:
check-swift-api-digester:
runs-on: macos-latest

steps:
- name: Checkout repository
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 #v4.1.1

- name: Check API Digester
shell: bash
env:
WEBHOOK_URL: ${{ secrets.SLACK_API_CHECKER_WEBHOOK_URL }}
run: |
TEMP_DIR=$(mktemp -d)
echo "Temporary directory created at $TEMP_DIR"
SDK_PATH=$(xcrun --sdk macosx --show-sdk-path)
echo "SDK Path: $SDK_PATH"

# Run swift-api-digester
swift api-digester -sdk "$SDK_PATH" -diagnose-sdk --input-paths api-dump-test/A.json --input-paths api-dump-test/B.json >> "$TEMP_DIR/api-digester-output.txt" 2>&1

# Display the output
cat "$TEMP_DIR/api-digester-output.txt"

if diff "$TEMP_DIR/api-digester-output.txt" api-dump-test/expected-result.txt; then
echo "The output matches the expected result."
else
echo "The output does not match the expected result."
WORKFLOW_URL="https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}"
echo "$WORKFLOW_URL" | xargs -I {} curl -s POST "$WEBHOOK_URL" -H "Content-Type:application/json" --data '{"WORKFLOW_URL":"{}"}'
exit 1
fi
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,11 @@ public extension StorageListRequest {
@available(*, deprecated, message: "Use `path` in Storage API instead of `Options`")
public let path: String?

/// The strategy to use when listing contents from subpaths. Defaults to [`.include`](x-source-tag://SubpathStrategy.include)
///
/// - Tag: StorageListRequestOptions.subpathStrategy
public let subpathStrategy: SubpathStrategy

/// Number between 1 and 1,000 that indicates the limit of how many entries to fetch when
/// retreiving file lists from the server.
///
Expand Down Expand Up @@ -94,15 +99,47 @@ public extension StorageListRequest {
public init(accessLevel: StorageAccessLevel = .guest,
targetIdentityId: String? = nil,
path: String? = nil,
subpathStrategy: SubpathStrategy = .include,
pageSize: UInt = 1000,
nextToken: String? = nil,
pluginOptions: Any? = nil) {
self.accessLevel = accessLevel
self.targetIdentityId = targetIdentityId
self.path = path
self.subpathStrategy = subpathStrategy
self.pageSize = pageSize
self.nextToken = nextToken
self.pluginOptions = pluginOptions
}
}
}

public extension StorageListRequest.Options {
/// Represents the strategy used when listing contents from subpaths relative to the given path.
///
/// - Tag: StorageListRequestOptions.SubpathStrategy
enum SubpathStrategy {
/// Items from nested subpaths are included in the results
///
/// - Tag: SubpathStrategy.include
case include

/// Items from nested subpaths are not included in the results. Their subpaths are instead grouped under [`StorageListResult.excludedSubpaths`](StorageListResult.excludedSubpaths).
///
/// - Parameter delimitedBy: The delimiter used to determine subpaths. Defaults to `"/"`
///
/// - SeeAlso: [`StorageListResult.excludedSubpaths`](x-source-tag://StorageListResult.excludedSubpaths)
///
/// - Tag: SubpathStrategy.excludeWithDelimiter
case exclude(delimitedBy: String = "/")

/// Items from nested subpaths are not included in the results. Their subpaths are instead grouped under [`StorageListResult.excludedSubpaths`](StorageListResult.excludedSubpaths).
///
/// - SeeAlso: [`StorageListResult.excludedSubpaths`](x-source-tag://StorageListResult.excludedSubpaths)
///
/// - Tag: SubpathStrategy.exclude
public static var exclude: SubpathStrategy {
return .exclude()
}
}
}
14 changes: 13 additions & 1 deletion Amplify/Categories/Storage/Result/StorageListResult.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,13 @@ public struct StorageListResult {
/// [StorageCategoryBehavior.list](x-source-tag://StorageCategoryBehavior.list).
///
/// - Tag: StorageListResult.init
public init(items: [Item], nextToken: String? = nil) {
public init(
items: [Item],
excludedSubpaths: [String] = [],
nextToken: String? = nil
) {
self.items = items
self.excludedSubpaths = excludedSubpaths
self.nextToken = nextToken
}

Expand All @@ -27,6 +32,13 @@ public struct StorageListResult {
/// - Tag: StorageListResult.items
public var items: [Item]


/// Array of excluded subpaths in the Result.
/// This field is only populated when [`StorageListRequest.Options.subpathStrategy`](x-source-tag://StorageListRequestOptions.subpathStragegy) is set to [`.exclude()`](x-source-tag://SubpathStrategy.exclude).
///
/// - Tag: StorageListResult.excludedSubpaths
public var excludedSubpaths: [String]

/// Opaque string indicating the page offset at which to resume a listing. This value is usually copied to
/// [StorageListRequestOptions.nextToken](x-source-tag://StorageListRequestOptions.nextToken).
///
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ extension AWSS3StorageService {
}
let input = ListObjectsV2Input(bucket: bucket,
continuationToken: options.nextToken,
delimiter: nil,
delimiter: options.subpathStrategy.delimiter,
maxKeys: Int(options.pageSize),
prefix: finalPrefix,
startAfter: nil)
Expand All @@ -41,7 +41,20 @@ extension AWSS3StorageService {
let items = try contents.map {
try StorageListResult.Item(s3Object: $0, prefix: prefix)
}
return StorageListResult(items: items, nextToken: response.nextContinuationToken)

let commonPrefixes = response.commonPrefixes ?? []
let excludedSubpaths: [String] = commonPrefixes.compactMap {
guard let commonPrefix = $0.prefix else {
return nil
}
return String(commonPrefix.dropFirst(prefix.count))
}

return StorageListResult(
items: items,
excludedSubpaths: excludedSubpaths,
nextToken: response.nextContinuationToken
)
} catch let error as StorageErrorConvertible {
throw error.storageError
} catch {
Expand Down
Loading
Loading