Skip to content

Commit 4e40536

Browse files
authored
fix(sam): SAM CLI 1.85-1.86 fails on Windows #3559
Problem: SAM CLI 1.85-1.86 fails on Windows. Solution: Attempt to detect the situation, and show a warning. Also fix a typo in `locationPaths`.
1 parent 1a19d5e commit 4e40536

File tree

10 files changed

+112
-74
lines changed

10 files changed

+112
-74
lines changed
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"type": "Bug Fix",
3+
"description": "SAM CLI 1.85-1.86 fails on Windows"
4+
}

src/shared/constants.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,12 @@ export const hostedFilesBaseUrl: string = 'https://d3rrggjwfhwld2.cloudfront.net
1515
export const endpointsFileUrl: string = 'https://idetoolkits.amazonwebservices.com/endpoints.json'
1616
export const aboutCredentialsFileUrl: string =
1717
'https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-files.html'
18-
export const samAboutInstallUrl = vscode.Uri.parse('https://aws.amazon.com/serverless/sam/')
18+
export const samInstallUrl = vscode.Uri.parse(
19+
'https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/install-sam-cli.html'
20+
)
1921
export const samUpgradeUrl = vscode.Uri.parse(
2022
'https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/manage-sam-cli-versions.html#manage-sam-cli-versions-upgrade'
2123
)
22-
export const vscodeMarketplaceUrl: string =
23-
'https://marketplace.visualstudio.com/items?itemName=AmazonWebServices.aws-toolkit-vscode'
2424
export const githubUrl: string = 'https://github.com/aws/aws-toolkit-vscode'
2525
export const githubCreateIssueUrl = `${githubUrl}/issues/new/choose`
2626
export const documentationUrl: string = isCloud9()

src/shared/sam/activation.ts

Lines changed: 2 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ import { shared } from '../utilities/functionUtils'
3939
import { SamCliSettings } from './cli/samCliSettings'
4040
import { Commands } from '../vscode/commands2'
4141
import { registerSync } from './sync'
42+
import { showExtensionPage } from '../utilities/vsCodeUtils'
4243

4344
const sharedDetectSamCli = shared(detectSamCli)
4445

@@ -423,19 +424,7 @@ async function promptInstallYamlPlugin(disposables: vscode.Disposable[]) {
423424

424425
switch (response) {
425426
case installBtn:
426-
// Available options are:
427-
// extension.open: opens extension page in VS Code extension marketplace view
428-
// workbench.extensions.installExtension: autoinstalls plugin with no additional feedback
429-
// workspace.extension.search: preloads and executes a search in the extension sidebar with the given term
430-
431-
// not sure if these are 100% stable.
432-
// Opting for `extension.open` as this gives the user a good path forward to install while not doing anything potentially unexpected.
433-
try {
434-
await vscode.commands.executeCommand('extension.open', VSCODE_EXTENSION_ID.yaml)
435-
} catch (e) {
436-
const err = e as Error
437-
getLogger().error(`Extension ${VSCODE_EXTENSION_ID.yaml} could not be opened: `, err.message)
438-
}
427+
showExtensionPage(VSCODE_EXTENSION_ID.yaml)
439428
break
440429
case permanentlySuppress:
441430
settings.disablePrompt('yamlExtPrompt')

src/shared/sam/cli/samCliDetection.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,13 @@
55

66
import * as vscode from 'vscode'
77
import * as nls from 'vscode-nls'
8-
import { samAboutInstallUrl } from '../../constants'
8+
import { samInstallUrl } from '../../constants'
99
import { telemetry } from '../../telemetry/telemetry'
1010
import { SamCliSettings } from './samCliSettings'
1111

1212
const localize = nls.loadMessageBundle()
1313

14-
const learnMore = localize('AWS.samcli.userChoice.visit.install.url', 'Get SAM CLI')
14+
const learnMore = localize('AWS.samcli.userChoice.visit.install.url', 'Install SAM CLI')
1515
const browseToSamCli = localize('AWS.samcli.userChoice.browse', 'Locate SAM CLI...')
1616
const settingsUpdated = localize('AWS.samcli.detect.settings.updated', 'Settings updated.')
1717

@@ -64,20 +64,20 @@ function notifyUserSamCliNotDetected(SamCliSettings: SamCliSettings): void {
6464
.showErrorMessage(
6565
localize(
6666
'AWS.samcli.error.notFound',
67-
'Cannot find SAM CLI, which is required to create new Serverless Applications and debug them locally. If you have already installed the SAM CLI, update your User Settings by locating it.'
67+
'Cannot find SAM CLI, which is required to create and debug SAM applications. If you have SAM CLI in a custom location, set the "aws.samcli.location" user setting.'
6868
),
6969
learnMore,
7070
browseToSamCli
7171
)
7272
.then(async userResponse => {
7373
if (userResponse === learnMore) {
74-
await vscode.commands.executeCommand('vscode.open', samAboutInstallUrl)
74+
await vscode.commands.executeCommand('vscode.open', samInstallUrl)
7575
} else if (userResponse === browseToSamCli) {
7676
const location: vscode.Uri[] | undefined = await vscode.window.showOpenDialog({
7777
canSelectFiles: true,
7878
canSelectFolders: false,
7979
canSelectMany: false,
80-
openLabel: 'Apply location to Settings',
80+
openLabel: 'Set location in user settings',
8181
})
8282

8383
if (!!location && location.length === 1) {

src/shared/sam/cli/samCliLocator.ts

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ export class DefaultSamCliLocationProvider implements SamCliLocationProvider {
6060

6161
abstract class BaseSamCliLocator {
6262
/** Indicates that findFileInFolders() returned at least once. */
63-
static didFind = false
63+
static didSearch = false
6464
protected readonly logger: Logger = getLogger()
6565

6666
public constructor() {
@@ -70,7 +70,7 @@ abstract class BaseSamCliLocator {
7070
public async getLocation() {
7171
let location = await this.findFileInFolders(this.getExecutableFilenames(), this.getExecutableFolders())
7272

73-
if (!location) {
73+
if (!location?.version) {
7474
location = await this.getSystemPathLocation()
7575
}
7676

@@ -81,7 +81,16 @@ abstract class BaseSamCliLocator {
8181
protected abstract getExecutableFilenames(): string[]
8282
protected abstract getExecutableFolders(): string[]
8383

84+
/**
85+
* Searches for `sam` in various places and the $PATH.
86+
*
87+
* If only a broken `sam` is found it is returned with an empty `version`.
88+
*/
8489
protected async findFileInFolders(files: string[], folders: string[]) {
90+
// Keep the first found "sam" even if it is broken.
91+
// This allows us to give a better message than "not found".
92+
let brokenSam: string | undefined
93+
8594
const fullPaths: string[] = files
8695
.map(file => folders.filter(folder => !!folder).map(folder => path.join(folder, file)))
8796
.reduce((accumulator, paths) => {
@@ -91,7 +100,7 @@ abstract class BaseSamCliLocator {
91100
})
92101

93102
for (const fullPath of fullPaths) {
94-
if (!BaseSamCliLocator.didFind) {
103+
if (!BaseSamCliLocator.didSearch) {
95104
this.logger.verbose(`samCliLocator: searching in: ${fullPath}`)
96105
}
97106
const context: SamCliValidatorContext = {
@@ -103,21 +112,22 @@ abstract class BaseSamCliLocator {
103112
try {
104113
const validationResult = await validator.getVersionValidatorResult()
105114
if (validationResult.validation === SamCliVersionValidation.Valid) {
106-
BaseSamCliLocator.didFind = true
115+
BaseSamCliLocator.didSearch = true
107116
return { path: fullPath, version: validationResult.version }
108117
}
109118
this.logger.warn(
110119
`samCliLocator: found invalid SAM CLI (${validationResult.validation}): ${fullPath}`
111120
)
121+
brokenSam = brokenSam ?? fullPath
112122
} catch (e) {
113123
const err = e as Error
114124
this.logger.error('samCliLocator failed: %s', err.message)
115125
}
116126
}
117127
}
118128

119-
BaseSamCliLocator.didFind = true
120-
return undefined
129+
BaseSamCliLocator.didSearch = true
130+
return brokenSam ? { path: brokenSam, version: '' } : undefined
121131
}
122132

123133
/**
@@ -180,7 +190,7 @@ class WindowsSamCliLocator extends BaseSamCliLocator {
180190

181191
class UnixSamCliLocator extends BaseSamCliLocator {
182192
private static readonly locationPaths: string[] = [
183-
'/opt/homebrew/bin/sam',
193+
'/opt/homebrew/bin',
184194
'/usr/local/bin',
185195
'/usr/bin',
186196
// WEIRD BUT TRUE: brew installs to /home/linuxbrew/.linuxbrew if

src/shared/sam/cli/samCliValidationNotification.ts

Lines changed: 41 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,11 @@
33
* SPDX-License-Identifier: Apache-2.0
44
*/
55

6-
import { openUrl } from '../../../shared/utilities/vsCodeUtils'
6+
import { openUrl, showExtensionPage } from '../../../shared/utilities/vsCodeUtils'
77
import * as vscode from 'vscode'
88
import * as nls from 'vscode-nls'
99

10-
import { samAboutInstallUrl, vscodeMarketplaceUrl } from '../../constants'
10+
import { samInstallUrl } from '../../constants'
1111
import { getIdeProperties } from '../../extensionUtilities'
1212
import {
1313
InvalidSamCliError,
@@ -18,6 +18,7 @@ import {
1818
SamCliVersionValidation,
1919
SamCliVersionValidatorResult,
2020
} from './samCliValidator'
21+
import { VSCODE_EXTENSION_ID } from '../../extensions'
2122

2223
const localize = nls.loadMessageBundle()
2324

@@ -28,17 +29,20 @@ export interface SamCliValidationNotificationAction {
2829
}
2930

3031
const actionGoToSamCli: SamCliValidationNotificationAction = {
31-
label: localize('AWS.samcli.userChoice.visit.install.url', 'Get SAM CLI'),
32+
label: localize('AWS.samcli.userChoice.visit.install.url', 'Install latest SAM CLI'),
3233
invoke: async () => {
33-
openUrl(samAboutInstallUrl)
34+
openUrl(samInstallUrl)
3435
},
3536
}
3637

3738
const actionGoToVsCodeMarketplace: SamCliValidationNotificationAction = {
38-
label: localize('AWS.samcli.userChoice.update.awstoolkit.url', 'Visit Marketplace'),
39+
label: localize(
40+
'AWS.samcli.userChoice.update.awstoolkit.url',
41+
'Install latest {0} Toolkit',
42+
getIdeProperties().company
43+
),
3944
invoke: async () => {
40-
// TODO : Switch to the Extension panel in VS Code instead
41-
await vscode.env.openExternal(vscode.Uri.parse(vscodeMarketplaceUrl))
45+
showExtensionPage(VSCODE_EXTENSION_ID.awstoolkit)
4246
},
4347
}
4448

@@ -74,12 +78,11 @@ export async function notifySamCliValidation(samCliValidationError: InvalidSamCl
7478
return
7579
}
7680

77-
const notification: SamCliValidationNotification = makeSamCliValidationNotification(samCliValidationError)
78-
81+
const notification = getInvalidSamMsg(samCliValidationError)
7982
await notification.show()
8083
}
8184

82-
export function makeSamCliValidationNotification(
85+
export function getInvalidSamMsg(
8386
samCliValidationError: InvalidSamCliError,
8487
onCreateNotification: (
8588
message: string,
@@ -97,46 +100,56 @@ export function makeSamCliValidationNotification(
97100
)
98101
} else if (samCliValidationError instanceof InvalidSamCliVersionError) {
99102
return onCreateNotification(
100-
makeVersionValidationNotificationMessage(samCliValidationError.versionValidation),
101-
makeVersionValidationActions(samCliValidationError.versionValidation.validation)
103+
getInvalidVersionMsg(samCliValidationError.versionValidation),
104+
getActions(samCliValidationError.versionValidation.validation)
102105
)
103106
} else {
104107
return onCreateNotification(
105108
localize(
106109
'AWS.samcli.notification.unexpected.validation.issue',
107-
'An unexpected issue occured while validating SAM CLI: {0}',
110+
'Unexpected error while validating SAM CLI: {0}',
108111
samCliValidationError.message
109112
),
110113
[]
111114
)
112115
}
113116
}
114117

115-
function makeVersionValidationNotificationMessage(validationResult: SamCliVersionValidatorResult): string {
116-
const recommendationUpdateToolkit: string = localize(
117-
'AWS.samcli.recommend.update.toolkit',
118-
'Check the Marketplace for an updated {0} Toolkit.',
119-
getIdeProperties().company
118+
function getInvalidVersionMsg(validationResult: SamCliVersionValidatorResult): string {
119+
const win185msg = localize(
120+
'AWS.sam.updateSamWindows',
121+
'SAM CLI 1.85-1.86 has [known issues](https://github.com/aws/aws-sam-cli/issues/5243) on Windows. Update SAM CLI.'
120122
)
121-
122-
const recommendationUpdateSamCli: string = localize('AWS.samcli.recommend.update.samcli', 'Update your SAM CLI.')
123-
124-
const recommendation: string =
125-
validationResult.validation === SamCliVersionValidation.VersionTooHigh
126-
? recommendationUpdateToolkit
127-
: recommendationUpdateSamCli
123+
let recommendation: string
124+
125+
switch (validationResult.validation) {
126+
case SamCliVersionValidation.VersionTooHigh:
127+
recommendation = localize('AWS.sam.updateToolkit', 'Update {0} Toolkit.', getIdeProperties().company)
128+
break
129+
case SamCliVersionValidation.Version185Win:
130+
return win185msg
131+
case SamCliVersionValidation.VersionNotParseable: {
132+
if (process.platform === 'win32') {
133+
return win185msg
134+
}
135+
return localize('AWS.sam.installSam', 'SAM CLI failed to run.')
136+
}
137+
default:
138+
recommendation = localize('AWS.sam.updateSam', 'Update SAM CLI.')
139+
break
140+
}
128141

129142
return localize(
130-
'AWS.samcli.notification.version.invalid',
131-
'Your SAM CLI version {0} does not meet requirements ({1} ≤ version < {2}). {3}',
143+
'AWS.sam.invalid',
144+
'SAM CLI {0} is not in required range ({1} ≤ version < {2}). {3}',
132145
validationResult.version,
133146
minSamCliVersion,
134147
maxSamCliVersionExclusive,
135148
recommendation
136149
)
137150
}
138151

139-
function makeVersionValidationActions(validation: SamCliVersionValidation): SamCliValidationNotificationAction[] {
152+
function getActions(validation: SamCliVersionValidation): SamCliValidationNotificationAction[] {
140153
const actions: SamCliValidationNotificationAction[] = []
141154

142155
if (validation === SamCliVersionValidation.VersionTooHigh) {

src/shared/sam/cli/samCliValidator.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ export enum SamCliVersionValidation {
3737
VersionTooLow = 'VersionTooLow',
3838
VersionTooHigh = 'VersionTooHigh',
3939
VersionNotParseable = 'VersionNotParseable',
40+
Version185Win = 'Version185Win',
4041
}
4142

4243
export type SamCliVersionValidatorResult =
@@ -103,6 +104,10 @@ export class DefaultSamCliValidator implements SamCliValidator {
103104
return SamCliVersionValidation.VersionTooHigh
104105
}
105106

107+
if (process.platform === 'win32' && semver.gte(version, '1.85.0') && semver.lte(version, '1.86.0')) {
108+
return SamCliVersionValidation.Version185Win
109+
}
110+
106111
return SamCliVersionValidation.Valid
107112
}
108113
}

src/shared/utilities/messages.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import { Timeout } from './timeoutUtils'
1414
import { addCodiconToString } from './textUtilities'
1515
import { getIcon, codicon } from '../icons'
1616
import globals from '../extensionGlobals'
17+
import { openUrl } from './vsCodeUtils'
1718

1819
export const messages = {
1920
editCredentials(icon: boolean) {
@@ -71,7 +72,7 @@ export async function showMessageWithUrl(
7172
const p = showMessageWithItems(message, kind, items)
7273
return p.then<string | undefined>(selection => {
7374
if (selection === urlItem) {
74-
vscode.env.openExternal(uri)
75+
openUrl(uri)
7576
}
7677
return selection
7778
})

src/shared/utilities/vsCodeUtils.ts

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,21 @@ export function isExtensionInstalled(
8282
return semver.gte(extSemver, minSemver)
8383
}
8484

85+
export async function showExtensionPage(extId: string) {
86+
try {
87+
// Available commands:
88+
// - extension.open: opens extension page in VS Code extension marketplace view
89+
// - workbench.extensions.installExtension: autoinstalls plugin with no additional feedback
90+
// - workspace.extension.search: preloads and executes a search in the extension sidebar with the given term
91+
await vscode.commands.executeCommand('extension.open', extId)
92+
} catch (e) {
93+
const err = e as Error
94+
getLogger().error('extension.open command failed: %s', err.message)
95+
const uri = vscode.Uri.parse(`https://marketplace.visualstudio.com/items?itemName=${extId}`)
96+
openUrl(uri)
97+
}
98+
}
99+
85100
/**
86101
* Checks if an extension is installed, and shows a message if not.
87102
*/
@@ -103,7 +118,7 @@ export function showInstallExtensionMsg(
103118
const p = vscode.window.showErrorMessage(msg, ...items)
104119
p.then<string | undefined>(selection => {
105120
if (selection === installBtn) {
106-
vscode.commands.executeCommand('extension.open', extId)
121+
showExtensionPage(extId)
107122
}
108123
return selection
109124
})

0 commit comments

Comments
 (0)