Thanks to visit codestin.com
Credit goes to github.com

Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
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
33 changes: 16 additions & 17 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -327,23 +327,22 @@ jobs:
- run:
name: Load docker image
command: docker load -i target/localstack-docker-image-<< parameters.platform >>.tar
- when:
condition:
equal: [ "amd64", << parameters.platform >>]
steps:
- run:
name: compute-src-hashes
command: |
find tests/aws/services/lambda_/functions/common -type f -path '**/src/**' -not -path '*/.*' | xargs sha256sum > /tmp/common-functions-checksums
- restore_cache:
key: common-functions-{{ checksum "/tmp/common-functions-checksums" }}
- run:
name: pre-build lambda common test packages
command: ./scripts/build_common_test_functions.sh `pwd`/tests/aws/services/lambda_/functions/common
- save_cache:
key: common-functions-{{ checksum "/tmp/common-functions-checksums" }}
paths:
- "tests/aws/services/lambda_/functions/common"
# Prebuild and cache Lambda multiruntime test functions, supporting both architectures: amd64 and arm64
# Currently, all runners prebuild the Lambda functions, not just the one(s) executing Lambda multiruntime tests.
- run:
name: Compute Lambda build hashes
# Any change in the Lambda function source code (i.e., **/src/**) or build process (i.e., **/Makefile) invalidates the cache
command: |
find tests/aws/services/lambda_/functions/common -type f \( -path '**/src/**' -o -path '**/Makefile' \) | xargs sha256sum > /tmp/common-functions-checksums
- restore_cache:
key: common-functions-<< parameters.platform >>-{{ checksum "/tmp/common-functions-checksums" }}
- run:
name: Pre-build Lambda common test packages
command: ./scripts/build_common_test_functions.sh `pwd`/tests/aws/services/lambda_/functions/common
- save_cache:
key: common-functions-<< parameters.platform >>-{{ checksum "/tmp/common-functions-checksums" }}
paths:
- "tests/aws/services/lambda_/functions/common"
- prepare-pytest-tinybird
- prepare-account-region-randomization
- run:
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/tests-pro-integration.yml
Original file line number Diff line number Diff line change
Expand Up @@ -248,7 +248,7 @@ jobs:
with:
path: |
localstack/tests/aws/services/lambda_/functions/common
key: community-it-${{ runner.os }}-lambda-common-${{ hashFiles('localstack/tests/aws/services/lambda_/functions/common/**/src/*') }}
key: community-it-${{ runner.os }}-${{ runner.arch }}-lambda-common-${{ hashFiles('tests/aws/services/lambda_/functions/common/**/src/*', 'tests/aws/services/lambda_/functions/common/**/Makefile') }}

- name: Prebuild lambda common packages
working-directory: localstack
Expand Down
62 changes: 35 additions & 27 deletions localstack/services/lambda_/runtimes.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
"""This Lambda Runtimes reference defines everything around Lambda runtimes to facilitate adding new runtimes."""
from localstack.aws.api.lambda_ import Runtime

# LocalStack Lambda runtimes support policy
# We support all Lambda runtimes currently actively supported at AWS.
# Further, we aim to provide best-effort support for deprecated runtimes at least until function updates are blocked,
# ideally a bit longer to help users migrate their Lambda runtimes. However, we do not actively test them anymore.

# HOWTO add a new Lambda runtime:
# 1. Update botocore and generate the Lambda API stubs using `python3 -m localstack.aws.scaffold upgrade`
# => This usually happens automatically through the Github Action "Update ASF APIs"
Expand All @@ -10,6 +15,7 @@
# c) `SNAP_START_SUPPORTED_RUNTIMES` if supported (currently only new Java runtimes)
# 3. Re-create snapshots for Lambda tests with the marker @markers.lambda_runtime_update
# => Filter the tests using pytest -m lambda_runtime_update (i.e., additional arguments in PyCharm)
# Depending on the runtime, `test_lambda_runtimes.py` might require further snapshot updates.
# 4. Add the new runtime to these variables below:
# a) `VALID_RUNTIMES` matching the order of the snapshots
# b) `VALID_LAYER_RUNTIMES` matching the order of the snapshots
Expand All @@ -25,50 +31,58 @@
# a) AWS Lambda runtimes: https://docs.aws.amazon.com/lambda/latest/dg/lambda-runtimes.html
# b) Amazon ECR Lambda images: https://gallery.ecr.aws/lambda
# => Synchronize the order with the "Supported runtimes" under "AWS Lambda runtimes" (a)
# => Add comments for deprecated runtimes using <Phase 1> => <Phase 2>
# => Add comments for deprecated runtimes using <Deprecation date> => <Block function create> => <Block function update>
IMAGE_MAPPING: dict[Runtime, str] = {
# "nodejs22.x": "nodejs:22", expected November 2024
Runtime.nodejs20_x: "nodejs:20",
Runtime.nodejs18_x: "nodejs:18",
Runtime.nodejs16_x: "nodejs:16",
Runtime.nodejs14_x: "nodejs:14",
Runtime.nodejs12_x: "nodejs:12", # deprecated Mar 31, 2023 => Apr 30, 2023
Runtime.nodejs14_x: "nodejs:14", # deprecated Dec 4, 2023 => Jan 9, 2024 => Feb 8, 2024
Runtime.nodejs12_x: "nodejs:12", # deprecated Mar 31, 2023 => Mar 31, 2023 => Apr 30, 2023
# "python3.13": "python:3.13", expected November 2024
Runtime.python3_12: "python:3.12",
Runtime.python3_11: "python:3.11",
Runtime.python3_10: "python:3.10",
Runtime.python3_9: "python:3.9",
Runtime.python3_8: "python:3.8",
Runtime.python3_7: "python:3.7",
Runtime.python3_7: "python:3.7", # deprecated Dec 4, 2023 => Jan 9, 2024 => Feb 8, 2024
Runtime.java21: "java:21",
Runtime.java17: "java:17",
Runtime.java11: "java:11",
Runtime.java8_al2: "java:8.al2",
Runtime.java8: "java:8",
# "dotnet8": "dotnet:8", expected January 2024
Runtime.java8: "java:8", # deprecated Jan 8, 2024 => Feb 8, 2024 => Mar 12, 2024
# Runtime.dotnet8: "dotnet:8", # TODO
# dotnet7 (container only)
Runtime.dotnet6: "dotnet:6",
Runtime.dotnetcore3_1: "dotnet:core3.1", # deprecated Apr 3, 2023 => May 3, 2023
Runtime.go1_x: "go:1",
# "ruby3.3": "ruby:3.3", expected March 2024
Runtime.dotnetcore3_1: "dotnet:core3.1", # deprecated Apr 3, 2023 => Apr 3, 2023 => May 3, 2023
Runtime.go1_x: "go:1", # deprecated Jan 8, 2024 => Feb 8, 2024 => Mar 12, 2024
# "ruby3.3": "ruby:3.3", expected April 2024
Runtime.ruby3_2: "ruby:3.2",
Runtime.ruby2_7: "ruby:2.7",
Runtime.ruby2_7: "ruby:2.7", # deprecated Dec 7, 2023 => Jan 9, 2024 => Feb 8, 2024
Runtime.provided_al2023: "provided:al2023",
Runtime.provided_al2: "provided:al2",
Runtime.provided: "provided:alami",
Runtime.provided: "provided:alami", # deprecated Jan 8, 2024 => Feb 8, 2024 => Mar 12, 2024
}

# An unordered list of all deprecated Lambda runtimes that are still supported in LocalStack.
# A list of all deprecated Lambda runtimes, ideally ordered by deprecation date (following the AWS docs).
# LocalStack can still provide best-effort support.
DEPRECATED_RUNTIMES: list[Runtime] = [
Runtime.nodejs12_x, # deprecated Mar 31, 2023 => Apr 30, 2023
Runtime.dotnetcore3_1, # deprecated Apr 3, 2023 => May 3, 2023
# deprecate once snapshot tests show that it really happened
# Runtime.python3_7, # deprecated Nov 27, 2023 => ???
# Runtime.nodejs14_x, # deprecated Nov 27, 2023 => ???
Runtime.java8, # deprecated Jan 8, 2024 => Feb 8, 2024 => Mar 12, 2024
Runtime.go1_x, # deprecated Jan 8, 2024 => Feb 8, 2024 => Mar 12, 2024
Runtime.provided, # deprecated Jan 8, 2024 => Feb 8, 2024 => Mar 12, 2024
Runtime.ruby2_7, # deprecated Dec 7, 2023 => Jan 9, 2024 => Feb 8, 2024
Runtime.nodejs14_x, # deprecated Dec 4, 2023 => Jan 9, 2024 => Feb 8, 2024
Runtime.python3_7, # deprecated Dec 4, 2023 => Jan 9, 2024 => Feb 8, 2024
Runtime.dotnetcore3_1, # deprecated Apr 3, 2023 => Apr 3, 2023 => May 3, 2023
Runtime.nodejs12_x, # deprecated Mar 31, 2023 => Mar 31, 2023 => Apr 30, 2023
]
# An unordered list of all AWS-supported runtimes.
SUPPORTED_RUNTIMES: list[Runtime] = list(set(IMAGE_MAPPING.keys()) - set(DEPRECATED_RUNTIMES))

# A temporary list of missing runtimes not yet supported in LocalStack. Used for modular updates.
# TODO: add Dotnet8 runtime
MISSING_RUNTIMES = [Runtime.dotnet8]
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be removed with the follow-up addition in the Dotnet8 PR


# An unordered list of all Lambda runtimes supported by LocalStack.
ALL_RUNTIMES: list[Runtime] = list(IMAGE_MAPPING.keys())

Expand All @@ -79,33 +93,27 @@
Runtime.nodejs20_x,
Runtime.nodejs18_x,
Runtime.nodejs16_x,
Runtime.nodejs14_x,
],
"python": [
Runtime.python3_12,
Runtime.python3_11,
Runtime.python3_10,
Runtime.python3_9,
Runtime.python3_8,
Runtime.python3_7,
],
"java": [
Runtime.java21,
Runtime.java17,
Runtime.java11,
Runtime.java8_al2,
Runtime.java8,
],
"ruby": [
Runtime.ruby3_2,
Runtime.ruby2_7,
],
"dotnet": [Runtime.dotnet6],
"go": [Runtime.go1_x],
"custom": [
"dotnet": [Runtime.dotnet6], # TODO: Runtime.dotnet8
"provided": [
Runtime.provided_al2023,
Runtime.provided_al2,
Runtime.provided,
],
}

Expand All @@ -119,6 +127,6 @@
SNAP_START_SUPPORTED_RUNTIMES = [Runtime.java11, Runtime.java17, Runtime.java21]

# An ordered list of all Lambda runtimes considered valid by AWS. Matching snapshots in test_create_lambda_exceptions
VALID_RUNTIMES: str = "[nodejs20.x, provided.al2023, python3.12, java17, provided, nodejs16.x, nodejs14.x, ruby2.7, python3.10, java11, python3.11, dotnet6, go1.x, java21, nodejs18.x, provided.al2, java8, java8.al2, ruby3.2, python3.7, python3.8, python3.9]"
VALID_RUNTIMES: str = "[nodejs20.x, provided.al2023, python3.12, java17, nodejs16.x, dotnet8, python3.10, java11, python3.11, dotnet6, java21, nodejs18.x, provided.al2, java8.al2, ruby3.2, python3.8, python3.9]"
# An ordered list of all Lambda runtimes for layers considered valid by AWS. Matching snapshots in test_layer_exceptions
VALID_LAYER_RUNTIMES: str = "[ruby2.6, dotnetcore1.0, python3.7, nodejs8.10, nasa, ruby2.7, python2.7-greengrass, dotnetcore2.0, python3.8, java21, dotnet6, dotnetcore2.1, python3.9, java11, nodejs6.10, provided, dotnetcore3.1, dotnet8, java17, nodejs, nodejs4.3, java8.al2, go1.x, nodejs20.x, go1.9, byol, nodejs10.x, provided.al2023, python3.10, java8, nodejs12.x, python3.11, nodejs8.x, python3.12, nodejs14.x, nodejs8.9, python3.13, nodejs16.x, provided.al2, nodejs4.3-edge, nodejs18.x, ruby3.2, python3.4, ruby2.5, python3.6, python2.7]"
VALID_LAYER_RUNTIMES: str = "[ruby2.6, dotnetcore1.0, python3.7, nodejs8.10, nasa, ruby2.7, python2.7-greengrass, dotnetcore2.0, python3.8, java21, dotnet6, dotnetcore2.1, python3.9, java11, nodejs6.10, provided, dotnetcore3.1, dotnet8, java17, nodejs, nodejs4.3, java8.al2, go1.x, nodejs20.x, go1.9, byol, nodejs10.x, provided.al2023, python3.10, java8, nodejs12.x, python3.11, nodejs8.x, python3.12, nodejs14.x, nodejs8.9, python3.13, nodejs16.x, provided.al2, nodejs4.3-edge, nodejs18.x, ruby3.2, python3.4, ruby3.3, ruby2.5, python3.6, python2.7]"
29 changes: 20 additions & 9 deletions localstack/testing/aws/lambda_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,10 @@
from pathlib import Path
from typing import TYPE_CHECKING, Literal, Mapping, Optional, Sequence, overload

from localstack import config
from localstack.services.lambda_.runtimes import RUNTIMES_AGGREGATED
from localstack.utils.files import load_file
from localstack.utils.platform import Arch, get_arch
from localstack.utils.strings import short_uid
from localstack.utils.sync import ShortCircuitWaitException, retry
from localstack.utils.testutil import get_lambda_log_events
Expand All @@ -35,21 +37,18 @@
**dict.fromkeys(RUNTIMES_AGGREGATED.get("nodejs"), "index.handler"),
**dict.fromkeys(RUNTIMES_AGGREGATED.get("ruby"), "function.handler"),
**dict.fromkeys(RUNTIMES_AGGREGATED.get("java"), "echo.Handler"),
**dict.fromkeys(RUNTIMES_AGGREGATED.get("custom"), "function.handler"),
**dict.fromkeys(RUNTIMES_AGGREGATED.get("go"), "main"),
"dotnetcore3.1": "dotnetcore31::dotnetcore31.Function::FunctionHandler", # TODO lets see if we can accumulate those
"dotnet6": "dotnet6::dotnet6.Function::FunctionHandler",
# The handler value does not matter unless the custom runtime reads it in some way but it is a required field.
**dict.fromkeys(RUNTIMES_AGGREGATED.get("provided"), "function.handler"),
"dotnet6": "dotnet6::dotnet6.Function::FunctionHandler", # TODO lets see if we can accumulate those
}

PACKAGE_FOR_RUNTIME = {
**dict.fromkeys(RUNTIMES_AGGREGATED.get("python"), "python"),
**dict.fromkeys(RUNTIMES_AGGREGATED.get("nodejs"), "nodejs"),
**dict.fromkeys(RUNTIMES_AGGREGATED.get("ruby"), "ruby"),
**dict.fromkeys(RUNTIMES_AGGREGATED.get("java"), "java"),
**dict.fromkeys(RUNTIMES_AGGREGATED.get("custom"), "provided"),
**dict.fromkeys(RUNTIMES_AGGREGATED.get("go"), "go"),
**dict.fromkeys(RUNTIMES_AGGREGATED.get("provided"), "provided"),
"dotnet6": "dotnet6",
"dotnetcore3.1": "dotnetcore3.1",
}


Expand Down Expand Up @@ -94,7 +93,7 @@ def package_for_lang(scenario: str, runtime: str, root_folder: Path) -> str:
generic_runtime_dir_candidate = scenario_dir / runtime_folder

# if a more specific folder exists, use that one
# otherwise: try to fall back to generic runtime (e.g. python for python3.9)
# otherwise: try to fall back to generic runtime (e.g. python for python3.12)
if runtime_dir_candidate.exists() and runtime_dir_candidate.is_dir():
runtime_dir = runtime_dir_candidate
else:
Expand All @@ -109,7 +108,19 @@ def package_for_lang(scenario: str, runtime: str, root_folder: Path) -> str:
return package_path

# packaging
result = subprocess.run(["make", "build"], cwd=runtime_dir)
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

L90 above: We could consider renaming common to multiruntime to make the reuse a bit more explicit.

# Use the default Lambda architecture x86_64 unless the ignore architecture flag is configured.
# This enables local testing of both architectures on multi-architecture platforms such as Apple Silicon machines.
architecture = "x86_64"
if config.LAMBDA_IGNORE_ARCHITECTURE:
architecture = "arm64" if get_arch() == Arch.arm64 else "x86_64"
build_cmd = ["make", "build", f"ARCHITECTURE={architecture}"]
LOG.debug(
"Building Lambda function for scenario %s and runtime %s using %s.",
scenario,
runtime,
" ".join(build_cmd),
)
result = subprocess.run(build_cmd, cwd=runtime_dir)
if result.returncode != 0:
raise Exception(
f"Failed to build multiruntime {scenario=} for {runtime=} with error code: {result.returncode}"
Expand Down
1 change: 1 addition & 0 deletions scripts/build_common_test_functions.sh
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ for scenario in */ ; do
# skip if zip file exists, otherwise run makefile
[ -f "handler.zip" ] && echo "found handler.zip => skipping" && continue
echo -n "building ..."
# MAYBE: consider printing build logs only if the build fails (using CircleCI SSH seems easier for now)
make build >/dev/null

# if no zipfile, package build folder
Expand Down
10 changes: 10 additions & 0 deletions tests/aws/services/lambda_/functions/common/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# Top-level Makefile to invoke all make targets in sub-directories

# Based on https://stackoverflow.com/a/72209214/6875981
SUBDIRS := $(patsubst %/,%,$(wildcard */))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice!


.PHONY: all $(MAKECMDGOALS) $(SUBDIRS)
$(MAKECMDGOALS) all: $(SUBDIRS)

$(SUBDIRS):
$(MAKE) -C $@ $(MAKECMDGOALS)
29 changes: 29 additions & 0 deletions tests/aws/services/lambda_/functions/common/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# Lambda Multiruntime Builds

This directory contains the source code and build instructions for Lambda multiruntime tests.
Example tests are available under `tests.aws.lambda_.functions.common`

Each scenario (e.g., "echo") has the following folder structure: `./<scenario>/runtime/`
A runtime can be an aggregated runtime defined in `runtimes.py` (e.g., "python") or
a specific runtime (e.g., "python3.12") if customizations are required.

Each runtime directory defines a `Makefile` that
* MUST define a `build` target that:
* a) creates a `build` directory containing all Lambda sources ready for packaging
* b) creates a `handler.zip` file with a Lambda deployment package
* SHOULD define an `ARCHITECTURE` parameter to overwrite the target architecture (i.e., `x86_64` or `arm64`)
if architecture-specific builds are required (e.g., Dotnet, Golang, Rust).
* By default, the Makefile should build a deployment package with the same architecture as the host.
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This default behavior is debatable:

  • It currently aims for compatibility. For example, the build on a Linux ARM machine should not fail.
  • It differs from the default Lambda behavior using x86_64 by default.

Hence, on multi-architecture platforms (e.g., Apple Silicon), x86_64 builds require a parameter make ARCHITECTURE=x86_64.

* However, for testing on multi-architecture platforms, we should be able to overwrite the `ARCHITECTURE` parameter.
* We need to standardize `uname -m` into `ARCHITECTURE` because the output differs depending on the platform.
Ubuntu yields `aarch64` and macOS yields `arm64`.
* If we want to support dev systems without the `uname` utility, we could add `|| echo x86_64` to the uname detection.
* SHOULD define a `clean` target that deletes any build artefacts, including the `handler.zip`.
This helps a lot during development to tidy up and invalidate caching.

Checkout the [AWS guides](https://docs.aws.amazon.com/lambda/latest/dg/gettingstarted-package.html) on
"Building with <language>" (e.g., "Building with Java") for instructions how to
build Lambda deployment packages correctly.

The top-level and intermediary directories provided a meta-Makefile that automatically invokes sub-Makefiles such that
we can run `make clean` at the top-level recursively.
10 changes: 10 additions & 0 deletions tests/aws/services/lambda_/functions/common/echo/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# Top-level Makefile to invoke all make targets in sub-directories

# Based on https://stackoverflow.com/a/72209214/6875981
SUBDIRS := $(patsubst %/,%,$(wildcard */))

.PHONY: all $(MAKECMDGOALS) $(SUBDIRS)
$(MAKECMDGOALS) all: $(SUBDIRS)

$(SUBDIRS):
$(MAKE) -C $@ $(MAKECMDGOALS)
15 changes: 13 additions & 2 deletions tests/aws/services/lambda_/functions/common/echo/dotnet6/Makefile
Original file line number Diff line number Diff line change
@@ -1,9 +1,20 @@
UNAME := $(shell uname -m)
ifeq ($(UNAME),x86_64)
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wished this could be shorter but Ubuntu (in CI) yields aarch64 and macOS yields arm64, so we need the conditional to standardize the UNAME architecture :(

ARCHITECTURE ?= x86_64
else
ARCHITECTURE ?= arm64
endif
DOCKER_PLATFORM ?= linux/$(ARCHITECTURE)
# The packaging function architecture is x86_64 by default and needs to be set explicitly for arm64
# https://github.com/aws/aws-extensions-for-dotnet-cli/blob/cdd490450e0407139d49248d94a4a899367e84df/src/Amazon.Lambda.Tools/LambdaDefinedCommandOptions.cs#L111
FUNCTION_ARCHITECTURE ?= $(ARCHITECTURE)
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Dotnet is a good example why an explicit ARCHITECTURE flag is needed:

  • In general, the --platform=$(DOCKER_PLATFORM) flag is needed for cross-architecture on multi-architecture platforms
  • The Dotnet build specifically requires the --function-architecture flag for ARM build. It does not detect the right architecture automatically (unlike Golang builds). Therefore, being consistently verbose clarifies the build options.


# https://gallery.ecr.aws/sam/build-dotnet6
IMAGE ?= public.ecr.aws/sam/build-dotnet6:1.103.0
IMAGE ?= public.ecr.aws/sam/build-dotnet6:1.112.0

build:
mkdir -p build && \
docker run --rm -v $$(pwd)/src:/app -v $$(pwd)/build:/out $(IMAGE) bash -c "mkdir -p /app2 && cp /app/* /app2 && cd /app2 && dotnet lambda package -o ../out/handler.zip" && \
docker run --rm --platform=$(DOCKER_PLATFORM) -v $$(pwd)/src:/app -v $$(pwd)/build:/out $(IMAGE) bash -c "mkdir -p /app2 && cp /app/* /app2 && cd /app2 && dotnet lambda package --function-architecture $(FUNCTION_ARCHITECTURE) -o ../out/handler.zip" && \
cp build/handler.zip handler.zip

clean:
Expand Down
13 changes: 0 additions & 13 deletions tests/aws/services/lambda_/functions/common/echo/go/Makefile

This file was deleted.

This file was deleted.

14 changes: 0 additions & 14 deletions tests/aws/services/lambda_/functions/common/echo/go/src/main.go

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@

build:
mkdir -p build && \
cp -r ./src/* build/
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@

build:
mkdir build && \
cp -r ./src/* build/
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# Top-level Makefile to invoke all make targets in sub-directories

# Based on https://stackoverflow.com/a/72209214/6875981
SUBDIRS := $(patsubst %/,%,$(wildcard */))

.PHONY: all $(MAKECMDGOALS) $(SUBDIRS)
$(MAKECMDGOALS) all: $(SUBDIRS)

$(SUBDIRS):
$(MAKE) -C $@ $(MAKECMDGOALS)
Loading