Skip to content

Commit

Permalink
chore: Verify a compatable version of puya is available on the PATH, …
Browse files Browse the repository at this point in the history
…and fail or warn gracefully if it is not
  • Loading branch information
tristanmenzel committed Jan 16, 2025
1 parent 979a6b9 commit 8455edb
Show file tree
Hide file tree
Showing 9 changed files with 158 additions and 17 deletions.
53 changes: 43 additions & 10 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@
"@tsconfig/node20": "20.1.4",
"@types/cross-spawn": "^6.0.6",
"@types/node": "22.10.1",
"@types/which": "^3.0.4",
"@typescript-eslint/eslint-plugin": "8.18.0",
"@typescript-eslint/parser": "8.18.0",
"@vitest/coverage-v8": "2.1.8",
Expand Down Expand Up @@ -83,6 +84,7 @@
"polytype": "^0.17.0",
"typescript": "^5.7.2",
"upath": "^2.0.1",
"which": "^5.0.0",
"zod": "^3.24.0"
},
"overrides": {
Expand Down
1 change: 1 addition & 0 deletions src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ export const Constants = {

supportedAvmVersions: [10n, 11n],
mainNetAvmVersion: [10n],
targetedPuyaVersion: '4.1.1',
} as const

export type SupportedAvmVersion = (typeof Constants.supportedAvmVersions)[number]
19 changes: 18 additions & 1 deletion src/errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,15 @@ export class PuyaError extends Error {
}
}

export class CodeError extends PuyaError {
/**
* Any error that is 'caused' by a user of Puya
*/
export abstract class UserError extends PuyaError {}

/**
* Thrown when the user's code is invalid, or not supported
*/
export class CodeError extends UserError {
static unexpectedUnhandledArgs({ sourceLocation }: { sourceLocation: SourceLocation }) {
return new CodeError('Unexpected/unhandled args', {
sourceLocation,
Expand Down Expand Up @@ -56,6 +64,10 @@ export class CodeError extends PuyaError {
return new CodeError(`Cannot resolve ${sourceType} to ${targetName}`, { sourceLocation })
}
}

/**
* Thrown when the compiler ends up in an unrecoverable state
*/
export class InternalError extends PuyaError {
static shouldBeUnreachable() {
return new InternalError('Code should be unreachable')
Expand All @@ -67,6 +79,11 @@ export class NotSupported extends CodeError {
}
}

/**
* Thrown when the user's environment is not set up correctly
*/
export class EnvironmentError extends UserError {}

export const throwError = (error: Error): never => {
throw error
}
Expand Down
5 changes: 3 additions & 2 deletions src/logger/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { SourceLocation } from '../awst/source-location'
import { AwstBuildFailureError, CodeError, PuyaError } from '../errors'
import { AwstBuildFailureError, PuyaError, UserError } from '../errors'
import type { LogSink } from './sinks'

type NodeOrSourceLocation = SourceLocation | { sourceLocation: SourceLocation }
Expand Down Expand Up @@ -55,7 +55,8 @@ class PuyaLogger {
error(source: NodeOrSourceLocation | undefined, message: string): void
error(source: NodeOrSourceLocation | undefined | Error, message?: string): void {
if (source instanceof Error) {
const stack = source instanceof CodeError ? '' : `\n ${source.stack}`
// Don't include the stack for user errors as the message and source location is what's relevant
const stack = source instanceof UserError ? '' : `\n ${source.stack}`
this.addLog(LogLevel.Error, tryGetSourceLocationFromError(source), `${source.message}${stack}`)
if (source.cause) {
this.addLog(LogLevel.Error, tryGetSourceLocationFromError(source.cause), `Caused by: ${source.cause}`)
Expand Down
58 changes: 58 additions & 0 deletions src/puya/check-puya-version.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import { Constants } from '../constants'
import { logger } from '../logger'
import { runPuya } from './run-puya'

export function checkPuyaVersion() {
const versionParser = new VersionParser()
runPuya({
command: 'puya',
args: ['--version'],
onOutput: (line) => versionParser.receiveLine(line),
})

if (versionParser.version) {
const ver = versionParser.version
// Compare
const [major, minor, rev] = Constants.targetedPuyaVersion.split('.').map((x) => Number(x))
if (major !== ver.major || minor !== ver.minor) {
logger.warn(
undefined,
`Installed version of puya (${ver.major}.${ver.minor}.${ver.rev}) does not match targeted version for puya-ts (${Constants.targetedPuyaVersion}). There may be compatability issues.`,
)
} else if (rev !== ver.rev) {
logger.debug(
undefined,
`Installed revision of puya (${ver.major}.${ver.minor}.${ver.rev}) does not match targeted revision for puya-ts (${Constants.targetedPuyaVersion})`,
)
}
} else {
logger.warn(undefined, `Unable to verify installed puya version. Please ensure version ${Constants.targetedPuyaVersion} is available`)
}
}

type SemVer = {
major: number
minor: number
rev: number
}

class VersionParser {
#ver: SemVer | undefined

receiveLine(line: string): void {
const matched = /^puya (\d+)\.(\d+)\.(\d+)$/.exec(line)
if (!matched) {
logger.debug(undefined, `'puya --version' command returned unexpected output: "${line}"`)
} else {
this.#ver = {
major: Number(matched[1]),
minor: Number(matched[2]),
rev: Number(matched[3]),
}
}
}

get version(): undefined | SemVer {
return this.#ver
}
}
14 changes: 14 additions & 0 deletions src/puya/ensure-puya-exists.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { sync as whichSync } from 'which'
import { EnvironmentError } from '../errors'

export function ensurePuyaExists() {
if (
whichSync('puya', {
nothrow: true,
})
)
return
throw new EnvironmentError(
`Failed to resolve command path, 'puya' wasn't found. Please ensure puya compiler has been installed and is available on your PATH.`,
)
}
6 changes: 6 additions & 0 deletions src/puya/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ import type { SourceFileMapping } from '../parser'
import { jsonSerializeSourceFiles } from '../parser/json-serialize-source-files'
import { generateTempFile } from '../util/generate-temp-file'
import { buildCompilationSetMapping } from './build-compilation-set-mapping'
import { checkPuyaVersion } from './check-puya-version'
import { ensurePuyaExists } from './ensure-puya-exists'
import { deserializeAndLog } from './log-deserializer'
import type { PuyaPassThroughOptions } from './options'
import { PuyaOptions } from './options'
import { runPuya } from './run-puya'
Expand All @@ -26,6 +29,8 @@ export function invokePuya({
compilationSet: CompilationSet
passThroughOptions: PuyaPassThroughOptions
}) {
ensurePuyaExists()
checkPuyaVersion()
// Write AWST file
using moduleAwstFile = generateTempFile()
logger.debug(undefined, `Writing awst to ${moduleAwstFile.filePath}`)
Expand Down Expand Up @@ -72,6 +77,7 @@ export function invokePuya({
'json',
],
cwd: programDirectory,
onOutput: deserializeAndLog,
})
}

Expand Down
17 changes: 13 additions & 4 deletions src/puya/run-puya.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,17 @@
import { sync } from 'cross-spawn'
import { logger } from '../logger'
import { deserializeAndLog } from './log-deserializer'

export function runPuya({ command, args, cwd }: { command: string; args: string[]; cwd?: string }) {
export function runPuya({
command,
args,
cwd,
onOutput,
}: {
command: string
args: string[]
cwd?: string
onOutput: (line: string) => void
}) {
const proc = sync(command, args, {
// forward all stdin/stdout/stderr to current handlers, with correct interleaving
stdio: 'pipe',
Expand All @@ -16,7 +25,7 @@ export function runPuya({ command, args, cwd }: { command: string; args: string[
for (const c of text) {
switch (c) {
case '\n':
deserializeAndLog(line)
onOutput(line)
line = ''
break
case '\r':
Expand All @@ -27,7 +36,7 @@ export function runPuya({ command, args, cwd }: { command: string; args: string[
}
}
}
if (line) deserializeAndLog(line)
if (line) onOutput(line)

if (proc.error) {
// only happens during invocation error, not error return status
Expand Down

0 comments on commit 8455edb

Please sign in to comment.