Skip to content

Commit d71ac5c

Browse files
Merge branch 'develop'
2 parents f032217 + 3870244 commit d71ac5c

File tree

17 files changed

+485
-96
lines changed

17 files changed

+485
-96
lines changed

aws_lambda_builders/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,5 +4,5 @@
44

55
# Changing version will trigger a new release!
66
# Please make the version change as the last step of your development.
7-
__version__ = "1.38.0"
7+
__version__ = "1.39.0"
88
RPC_PROTOCOL_VERSION = "0.3"

aws_lambda_builders/utils.py

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
"""
22
Common utilities for the library
33
"""
4-
4+
import locale
55
import logging
66
import os
77
import shutil
@@ -231,3 +231,27 @@ def extract_tarfile(tarfile_path: Union[str, os.PathLike], unpack_dir: Union[str
231231
raise tarfile.ExtractError("Attempted Path Traversal in Tar File")
232232

233233
tar.extractall(unpack_dir)
234+
235+
236+
def decode(to_decode: bytes, encoding: Optional[str] = None) -> str:
237+
"""
238+
Perform a "safe" decoding of a series of bytes. Attempts to find the localized encoding
239+
if not provided, and avoids raising an exception, instead, if an unrecognized character
240+
is found, replaces it with a replacement character.
241+
242+
https://docs.python.org/3/library/codecs.html#codec-base-classes
243+
244+
Parameters
245+
----------
246+
to_decode: bytes
247+
Series of bytes to be decoded
248+
encoding: Optional[str]
249+
Encoding type. If None, will attempt to find the correct encoding based on locale.
250+
251+
Returns
252+
-------
253+
str
254+
Decoded string with unrecognized characters replaced with a replacement character
255+
"""
256+
encoding = encoding or locale.getpreferredencoding()
257+
return to_decode.decode(encoding, errors="replace").strip()

aws_lambda_builders/workflows/dotnet_clipackage/dotnetcli.py

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,10 @@
22
Wrapper around calls to dotent CLI through a subprocess.
33
"""
44

5-
import locale
65
import logging
76

8-
from .utils import OSUtils
7+
from aws_lambda_builders.utils import decode
8+
from aws_lambda_builders.workflows.dotnet_clipackage.utils import OSUtils
99

1010
LOG = logging.getLogger(__name__)
1111

@@ -52,15 +52,14 @@ def run(self, args, cwd=None):
5252
# DotNet output is in system locale dependent encoding
5353
# https://learn.microsoft.com/en-us/dotnet/api/system.console.outputencoding?view=net-6.0#remarks
5454
# "The default code page that the console uses is determined by the system locale."
55-
encoding = locale.getpreferredencoding()
5655
p = self.os_utils.popen(invoke_dotnet, stdout=self.os_utils.pipe, stderr=self.os_utils.pipe, cwd=cwd)
5756

5857
out, err = p.communicate()
5958

6059
# The package command contains lots of useful information on how the package was created and
6160
# information when the package command was not successful. For that reason the output is
6261
# always written to the output to help developers diagnose issues.
63-
LOG.info(out.decode(encoding).strip())
62+
LOG.info(decode(out))
6463

6564
if p.returncode != 0:
66-
raise DotnetCLIExecutionError(message=err.decode(encoding).strip())
65+
raise DotnetCLIExecutionError(message=decode(err))

aws_lambda_builders/workflows/dotnet_clipackage/utils.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
import subprocess
88
import zipfile
99

10-
from aws_lambda_builders.utils import which
10+
from aws_lambda_builders.utils import decode, which
1111

1212
LOG = logging.getLogger(__name__)
1313

@@ -96,7 +96,7 @@ def _extract(self, file_info, output_dir, zip_ref):
9696
if not self._is_symlink(file_info):
9797
return zip_ref.extract(file_info, output_dir)
9898

99-
source = zip_ref.read(file_info.filename).decode("utf8")
99+
source = decode(zip_ref.read(file_info.filename))
100100
link_name = os.path.normpath(os.path.join(output_dir, file_info.filename))
101101

102102
# make leading dirs if needed

aws_lambda_builders/workflows/java_gradle/gradle_validator.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import logging
66
import re
77

8+
from aws_lambda_builders.utils import decode
89
from aws_lambda_builders.validator import RuntimeValidator
910
from aws_lambda_builders.workflows.java.utils import OSUtils
1011

@@ -81,6 +82,6 @@ def _get_jvm_string(self, gradle_path):
8182
return None
8283

8384
for line in stdout.splitlines():
84-
l_dec = line.decode()
85+
l_dec = decode(line)
8586
if l_dec.startswith("JVM"):
8687
return l_dec

aws_lambda_builders/workflows/java_maven/maven.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
import logging
66
import subprocess
77

8+
from aws_lambda_builders.utils import decode
9+
810
LOG = logging.getLogger(__name__)
911

1012

@@ -28,10 +30,10 @@ def build(self, scratch_dir):
2830
args = ["clean", "install"]
2931
ret_code, stdout, _ = self._run(args, scratch_dir)
3032

31-
LOG.debug("Maven logs: %s", stdout.decode("utf8").strip())
33+
LOG.debug("Maven logs: %s", decode(stdout))
3234

3335
if ret_code != 0:
34-
raise MavenExecutionError(message=stdout.decode("utf8").strip())
36+
raise MavenExecutionError(message=decode(stdout))
3537

3638
def copy_dependency(self, scratch_dir):
3739
include_scope = "runtime"
@@ -40,7 +42,7 @@ def copy_dependency(self, scratch_dir):
4042
ret_code, stdout, _ = self._run(args, scratch_dir)
4143

4244
if ret_code != 0:
43-
raise MavenExecutionError(message=stdout.decode("utf8").strip())
45+
raise MavenExecutionError(message=decode(stdout))
4446

4547
def _run(self, args, cwd=None):
4648
p = self.os_utils.popen(

aws_lambda_builders/workflows/nodejs_npm/actions.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,7 @@
77

88
from aws_lambda_builders.actions import ActionFailedError, BaseAction, Purpose
99
from aws_lambda_builders.utils import extract_tarfile
10-
11-
from .npm import NpmExecutionError, SubprocessNpm
10+
from aws_lambda_builders.workflows.nodejs_npm.npm import NpmExecutionError, SubprocessNpm
1211

1312
LOG = logging.getLogger(__name__)
1413

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
"""
2+
Exceptions for the Node.js workflow
3+
"""
4+
5+
6+
from aws_lambda_builders.exceptions import LambdaBuilderError
7+
8+
9+
class NpmExecutionError(LambdaBuilderError):
10+
"""
11+
Exception raised in case NPM execution fails.
12+
It will pass on the standard error output from the NPM console.
13+
"""
14+
15+
MESSAGE = "NPM Failed: {message}"
16+
17+
def __init__(self, **kwargs):
18+
Exception.__init__(self, self.MESSAGE.format(**kwargs))

aws_lambda_builders/workflows/nodejs_npm/npm.py

Lines changed: 3 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -4,20 +4,9 @@
44

55
import logging
66

7-
LOG = logging.getLogger(__name__)
8-
9-
10-
class NpmExecutionError(Exception):
11-
12-
"""
13-
Exception raised in case NPM execution fails.
14-
It will pass on the standard error output from the NPM console.
15-
"""
7+
from aws_lambda_builders.workflows.nodejs_npm.exceptions import NpmExecutionError
168

17-
MESSAGE = "NPM Failed: {message}"
18-
19-
def __init__(self, **kwargs):
20-
Exception.__init__(self, self.MESSAGE.format(**kwargs))
9+
LOG = logging.getLogger(__name__)
2110

2211

2312
class SubprocessNpm(object):
@@ -59,7 +48,7 @@ def run(self, args, cwd=None):
5948
:rtype: str
6049
:return: text of the standard output from the command
6150
62-
:raises aws_lambda_builders.workflows.nodejs_npm.npm.NpmExecutionError:
51+
:raises aws_lambda_builders.workflows.nodejs_npm.exceptions.NpmExecutionError:
6352
when the command executes with a non-zero return code. The exception will
6453
contain the text of the standard error output from the command.
6554

aws_lambda_builders/workflows/nodejs_npm/workflow.py

Lines changed: 57 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,20 +15,30 @@
1515
)
1616
from aws_lambda_builders.path_resolver import PathResolver
1717
from aws_lambda_builders.workflow import BaseWorkflow, BuildDirectory, BuildInSourceSupport, Capability
18-
19-
from .actions import (
18+
from aws_lambda_builders.workflows.nodejs_npm.actions import (
2019
NodejsNpmCIAction,
2120
NodejsNpmInstallAction,
2221
NodejsNpmLockFileCleanUpAction,
2322
NodejsNpmPackAction,
2423
NodejsNpmrcAndLockfileCopyAction,
2524
NodejsNpmrcCleanUpAction,
2625
)
27-
from .npm import SubprocessNpm
28-
from .utils import OSUtils
26+
from aws_lambda_builders.workflows.nodejs_npm.npm import SubprocessNpm
27+
from aws_lambda_builders.workflows.nodejs_npm.utils import OSUtils
2928

3029
LOG = logging.getLogger(__name__)
3130

31+
# npm>=8.8.0 supports --install-links
32+
MINIMUM_NPM_VERSION_INSTALL_LINKS = (8, 8)
33+
UNSUPPORTED_NPM_VERSION_MESSAGE = (
34+
"Building in source was enabled, however the "
35+
"currently installed npm version does not support "
36+
"--install-links. Please ensure that the npm "
37+
"version is at least 8.8.0. Switching to build "
38+
f"in outside of the source directory.{os.linesep}"
39+
"https://docs.npmjs.com/cli/v8/using-npm/changelog#v880-2022-04-27"
40+
)
41+
3242

3343
class NodejsNpmWorkflow(BaseWorkflow):
3444

@@ -89,6 +99,12 @@ def __init__(self, source_dir, artifacts_dir, scratch_dir, manifest_path, runtim
8999
self.actions.append(CopySourceAction(self.source_dir, artifacts_dir, excludes=self.EXCLUDED_FILES))
90100

91101
if self.download_dependencies:
102+
if is_building_in_source and not self.can_use_install_links(subprocess_npm):
103+
LOG.warning(UNSUPPORTED_NPM_VERSION_MESSAGE)
104+
105+
is_building_in_source = False
106+
self.build_dir = self._select_build_dir(build_in_source=False)
107+
92108
self.actions.append(
93109
NodejsNpmWorkflow.get_install_action(
94110
source_dir=source_dir,
@@ -235,3 +251,40 @@ def get_install_action(
235251
return NodejsNpmInstallAction(
236252
install_dir=install_dir, subprocess_npm=subprocess_npm, install_links=install_links
237253
)
254+
255+
@staticmethod
256+
def can_use_install_links(npm_process: SubprocessNpm) -> bool:
257+
"""
258+
Checks the version of npm that is currently installed to determine
259+
whether or not --install-links can be used
260+
261+
Parameters
262+
----------
263+
npm_process: SubprocessNpm
264+
Object containing helper methods to call the npm process
265+
266+
Returns
267+
-------
268+
bool
269+
True if the current npm version meets the minimum for --install-links
270+
"""
271+
try:
272+
current_version = npm_process.run(["--version"])
273+
274+
LOG.debug(f"Currently installed version of npm is: {current_version}")
275+
276+
current_version = current_version.split(".")
277+
278+
major_version = int(current_version[0])
279+
minor_version = int(current_version[1])
280+
except (ValueError, IndexError):
281+
LOG.debug(f"Failed to parse {current_version} output from npm for --install-links validation")
282+
return False
283+
284+
is_older_major_version = major_version < MINIMUM_NPM_VERSION_INSTALL_LINKS[0]
285+
is_older_patch_version = (
286+
major_version == MINIMUM_NPM_VERSION_INSTALL_LINKS[0]
287+
and minor_version < MINIMUM_NPM_VERSION_INSTALL_LINKS[1]
288+
)
289+
290+
return not (is_older_major_version or is_older_patch_version)

aws_lambda_builders/workflows/nodejs_npm_esbuild/workflow.py

Lines changed: 22 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -14,17 +14,17 @@
1414
LinkSourceAction,
1515
MoveDependenciesAction,
1616
)
17+
from aws_lambda_builders.path_resolver import PathResolver
1718
from aws_lambda_builders.utils import which
1819
from aws_lambda_builders.workflow import BaseWorkflow, BuildDirectory, BuildInSourceSupport, Capability
19-
20-
from ...path_resolver import PathResolver
21-
from ..nodejs_npm import NodejsNpmWorkflow
22-
from ..nodejs_npm.npm import SubprocessNpm
23-
from ..nodejs_npm.utils import OSUtils
24-
from .actions import (
20+
from aws_lambda_builders.workflows.nodejs_npm import NodejsNpmWorkflow
21+
from aws_lambda_builders.workflows.nodejs_npm.npm import SubprocessNpm
22+
from aws_lambda_builders.workflows.nodejs_npm.utils import OSUtils
23+
from aws_lambda_builders.workflows.nodejs_npm.workflow import UNSUPPORTED_NPM_VERSION_MESSAGE
24+
from aws_lambda_builders.workflows.nodejs_npm_esbuild.actions import (
2525
EsbuildBundleAction,
2626
)
27-
from .esbuild import EsbuildExecutionError, SubprocessEsbuild
27+
from aws_lambda_builders.workflows.nodejs_npm_esbuild.esbuild import EsbuildExecutionError, SubprocessEsbuild
2828

2929
LOG = logging.getLogger(__name__)
3030

@@ -98,6 +98,12 @@ def __init__(self, source_dir, artifacts_dir, scratch_dir, manifest_path, runtim
9898
)
9999

100100
if self.download_dependencies:
101+
if is_building_in_source and not NodejsNpmWorkflow.can_use_install_links(self.subprocess_npm):
102+
LOG.warning(UNSUPPORTED_NPM_VERSION_MESSAGE)
103+
104+
is_building_in_source = False
105+
self.build_dir = self._select_build_dir(build_in_source=False)
106+
101107
self.actions.append(
102108
NodejsNpmWorkflow.get_install_action(
103109
source_dir=source_dir,
@@ -177,8 +183,16 @@ def get_resolvers(self):
177183
return [PathResolver(runtime=self.runtime, binary="npm")]
178184

179185
def _get_esbuild_subprocess(self) -> SubprocessEsbuild:
186+
"""
187+
Creates a subprocess object that is able to invoke the esbuild executable.
188+
189+
Returns
190+
-------
191+
SubprocessEsbuild
192+
An esbuild specific subprocess object
193+
"""
180194
try:
181-
npm_bin_path_root = self.subprocess_npm.run(["root"], cwd=self.scratch_dir)
195+
npm_bin_path_root = self.subprocess_npm.run(["root"], cwd=self.build_dir)
182196
npm_bin_path = str(Path(npm_bin_path_root, ".bin"))
183197
except FileNotFoundError:
184198
raise EsbuildExecutionError(message="The esbuild workflow couldn't find npm installed on your system.")

0 commit comments

Comments
 (0)