Skip to content

Commit a3c4a2a

Browse files
committed
wip - moving to dual output for x64 and arm64
1 parent fe8ac94 commit a3c4a2a

File tree

3 files changed

+196
-44
lines changed

3 files changed

+196
-44
lines changed

.github/workflows/docker-workflow.yml

Lines changed: 114 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -7,42 +7,135 @@ on:
77
pull_request:
88
branches:
99
- main
10-
#schedule:
11-
#- cron: '0 0 * * *'
10+
workflow_dispatch: # Allows manual triggering
1211

1312
jobs:
14-
build:
13+
# Setup job to determine Sharp version and check release existence once
14+
setup:
1515
runs-on: ubuntu-latest
16+
outputs:
17+
sharp_version: ${{ steps.vars.outputs.sharp_version }}
18+
release_exists: ${{ steps.vars.outputs.release_exists }}
1619
steps:
1720
- name: Checkout
18-
uses: actions/checkout@v3
21+
uses: actions/checkout@v4
1922
with:
20-
fetch-depth: 0
21-
- name: Variables
23+
fetch-depth: 0 # Needed for tag checking
24+
25+
- name: Install jq
26+
run: sudo apt-get update && sudo apt-get install -y jq
27+
28+
- name: Get Sharp Version and Check Release
2229
id: vars
2330
run: |
2431
content=$(cat ./package-lock.json)
2532
sharp_version=$(echo $content | jq -r '.packages."node_modules/sharp".version')
26-
echo "sharp_version=$sharp_version"
33+
if [ -z "$sharp_version" ] || [ "$sharp_version" == "null" ]; then
34+
echo "Error: Could not extract sharp version from package-lock.json"
35+
exit 1
36+
fi
37+
echo "Determined Sharp version: $sharp_version"
2738
echo "sharp_version=$sharp_version" >> $GITHUB_OUTPUT
28-
release_exists="true"
29-
git show-ref --tags --quiet --verify -- "refs/tags/$sharp_version" || release_exists="false"
30-
echo "release_exists=$release_exists"
39+
40+
release_exists="false"
41+
# Check if tag exists locally first (faster)
42+
if git rev-parse "v${sharp_version}" >/dev/null 2>&1; then
43+
release_exists="true"
44+
else
45+
# If not local, check remote tags
46+
git ls-remote --tags origin | grep -q "refs/tags/v${sharp_version}$" && release_exists="true" || release_exists="false"
47+
fi
48+
49+
echo "Release tag v${sharp_version} exists: $release_exists"
3150
echo "release_exists=$release_exists" >> $GITHUB_OUTPUT
32-
echo "sharp_version=$sharp_version" >> $GITHUB_OUTPUT
33-
- name: Build
34-
id: docker_build
35-
uses: docker/build-push-action@v4
51+
52+
53+
# Build job using matrix for architectures
54+
build:
55+
needs: setup
56+
runs-on: ubuntu-latest
57+
strategy:
58+
matrix:
59+
# Define the platforms and corresponding simple architecture names
60+
include:
61+
- platform: linux/amd64
62+
arch: x64
63+
- platform: linux/arm64
64+
arch: arm64
65+
steps:
66+
- name: Checkout
67+
uses: actions/checkout@v4
68+
69+
- name: Set up QEMU
70+
uses: docker/setup-qemu-action@v3
71+
72+
- name: Set up Docker Buildx
73+
uses: docker/setup-buildx-action@v3
74+
75+
- name: Build Docker image for ${{ matrix.arch }}
76+
uses: docker/build-push-action@v6
3677
with:
3778
context: .
3879
file: ./Dockerfile
39-
tags: sharp-aws-lambda-layer:dev
40-
- name: Copy artifacts
41-
run: docker run -v "${{ github.workspace }}/dist":/dist sharp-aws-lambda-layer:dev
42-
- name: Create release
80+
# Specify the platform for the build
81+
platforms: ${{ matrix.platform }}
82+
# Pass the architecture name as a build argument
83+
build-args: |
84+
TARGET_ARCH=${{ matrix.arch }}
85+
# Load the image into the local Docker daemon to extract files
86+
# Alternatively, use 'outputs: type=local,dest=output-docker'
87+
# then copy from 'output-docker/dist/...' in the next step.
88+
# Loading is simpler here if we just need the zip.
89+
load: true
90+
tags: sharp-aws-lambda-layer:${{ matrix.arch }}-dev # Tag with arch
91+
92+
- name: Create dist directory
93+
run: mkdir -p dist # Create directory if it doesn't exist
94+
95+
- name: Copy artifact (${{ matrix.arch }})
96+
# Run a temporary container from the built image to copy the specific zip file
97+
run: |
98+
docker run --rm -v "${{ github.workspace }}/dist":/output \
99+
sharp-aws-lambda-layer:${{ matrix.arch }}-dev \
100+
cp /build/dist/sharp-layer-${{ matrix.arch }}.zip /output/
101+
102+
- name: Upload artifact (${{ matrix.arch }})
103+
uses: actions/upload-artifact@v4
104+
with:
105+
name: sharp-layer-${{ matrix.arch }} # Artifact name includes arch
106+
path: dist/sharp-layer-${{ matrix.arch }}.zip # Path to the specific zip file
107+
108+
# Release job runs after all builds complete
109+
release:
110+
needs: [setup, build] # Depends on setup for version and build for artifacts
111+
runs-on: ubuntu-latest
112+
# Only run on push to main, and if the release doesn't exist yet
113+
# You might want to adjust this condition (e.g., always create/overwrite?)
114+
if: github.event_name == 'push' && github.ref == 'refs/heads/main' # && needs.setup.outputs.release_exists == 'false'
115+
steps:
116+
- name: Download all artifacts
117+
uses: actions/download-artifact@v4
118+
with:
119+
# No name specified, downloads all artifacts from the workflow run
120+
path: dist # Download to 'dist' directory
121+
# Optional: Pattern matching if you have other artifacts
122+
# pattern: sharp-layer-*
123+
# merge-multiple: true # Merges artifacts if they have the same name (not needed here)
124+
125+
- name: List downloaded files
126+
run: ls -R dist
127+
128+
- name: Create or Update GitHub Release
43129
uses: svenstaro/upload-release-action@v2
44130
with:
45131
repo_token: ${{ secrets.GITHUB_TOKEN }}
46-
tag: ${{ steps.vars.outputs.sharp_version }}
47-
file: dist/sharp-layer.zip
48-
overwrite: true
132+
# Use the sharp version determined in the setup job
133+
tag: v${{ needs.setup.outputs.sharp_version }}
134+
release_name: Sharp v${{ needs.setup.outputs.sharp_version }} Layer
135+
# Upload all zip files found in the 'dist' directory's subdirectories
136+
# Adjust the pattern if download-artifact structure is different
137+
file: dist/sharp-layer-*/*.zip
138+
file_glob: true # Enable globbing for the 'file' input
139+
overwrite: true # Overwrite existing assets in the release if tag exists
140+
body: "AWS Lambda Layer for Sharp v${{ needs.setup.outputs.sharp_version }}. Includes builds for x64 (amd64) and arm64."
141+
# make_latest: true # Optionally mark as latest release

Dockerfile

Lines changed: 24 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,32 @@
1-
FROM public.ecr.aws/sam/build-nodejs22.x:latest
1+
# Use a multi-arch base image
2+
FROM public.ecr.aws/sam/build-nodejs20.x:latest
23

34
WORKDIR /build
45

5-
COPY * ./
6+
# Declare build argument for target architecture
7+
ARG TARGET_ARCH
68

7-
RUN npm --no-optional --no-audit --progress=false install
9+
# Verify the build environment architecture (optional debug step)
10+
RUN echo "Building on architecture: $(uname -m)"
11+
RUN echo "TARGET_ARCH argument: ${TARGET_ARCH}"
812

9-
RUN node ./node_modules/webpack/bin/webpack.js
13+
# Copy necessary files
14+
COPY package.json package-lock.json webpack.config.js ./
15+
# Copy other potential source files if needed by webpack or sharp install
16+
# COPY src ./src
1017

11-
RUN node -e "console.log(require('sharp'))"
18+
# Install dependencies - npm install inside the target arch container
19+
# Sharp's install script will use process.arch (e.g., 'x64', 'arm64')
20+
# derived from the container's architecture to fetch correct binaries.
21+
RUN npm --no-optional --no-audit --progress=false --arch=${TARGET_ARCH} --platform=linux install
1222

13-
RUN mkdir /dist && \
14-
echo "cp /build/dist/sharp-layer.zip /dist/sharp-layer.zip" > /entrypoint.sh && \
15-
chmod +x /entrypoint.sh
23+
# Run webpack, passing the TARGET_ARCH environment variable
24+
# Webpack config will use this to create arch-specific output
25+
RUN TARGET_ARCH=${TARGET_ARCH} node ./node_modules/webpack/bin/webpack.js
1626

17-
ENTRYPOINT "/entrypoint.sh"
27+
# Simple test to ensure sharp loads correctly for the target architecture
28+
RUN node -e "console.log(require('sharp')('./package.json'))"
29+
30+
# No entrypoint needed - we will copy the file out using 'docker run -v' or 'docker cp'
31+
# The final ZIP file is now expected to be in /build/dist/sharp-layer-${TARGET_ARCH}.zip
32+
# based on the updated webpack config.

webpack.config.js

Lines changed: 58 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -2,48 +2,92 @@ const path = require('path');
22
const CopyPlugin = require('copy-webpack-plugin');
33
const ZipPlugin = require('zip-webpack-plugin');
44

5-
const distPath = 'nodejs/node_modules/sharp';
5+
// Read the target architecture from the environment variable set in Dockerfile
6+
const targetArch = process.env.TARGET_ARCH; // Should be 'x64' or 'arm64'
7+
if (!targetArch) {
8+
throw new Error("TARGET_ARCH environment variable not set!");
9+
}
10+
11+
// Determine the correct platform (should always be linux in this build environment)
12+
const targetPlatform = 'linux'; // We are building in a linux container
13+
14+
// Construct the name of the native Sharp module file
15+
const nodeFileSuffix = `sharp-${targetPlatform}-${targetArch}.node`; // e.g., sharp-linux-x64.node or sharp-linux-arm64.node
16+
17+
// Define the output path structure for Lambda layers
18+
const layerBasePath = 'nodejs/node_modules/sharp';
19+
20+
console.log(`Webpack config: Building for ${targetPlatform}-${targetArch}`);
21+
console.log(`Webpack config: Expecting native module: ${nodeFileSuffix}`);
622

723
module.exports = {
8-
name: 'layer',
24+
name: `layer-${targetArch}`, // Give the config a name incorporating the arch
925
mode: 'production',
1026
stats: 'minimal',
1127
target: 'node',
1228
watch: false,
1329
entry: {
14-
[`${distPath}/index`]: './node_modules/sharp/lib/index',
30+
// Entry point for Sharp's main lib
31+
[`${layerBasePath}/index`]: './node_modules/sharp/lib/index.js', // Ensure .js is specified if needed
1532
},
1633
plugins: [
1734
new CopyPlugin({
1835
patterns: [
1936
{
20-
from: 'node_modules/sharp/build',
21-
to: `${distPath}/build`,
37+
// Copy the compiled native module build ('Release' directory)
38+
from: `node_modules/sharp/build/Release/${nodeFileSuffix}`,
39+
to: `${layerBasePath}/build/Release/${nodeFileSuffix}`, // Keep the same structure
2240
},
2341
{
2442
from: 'node_modules/sharp/LICENSE',
25-
to: distPath,
43+
to: layerBasePath,
2644
},
2745
{
46+
// Copy vendor libraries (like libvips) installed for the target arch
2847
from: `node_modules/sharp/vendor`,
29-
to: `${distPath}/vendor`,
48+
to: `${layerBasePath}/vendor`,
3049
},
50+
{
51+
// Copy Sharp's package.json - sometimes needed for runtime checks
52+
from: `node_modules/sharp/package.json`,
53+
to: layerBasePath,
54+
},
55+
// Add any other necessary files from sharp's node_modules directory
3156
],
3257
}),
3358
new ZipPlugin({
34-
filename: 'sharp-layer.zip',
59+
// Create an architecture-specific filename
60+
filename: `sharp-layer-${targetArch}.zip`,
61+
// Specify the base directory for files inside the zip
62+
// This ensures the 'nodejs/...' structure is at the root of the zip
63+
pathPrefix: '', // Default might be fine, but explicitly empty can help
3564
})
3665
],
3766
optimization: {
38-
// minimize will be enabled in Github Actions
39-
minimize: process.env.NODE_ENV === 'production',
67+
minimize: false, // Lambda layers generally shouldn't minimize node_modules internals
4068
},
4169
output: {
42-
filename: '[name].js',
43-
path: path.resolve(__dirname, 'dist'),
70+
filename: '[name].js', // Output filename for the entry point JS
71+
path: path.resolve(__dirname, 'dist'), // Output directory for webpack build artifacts (including the zip)
4472
libraryTarget: 'commonjs2',
4573
},
4674
externals: {
47-
'./sharp-linux-x64.node': './build/Release/sharp-linux-x64.node',
75+
// *** CRITICAL: Define the native module as external relative to its expected location ***
76+
// When sharp requires './build/Release/sharp-linux-x64.node' at runtime,
77+
// webpack will resolve it to `require('./sharp-linux-x64.node')` within the bundle context.
78+
// We are providing this file via the CopyPlugin into the correct relative path.
79+
// --- This section might not be strictly needed if CopyPlugin handles the .node file correctly ---
80+
// Let's comment it out initially, as CopyPlugin places the file correctly relative to index.js
81+
// './build/Release/sharp-linux-x64.node': 'commonjs ./build/Release/sharp-linux-x64.node',
82+
// './build/Release/sharp-linux-arm64.node': 'commonjs ./build/Release/sharp-linux-arm64.node'
83+
84+
// Update: Simpler approach - let CopyPlugin place the .node file.
85+
// Sharp's internal require path is usually relative like `require('../build/Release/sharp-....node')`
86+
// from within `lib/index.js`. Ensure the CopyPlugin maintains this structure.
87+
// The current CopyPlugin path seems correct for this.
88+
},
89+
// Ensure node module resolution works
90+
resolve: {
91+
modules: [path.resolve(__dirname, 'node_modules'), 'node_modules'],
4892
},
49-
};
93+
};

0 commit comments

Comments
 (0)