diff --git a/.github/actions/get_dfx_version/action.yml b/.github/actions/get_dfx_version/action.yml new file mode 100644 index 0000000000..92f68b5baa --- /dev/null +++ b/.github/actions/get_dfx_version/action.yml @@ -0,0 +1,16 @@ +name: Get dfx version +description: Determines Azle's dfx version +outputs: + dfx-version: + description: Returns the version of dfx that Azle will test against and use to generate its Wasm binary templates + value: ${{ steps.get-dfx-version.outputs.dfx-version }} +runs: + using: composite + steps: + - uses: actions/checkout@v4 + + - id: get-dfx-version + run: | + DFX_VERSION=$(jq -r '.azle.globalDependencies.dfx // error("dfx version not found")' "package.json") + echo "dfx-version=${DFX_VERSION}" >> "$GITHUB_OUTPUT" + shell: bash diff --git a/.github/actions/get_node_version/action.yml b/.github/actions/get_node_version/action.yml new file mode 100644 index 0000000000..eadb0684f6 --- /dev/null +++ b/.github/actions/get_node_version/action.yml @@ -0,0 +1,16 @@ +name: Get node version +description: Determines Azle's node version +outputs: + node-version: + description: Returns the version of node that Azle will test against and use to generate its Wasm binary templates + value: ${{ steps.get-node-version.outputs.node-version }} +runs: + using: composite + steps: + - uses: actions/checkout@v4 + + - id: get-node-version + run: | + NODE_VERSION=$(jq -r '.azle.globalDependencies.node // error("node version not found")' "package.json") + echo "node-version=${NODE_VERSION}" >> "$GITHUB_OUTPUT" + shell: bash diff --git a/.github/actions/get_test_infos/action.yml b/.github/actions/get_test_infos/action.yml index 9751ea0cbd..9d66d7ba09 100644 --- a/.github/actions/get_test_infos/action.yml +++ b/.github/actions/get_test_infos/action.yml @@ -9,6 +9,9 @@ description: displayPath: string // An abbreviated version of the path for display purposes only }' inputs: + node-version: + description: The version of Node.js to use + required: true directories: description: List of directories to search for npm projects with an npm test script required: true @@ -16,10 +19,6 @@ inputs: description: List of directories to exclude from the search required: false default: '' - node-version: - description: The version of Node.js to use - required: true - default: '20.x' outputs: test-infos: description: All of the test info objects found by this action diff --git a/.github/scripts/install_global_dependencies.sh b/.github/scripts/install_global_dependencies.sh deleted file mode 100755 index fd745a8677..0000000000 --- a/.github/scripts/install_global_dependencies.sh +++ /dev/null @@ -1,58 +0,0 @@ -#!/bin/bash - -# File path for the package.json -PACKAGE_JSON_FILE="$PWD/package.json" - -# Ensure jq is installed for JSON parsing -if ! command -v jq &> /dev/null; then - echo "jq could not be found. Please install jq to parse JSON." - exit 1 -fi - -# Extract the rustc version from the globalDependencies in package.json -RUST_VERSION=$(jq -r '.azle.globalDependencies.rustc // empty' "$PACKAGE_JSON_FILE") - -if [[ -z "$RUST_VERSION" ]]; then - echo "Rust version not found in $PACKAGE_JSON_FILE" - exit 1 -fi - -# Install Rust using rustup with the extracted version -echo "Installing Rust version $RUST_VERSION" -curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --default-toolchain "$RUST_VERSION" -source $HOME/.cargo/env - -rustup target add wasm32-wasi - -# Extract the wasi2ic version and repository URL from globalDependencies in package.json -WASI2IC_VERSION=$(jq -r '.azle.globalDependencies.wasi2ic // empty' "$PACKAGE_JSON_FILE") - -# Determine if WASI2IC_VERSION is a URL (repo) or just a version number -if [[ $WASI2IC_VERSION =~ ^https?:// ]]; then - WASI2IC_URL=$WASI2IC_VERSION -else - WASI2IC_URL="" -fi - -if [[ -z "$WASI2IC_VERSION" ]]; then - echo "wasi2ic version not found in $PACKAGE_JSON_FILE" - exit 1 -fi - -# Install wasi2ic -if [[ -n "$WASI2IC_URL" ]]; then - echo "Installing wasi2ic from repository $WASI2IC_URL" - cargo install --git "$WASI2IC_URL" -else - echo "Installing wasi2ic version $WASI2IC_VERSION" - cargo install wasi2ic --version "$WASI2IC_VERSION" -fi - -# Confirm installation -echo "\nThe following global dependencies were installed" -rustc --version -cargo --version - -rustup target list --installed | grep wasm32-wasi - -cargo install --list | grep wasi2ic diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 82e3f95461..20945b47e5 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -4,9 +4,6 @@ on: branches: - main pull_request: # Runs on pull requests to any branch -env: - DFX_VERSION: 0.21.0 - NODE_VERSION: 20 jobs: determine-should-release: if: ${{ startsWith(github.head_ref, 'release--') }} @@ -30,10 +27,14 @@ jobs: steps: - uses: actions/checkout@v4 + - id: get-node-version + uses: ./.github/actions/get_node_version + - name: Get all test infos id: get-test-infos uses: ./.github/actions/get_test_infos with: + node-version: ${{ steps.get-node-version.outputs.node-version }} directories: | ./examples ./tests @@ -55,9 +56,12 @@ jobs: ref: ${{ github.event.pull_request.head.ref || github.ref }} token: ${{ secrets.LASTMJS_GITHUB_TOKEN || github.token }} + - id: get-node-version + uses: ./.github/actions/get_node_version + - uses: actions/setup-node@v4 with: - node-version: ${{ env.NODE_VERSION }} + node-version: ${{ steps.get-node-version.outputs.node-version }} registry-url: https://registry.npmjs.org env: NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} @@ -65,14 +69,21 @@ jobs: - name: Install curl run: sudo apt-get install curl -y - - name: Install global dependencies - run: ./.github/scripts/install_global_dependencies.sh + - id: get-dfx-version + uses: ./.github/actions/get_dfx_version - name: Install dfx run: | - DFXVM_INIT_YES=true DFX_VERSION=${{ env.DFX_VERSION }} sh -ci "$(curl -fsSL https://sdk.dfinity.org/install.sh)" + # Install dfx (Note: DFX must be installed before `npx azle` because the azle instalation process requires dfx) + src/build/stable/commands/install_global_dependencies/install_dfx.sh ${{ steps.get-dfx-version.outputs.dfx-version }} echo "$HOME/.local/share/dfx/bin" >> $GITHUB_PATH + - run: npm install + + - name: Install global dependencies + run: | + AZLE_VERBOSE=true npx azle install-global-dependencies --rust --wasi2ic + # TODO we should use some Action-specific bot account - name: Configure git for publishing release run: | diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 45e467a940..586f8cf843 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -10,8 +10,6 @@ on: - main pull_request: # Runs on pull requests to any branch env: - DFX_VERSION: 0.22.0 - NODE_VERSION: 20 AZLE_IS_MAIN_BRANCH_PUSH: ${{ github.ref == 'refs/heads/main' && !contains(github.event.head_commit.message, 'demergent-labs/release--') }} AZLE_IS_MAIN_BRANCH_MERGE_FROM_RELEASE_PUSH: ${{ github.ref == 'refs/heads/main' && contains(github.event.head_commit.message, 'demergent-labs/release--') }} AZLE_IS_RELEASE_BRANCH_PR: ${{ startsWith(github.head_ref, 'release--') }} @@ -40,6 +38,9 @@ jobs: steps: - uses: actions/checkout@v4 + - id: get-node-version + uses: ./.github/actions/get_node_version + - name: Set exclude dirs id: set-exclude-dirs run: | @@ -103,6 +104,7 @@ jobs: id: get-test-infos uses: ./.github/actions/get_test_infos with: + node-version: ${{ steps.get-node-version.outputs.node-version }} directories: | ./examples ./tests @@ -144,15 +146,21 @@ jobs: - uses: actions/checkout@v4 + - id: get-node-version + uses: ./.github/actions/get_node_version + - uses: actions/setup-node@v4 with: - node-version: ${{ env.NODE_VERSION }} + node-version: ${{ steps.get-node-version.outputs.node-version }} + + - id: get-dfx-version + uses: ./.github/actions/get_dfx_version - name: Run pre-test Azle setup run: | - # Install dfx - DFXVM_INIT_YES=true DFX_VERSION=${{ env.DFX_VERSION }} sh -ci "$(curl -fsSL https://sdk.dfinity.org/install.sh)" + # Install dfx (Note: DFX must be installed before `npm install` because the azle installation process requires dfx) + src/build/stable/commands/install_global_dependencies/install_dfx.sh ${{ steps.get-dfx-version.outputs.dfx-version }} echo "$HOME/.local/share/dfx/bin" >> $GITHUB_PATH # MacOS-specific DNS configuration diff --git a/package.json b/package.json index b933f3f450..7632b3fe75 100644 --- a/package.json +++ b/package.json @@ -81,7 +81,8 @@ "globalDependencies": { "wasi2ic": "https://github.com/wasm-forge/wasi2ic?rev=806c3558aad24224852a9582f018178402cb3679#806c3558", "node": "20.11.0", - "rustc": "1.80.1" + "rust": "1.80.1", + "dfx": "0.22.0" } } } diff --git a/src/build/index.ts b/src/build/index.ts index 2730f5aca5..5d4022dc5f 100755 --- a/src/build/index.ts +++ b/src/build/index.ts @@ -12,6 +12,7 @@ import { import { runCommand as runCleanCommand } from './stable/commands/clean'; import { runCommand as runStableCompileCommand } from './stable/commands/compile'; import { runCommand as runInstallDfxExtensionCommand } from './stable/commands/install_dfx_extension'; +import { runCommand as runInstallGlobalDependenciesCommand } from './stable/commands/install_global_dependencies'; import { runCommand as runNewCommand } from './stable/commands/new'; import { runCommand as runStableTemplateCommand } from './stable/commands/template'; import { runCommand as runVersionCommand } from './stable/commands/version'; @@ -38,6 +39,12 @@ async function build(): Promise { return; } + if (command === 'install-global-dependencies') { + handleInstallGlobalDependenciesCommand(ioType); + + return; + } + if (command === 'upload-assets') { await handleUploadAssetsCommand(); @@ -129,6 +136,27 @@ async function handleTemplateCommand(ioType: IOType): Promise { } } +async function handleInstallGlobalDependenciesCommand( + ioType: IOType +): Promise { + const node = process.argv.includes('--node'); + const dfx = process.argv.includes('--dfx'); + const rust = process.argv.includes('--rust'); + const wasi2ic = process.argv.includes('--wasi2ic'); + + if (!node && !dfx && !rust && !wasi2ic) { + await runInstallGlobalDependenciesCommand( + { dfx: true, node: true, rust: true, wasi2ic: true }, + ioType + ); + } else { + await runInstallGlobalDependenciesCommand( + { dfx, node, rust, wasi2ic }, + ioType + ); + } +} + async function handleNewCommand(): Promise { const experimental = process.argv.includes('--experimental'); const httpServer = process.argv.includes('--http-server'); diff --git a/src/build/stable/commands/install_global_dependencies/index.ts b/src/build/stable/commands/install_global_dependencies/index.ts new file mode 100644 index 0000000000..9a0d7040cf --- /dev/null +++ b/src/build/stable/commands/install_global_dependencies/index.ts @@ -0,0 +1,33 @@ +import { IOType } from 'child_process'; + +import { azle } from '../../../../../package.json'; +import { execSyncPretty } from '../../utils/exec_sync_pretty'; +import { AZLE_PACKAGE_PATH } from '../../utils/global_paths'; + +type DependencyName = 'node' | 'dfx' | 'rust' | 'wasi2ic'; + +type DependencyInstallInfo = { + [key in DependencyName]: boolean; +}; + +export async function runCommand( + dependenciesToInstall: DependencyInstallInfo, + ioType: IOType +): Promise { + for (const key in dependenciesToInstall) { + const dependency = key as DependencyName; + if (dependenciesToInstall[dependency] === true) { + installDependency(dependency, ioType); + } + } +} + +function installDependency(dependency: DependencyName, ioType: IOType): void { + console.info(`Installing ${dependency}...`); + const version = azle.globalDependencies[dependency]; + const script = `install_${dependency}.sh`; + execSyncPretty( + `${AZLE_PACKAGE_PATH}/src/build/stable/commands/install_global_dependencies/${script} ${version}`, + ioType + ); +} diff --git a/src/build/stable/commands/install_global_dependencies/install_dfx.sh b/src/build/stable/commands/install_global_dependencies/install_dfx.sh new file mode 100755 index 0000000000..e957673178 --- /dev/null +++ b/src/build/stable/commands/install_global_dependencies/install_dfx.sh @@ -0,0 +1,16 @@ +#!/bin/bash + +if [ -z "$1" ]; then + echo "Error: No DFX version specified." + echo "Usage: ./install_dfx.sh " + exit 1 +fi + +DFX_VERSION=$1 + +# Install or update dfx using the official installation script +echo "Installing dfx version $DFX_VERSION..." +DFXVM_INIT_YES=true DFX_VERSION=$DFX_VERSION sh -ci "$(curl -fsSL https://sdk.dfinity.org/install.sh)" + +echo "dfx $DFX_VERSION installation completed." +exit 0 diff --git a/src/build/stable/commands/install_global_dependencies/install_node.sh b/src/build/stable/commands/install_global_dependencies/install_node.sh new file mode 100755 index 0000000000..93b55f7625 --- /dev/null +++ b/src/build/stable/commands/install_global_dependencies/install_node.sh @@ -0,0 +1,25 @@ +#!/bin/bash + +if [ -z "$1" ]; then + echo "Error: No Node.js version specified." + echo "Usage: ./install_node.sh " + exit 1 +fi + +NODE_VERSION=$1 + +source "$HOME/.nvm/nvm.sh" + +if nvm ls "$NODE_VERSION" &> /dev/null; then + echo "Node.js version $NODE_VERSION is already installed. Skipping installation." + nvm use "$NODE_VERSION" +else + echo "Installing Node.js version $NODE_VERSION..." + nvm install "$NODE_VERSION" + nvm use "$NODE_VERSION" + nvm alias default "$NODE_VERSION" + echo "Node.js $NODE_VERSION installation completed." +fi + +node --version +exit 0 diff --git a/src/build/stable/commands/install_global_dependencies/install_rust.sh b/src/build/stable/commands/install_global_dependencies/install_rust.sh new file mode 100755 index 0000000000..45091a8c7e --- /dev/null +++ b/src/build/stable/commands/install_global_dependencies/install_rust.sh @@ -0,0 +1,24 @@ +#!/bin/bash + +if [ -z "$1" ]; then + echo "Error: No Rust version specified." + echo "Usage: ./install_rust.sh " + exit 1 +fi + +RUST_VERSION=$1 + +# Install Rust +echo "Installing Rust version $RUST_VERSION..." +curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --default-toolchain "$RUST_VERSION" +source $HOME/.cargo/env + +# Add the WASM target for WebAssembly +rustup target add wasm32-wasi + +echo "Rust $RUST_VERSION installation completed." +rustc --version +cargo --version +rustup target list --installed | grep wasm32-wasi + +exit 0 diff --git a/src/build/stable/commands/install_global_dependencies/install_wasi2ic.sh b/src/build/stable/commands/install_global_dependencies/install_wasi2ic.sh new file mode 100755 index 0000000000..3124a4e330 --- /dev/null +++ b/src/build/stable/commands/install_global_dependencies/install_wasi2ic.sh @@ -0,0 +1,29 @@ +#!/bin/bash + +if [ -z "$1" ]; then + echo "Error: No WASI2IC version specified." + echo "Usage: ./install_wasi2ic.sh " + exit 1 +fi + +WASI2IC_VERSION=$1 + +# Check if WASI2IC_VERSION is a URL (repo) or just a version number +if [[ $WASI2IC_VERSION =~ ^https?:// ]]; then + WASI2IC_URL=$WASI2IC_VERSION +else + WASI2IC_URL="" +fi + +# Install or update wasi2ic +if [[ -n "$WASI2IC_URL" ]]; then + echo "Installing wasi2ic from repository $WASI2IC_URL" + cargo install --git "$WASI2IC_URL" +else + echo "Installing wasi2ic version $WASI2IC_VERSION" + cargo install wasi2ic --version "$WASI2IC_VERSION" +fi + +echo "wasi2ic installation completed." +cargo install --list | grep wasi2ic +exit 0 diff --git a/src/build/stable/utils/log_global_dependencies.ts b/src/build/stable/utils/log_global_dependencies.ts index 2652c62875..ac67caf065 100644 --- a/src/build/stable/utils/log_global_dependencies.ts +++ b/src/build/stable/utils/log_global_dependencies.ts @@ -1,18 +1,23 @@ import { readFile, writeFile } from 'fs/promises'; import { join } from 'path'; -import { execSyncPretty } from './exec_sync_pretty'; import { AZLE_PACKAGE_PATH } from './global_paths'; +import { getLocalDfxVersion } from './versions/dfx'; +import { getLocalNodeVersion } from './versions/node'; +import { getLocalRustVersion } from './versions/rust'; +import { getLocalWasi2icVersion } from './versions/wasi2ic'; export async function logGlobalDependencies(): Promise { - const wasiVersion = await getWasiVersion(); - const nodeVersion = await getNodeVersion(); - const rustVersion = await getRustVersion(); + const wasiVersion = getLocalWasi2icVersion(); + const nodeVersion = getLocalNodeVersion(); + const rustVersion = getLocalRustVersion(); + const dfxVersion = getLocalDfxVersion(); const globalDependencies = { wasi2ic: wasiVersion, node: nodeVersion, - rustc: rustVersion + rust: rustVersion, + dfx: dfxVersion }; const packageJsonPath = join(AZLE_PACKAGE_PATH, 'package.json'); @@ -29,49 +34,3 @@ export async function logGlobalDependencies(): Promise { ) ); } - -async function getWasiVersion(): Promise { - return await getCargoVersion('wasi2ic'); -} - -async function getNodeVersion(): Promise { - const nodeOutput = execSyncPretty('node --version').toString().trim(); - const match = nodeOutput.match(/^v(\d+\.\d+\.\d+)/); - - if (match !== null && match.length > 1 && typeof match[1] === 'string') { - return match[1]; // Returns the version number (e.g., "16.13.0") - } else { - throw new Error('Could not parse node version'); - } -} - -async function getRustVersion(): Promise { - const rustcOutput = execSyncPretty('rustc --version').toString().trim(); - const match = rustcOutput.match(/^rustc\s+(\d+\.\d+\.\d+)/); - - if (match !== null && match.length > 1 && typeof match[1] === 'string') { - return match[1]; // Returns the version number - } else { - throw new Error('Could not parse rustc version'); - } -} - -async function getCargoVersion(packageName: string): Promise { - const cargoOutput = execSyncPretty('cargo install --list').toString(); - - // Regular expression to capture both with and without a repository link - const regex = new RegExp( - `${packageName}\\s+v(\\d+\\.\\d+\\.\\d+)(?:\\s+\\((https?:\\/\\/.+?)\\))?` - ); - const match = cargoOutput.match(regex); - - if (match !== null && match.length > 1 && typeof match[1] === 'string') { - if (match.length > 2 && typeof match[2] === 'string') { - return match[2]; // Return the repository link if available - } else { - return match[1]; // Return the version number if no link is found - } - } else { - throw new Error(`Could not parse ${packageName} version`); - } -} diff --git a/src/build/stable/utils/types.ts b/src/build/stable/utils/types.ts index 805fd4bc9b..6debef368b 100644 --- a/src/build/stable/utils/types.ts +++ b/src/build/stable/utils/types.ts @@ -32,6 +32,7 @@ export type Context = { export type Command = | 'compile' | 'install-dfx-extension' + | 'install-global-dependencies' | 'template' | 'upload-assets' | '--version' diff --git a/src/build/stable/utils/versions/dfx.ts b/src/build/stable/utils/versions/dfx.ts new file mode 100644 index 0000000000..d5a8f7e763 --- /dev/null +++ b/src/build/stable/utils/versions/dfx.ts @@ -0,0 +1,13 @@ +import { execSyncPretty } from '../exec_sync_pretty'; + +export function getLocalDfxVersion(): string { + const dfxOutput = execSyncPretty('dfx --version').toString().trim(); + + const match = dfxOutput.match(/dfx (\d+\.\d+\.\d+)/); + + if (match !== null && match.length > 1 && typeof match[1] === 'string') { + return match[1]; // Return the version number + } else { + throw new Error('Could not parse the dfx version'); + } +} diff --git a/src/build/stable/utils/versions/node.ts b/src/build/stable/utils/versions/node.ts new file mode 100644 index 0000000000..0f045f0aa9 --- /dev/null +++ b/src/build/stable/utils/versions/node.ts @@ -0,0 +1,12 @@ +import { execSyncPretty } from '../exec_sync_pretty'; + +export function getLocalNodeVersion(): string { + const nodeOutput = execSyncPretty('node --version').toString().trim(); + const match = nodeOutput.match(/^v(\d+\.\d+\.\d+)/); + + if (match !== null && match.length > 1 && typeof match[1] === 'string') { + return match[1]; // Returns the version number (e.g., "16.13.0") + } else { + throw new Error('Could not parse node version'); + } +} diff --git a/src/build/stable/utils/versions/rust.ts b/src/build/stable/utils/versions/rust.ts new file mode 100644 index 0000000000..faa5b58650 --- /dev/null +++ b/src/build/stable/utils/versions/rust.ts @@ -0,0 +1,12 @@ +import { execSyncPretty } from '../exec_sync_pretty'; + +export function getLocalRustVersion(): string { + const rustcOutput = execSyncPretty('rustc --version').toString().trim(); + const match = rustcOutput.match(/^rustc\s+(\d+\.\d+\.\d+)/); + + if (match !== null && match.length > 1 && typeof match[1] === 'string') { + return match[1]; // Returns the version number + } else { + throw new Error('Could not parse rustc version'); + } +} diff --git a/src/build/stable/utils/versions/wasi2ic.ts b/src/build/stable/utils/versions/wasi2ic.ts new file mode 100644 index 0000000000..23d5a6db20 --- /dev/null +++ b/src/build/stable/utils/versions/wasi2ic.ts @@ -0,0 +1,25 @@ +import { execSyncPretty } from '../exec_sync_pretty'; + +export function getLocalWasi2icVersion(): string { + return getCargoVersion('wasi2ic'); +} + +function getCargoVersion(packageName: string): string { + const cargoOutput = execSyncPretty('cargo install --list').toString(); + + // Regular expression to capture both with and without a repository link + const regex = new RegExp( + `${packageName}\\s+v(\\d+\\.\\d+\\.\\d+)(?:\\s+\\((https?:\\/\\/.+?)\\))?` + ); + const match = cargoOutput.match(regex); + + if (match !== null && match.length > 1 && typeof match[1] === 'string') { + if (match.length > 2 && typeof match[2] === 'string') { + return match[2]; // Return the repository link if available + } else { + return match[1]; // Return the version number if no link is found + } + } else { + throw new Error(`Could not parse ${packageName} version`); + } +}