diff --git a/.github/workflows/gui.yml b/.github/workflows/gui.yml index b096c60802..a107781c86 100644 --- a/.github/workflows/gui.yml +++ b/.github/workflows/gui.yml @@ -24,7 +24,7 @@ jobs: conda-channels: anaconda, conda-forge - if: startsWith(runner.name, 'GitHub Actions') || startsWith(runner.name, 'Hosted Agent') name: Installing wasm-pack - uses: jetli/wasm-pack-action@v0.4.0 + uses: jetli/wasm-pack-action@v0.3.0 with: version: v0.10.2 - name: Expose Artifact API and context information. @@ -225,7 +225,7 @@ jobs: run: ./run git-clean env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - run: ./run --upload-artifacts ${{ runner.os == 'Linux' }} wasm build + - run: ./run wasm build --wasm-upload-artifact ${{ runner.os == 'Linux' }} env: ENSO_AG_GRID_LICENSE_KEY: ${{ secrets.ENSO_AG_GRID_LICENSE_KEY }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} @@ -282,7 +282,7 @@ jobs: run: ./run git-clean env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - run: ./run --upload-artifacts ${{ runner.os == 'Linux' }} wasm build + - run: ./run wasm build --wasm-upload-artifact ${{ runner.os == 'Linux' }} env: ENSO_AG_GRID_LICENSE_KEY: ${{ secrets.ENSO_AG_GRID_LICENSE_KEY }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} @@ -341,7 +341,7 @@ jobs: run: ./run git-clean env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - run: ./run --upload-artifacts ${{ runner.os == 'Linux' }} wasm build + - run: ./run wasm build --wasm-upload-artifact ${{ runner.os == 'Linux' }} env: ENSO_AG_GRID_LICENSE_KEY: ${{ secrets.ENSO_AG_GRID_LICENSE_KEY }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} @@ -483,8 +483,423 @@ jobs: run: ./run git-clean env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - enso-build-ci-gen-job-package-ide-linux: - name: Package IDE (linux) + enso-build-ci-gen-job-new-gui-build-linux: + name: New (Vue) GUI build (linux) + runs-on: + - self-hosted + - Linux + - engine + steps: + - if: startsWith(runner.name, 'GitHub Actions') || startsWith(runner.name, 'Hosted Agent') + name: Setup conda (GH runners only) + uses: s-weigand/setup-conda@v1.0.6 + with: + update-conda: false + conda-channels: anaconda, conda-forge + - if: startsWith(runner.name, 'GitHub Actions') || startsWith(runner.name, 'Hosted Agent') + name: Installing wasm-pack + uses: jetli/wasm-pack-action@v0.3.0 + with: + version: v0.10.2 + - name: Expose Artifact API and context information. + uses: actions/github-script@v6 + with: + script: "\n core.exportVariable(\"ACTIONS_RUNTIME_TOKEN\", process.env[\"ACTIONS_RUNTIME_TOKEN\"])\n core.exportVariable(\"ACTIONS_RUNTIME_URL\", process.env[\"ACTIONS_RUNTIME_URL\"])\n core.exportVariable(\"GITHUB_RETENTION_DAYS\", process.env[\"GITHUB_RETENTION_DAYS\"])\n console.log(context)\n " + - if: runner.os == 'Windows' + name: Workaround for https://github.com/actions/checkout/issues/590 (Windows) + run: '"c:\Program Files\Git\bin\bash.exe" -c "git checkout -f $(git -c user.name=x -c user.email=x@x commit-tree $(git hash-object -t tree /dev/null) < /dev/null) || :"' + shell: cmd + - if: runner.os != 'Windows' + name: Workaround for https://github.com/actions/checkout/issues/590 (non-Windows) + run: "git checkout -f $(git -c user.name=x -c user.email=x@x commit-tree $(git hash-object -t tree /dev/null) < /dev/null) || :" + shell: bash + - name: Checking out the repository + uses: actions/checkout@v2 + with: + clean: false + submodules: recursive + - name: Build Script Setup + run: ./run --help + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - if: "contains(github.event.pull_request.labels.*.name, 'CI: Clean build required')" + name: Clean before + run: ./run git-clean + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - run: ./run gui2 build + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - if: failure() && runner.os == 'Windows' + name: List files if failed (Windows) + run: Get-ChildItem -Force -Recurse + - if: failure() && runner.os != 'Windows' + name: List files if failed (non-Windows) + run: ls -lAR + - if: "always() && always() && contains(github.event.pull_request.labels.*.name, 'CI: Clean build required')" + name: Clean after + run: ./run git-clean + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + enso-build-ci-gen-job-new-gui-build-macos: + name: New (Vue) GUI build (macos) + runs-on: + - macos-latest + steps: + - if: startsWith(runner.name, 'GitHub Actions') || startsWith(runner.name, 'Hosted Agent') + name: Setup conda (GH runners only) + uses: s-weigand/setup-conda@v1.0.6 + with: + update-conda: false + conda-channels: anaconda, conda-forge + - if: startsWith(runner.name, 'GitHub Actions') || startsWith(runner.name, 'Hosted Agent') + name: Installing wasm-pack + uses: jetli/wasm-pack-action@v0.3.0 + with: + version: v0.10.2 + - name: Expose Artifact API and context information. + uses: actions/github-script@v6 + with: + script: "\n core.exportVariable(\"ACTIONS_RUNTIME_TOKEN\", process.env[\"ACTIONS_RUNTIME_TOKEN\"])\n core.exportVariable(\"ACTIONS_RUNTIME_URL\", process.env[\"ACTIONS_RUNTIME_URL\"])\n core.exportVariable(\"GITHUB_RETENTION_DAYS\", process.env[\"GITHUB_RETENTION_DAYS\"])\n console.log(context)\n " + - if: runner.os == 'Windows' + name: Workaround for https://github.com/actions/checkout/issues/590 (Windows) + run: '"c:\Program Files\Git\bin\bash.exe" -c "git checkout -f $(git -c user.name=x -c user.email=x@x commit-tree $(git hash-object -t tree /dev/null) < /dev/null) || :"' + shell: cmd + - if: runner.os != 'Windows' + name: Workaround for https://github.com/actions/checkout/issues/590 (non-Windows) + run: "git checkout -f $(git -c user.name=x -c user.email=x@x commit-tree $(git hash-object -t tree /dev/null) < /dev/null) || :" + shell: bash + - name: Checking out the repository + uses: actions/checkout@v2 + with: + clean: false + submodules: recursive + - name: Build Script Setup + run: ./run --help + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - if: "contains(github.event.pull_request.labels.*.name, 'CI: Clean build required')" + name: Clean before + run: ./run git-clean + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - run: ./run gui2 build + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - if: failure() && runner.os == 'Windows' + name: List files if failed (Windows) + run: Get-ChildItem -Force -Recurse + - if: failure() && runner.os != 'Windows' + name: List files if failed (non-Windows) + run: ls -lAR + - if: "always() && always() && contains(github.event.pull_request.labels.*.name, 'CI: Clean build required')" + name: Clean after + run: ./run git-clean + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + enso-build-ci-gen-job-new-gui-build-windows: + name: New (Vue) GUI build (windows) + runs-on: + - self-hosted + - Windows + - engine + steps: + - if: startsWith(runner.name, 'GitHub Actions') || startsWith(runner.name, 'Hosted Agent') + name: Setup conda (GH runners only) + uses: s-weigand/setup-conda@v1.0.6 + with: + update-conda: false + conda-channels: anaconda, conda-forge + - if: startsWith(runner.name, 'GitHub Actions') || startsWith(runner.name, 'Hosted Agent') + name: Installing wasm-pack + uses: jetli/wasm-pack-action@v0.3.0 + with: + version: v0.10.2 + - name: Expose Artifact API and context information. + uses: actions/github-script@v6 + with: + script: "\n core.exportVariable(\"ACTIONS_RUNTIME_TOKEN\", process.env[\"ACTIONS_RUNTIME_TOKEN\"])\n core.exportVariable(\"ACTIONS_RUNTIME_URL\", process.env[\"ACTIONS_RUNTIME_URL\"])\n core.exportVariable(\"GITHUB_RETENTION_DAYS\", process.env[\"GITHUB_RETENTION_DAYS\"])\n console.log(context)\n " + - if: runner.os == 'Windows' + name: Workaround for https://github.com/actions/checkout/issues/590 (Windows) + run: '"c:\Program Files\Git\bin\bash.exe" -c "git checkout -f $(git -c user.name=x -c user.email=x@x commit-tree $(git hash-object -t tree /dev/null) < /dev/null) || :"' + shell: cmd + - if: runner.os != 'Windows' + name: Workaround for https://github.com/actions/checkout/issues/590 (non-Windows) + run: "git checkout -f $(git -c user.name=x -c user.email=x@x commit-tree $(git hash-object -t tree /dev/null) < /dev/null) || :" + shell: bash + - name: Checking out the repository + uses: actions/checkout@v2 + with: + clean: false + submodules: recursive + - name: Build Script Setup + run: ./run --help + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - if: "contains(github.event.pull_request.labels.*.name, 'CI: Clean build required')" + name: Clean before + run: ./run git-clean + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - run: ./run gui2 build + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - if: failure() && runner.os == 'Windows' + name: List files if failed (Windows) + run: Get-ChildItem -Force -Recurse + - if: failure() && runner.os != 'Windows' + name: List files if failed (non-Windows) + run: ls -lAR + - if: "always() && always() && contains(github.event.pull_request.labels.*.name, 'CI: Clean build required')" + name: Clean after + run: ./run git-clean + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + enso-build-ci-gen-job-new-gui-test-linux: + name: New (Vue) GUI tests (linux) + runs-on: + - self-hosted + - Linux + - engine + steps: + - if: startsWith(runner.name, 'GitHub Actions') || startsWith(runner.name, 'Hosted Agent') + name: Setup conda (GH runners only) + uses: s-weigand/setup-conda@v1.0.6 + with: + update-conda: false + conda-channels: anaconda, conda-forge + - if: startsWith(runner.name, 'GitHub Actions') || startsWith(runner.name, 'Hosted Agent') + name: Installing wasm-pack + uses: jetli/wasm-pack-action@v0.3.0 + with: + version: v0.10.2 + - name: Expose Artifact API and context information. + uses: actions/github-script@v6 + with: + script: "\n core.exportVariable(\"ACTIONS_RUNTIME_TOKEN\", process.env[\"ACTIONS_RUNTIME_TOKEN\"])\n core.exportVariable(\"ACTIONS_RUNTIME_URL\", process.env[\"ACTIONS_RUNTIME_URL\"])\n core.exportVariable(\"GITHUB_RETENTION_DAYS\", process.env[\"GITHUB_RETENTION_DAYS\"])\n console.log(context)\n " + - if: runner.os == 'Windows' + name: Workaround for https://github.com/actions/checkout/issues/590 (Windows) + run: '"c:\Program Files\Git\bin\bash.exe" -c "git checkout -f $(git -c user.name=x -c user.email=x@x commit-tree $(git hash-object -t tree /dev/null) < /dev/null) || :"' + shell: cmd + - if: runner.os != 'Windows' + name: Workaround for https://github.com/actions/checkout/issues/590 (non-Windows) + run: "git checkout -f $(git -c user.name=x -c user.email=x@x commit-tree $(git hash-object -t tree /dev/null) < /dev/null) || :" + shell: bash + - name: Checking out the repository + uses: actions/checkout@v2 + with: + clean: false + submodules: recursive + - name: Build Script Setup + run: ./run --help + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - if: "contains(github.event.pull_request.labels.*.name, 'CI: Clean build required')" + name: Clean before + run: ./run git-clean + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - run: ./run gui2 test + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - if: failure() && runner.os == 'Windows' + name: List files if failed (Windows) + run: Get-ChildItem -Force -Recurse + - if: failure() && runner.os != 'Windows' + name: List files if failed (non-Windows) + run: ls -lAR + - if: "always() && always() && contains(github.event.pull_request.labels.*.name, 'CI: Clean build required')" + name: Clean after + run: ./run git-clean + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + enso-build-ci-gen-job-package-new-ide-linux: + name: Package New IDE (linux) + needs: + - enso-build-ci-gen-job-build-backend-linux + runs-on: + - self-hosted + - Linux + - engine + steps: + - if: startsWith(runner.name, 'GitHub Actions') || startsWith(runner.name, 'Hosted Agent') + name: Setup conda (GH runners only) + uses: s-weigand/setup-conda@v1.0.6 + with: + update-conda: false + conda-channels: anaconda, conda-forge + - if: startsWith(runner.name, 'GitHub Actions') || startsWith(runner.name, 'Hosted Agent') + name: Installing wasm-pack + uses: jetli/wasm-pack-action@v0.3.0 + with: + version: v0.10.2 + - name: Expose Artifact API and context information. + uses: actions/github-script@v6 + with: + script: "\n core.exportVariable(\"ACTIONS_RUNTIME_TOKEN\", process.env[\"ACTIONS_RUNTIME_TOKEN\"])\n core.exportVariable(\"ACTIONS_RUNTIME_URL\", process.env[\"ACTIONS_RUNTIME_URL\"])\n core.exportVariable(\"GITHUB_RETENTION_DAYS\", process.env[\"GITHUB_RETENTION_DAYS\"])\n console.log(context)\n " + - if: runner.os == 'Windows' + name: Workaround for https://github.com/actions/checkout/issues/590 (Windows) + run: '"c:\Program Files\Git\bin\bash.exe" -c "git checkout -f $(git -c user.name=x -c user.email=x@x commit-tree $(git hash-object -t tree /dev/null) < /dev/null) || :"' + shell: cmd + - if: runner.os != 'Windows' + name: Workaround for https://github.com/actions/checkout/issues/590 (non-Windows) + run: "git checkout -f $(git -c user.name=x -c user.email=x@x commit-tree $(git hash-object -t tree /dev/null) < /dev/null) || :" + shell: bash + - name: Checking out the repository + uses: actions/checkout@v2 + with: + clean: false + submodules: recursive + - name: Build Script Setup + run: ./run --help + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - if: "contains(github.event.pull_request.labels.*.name, 'CI: Clean build required')" + name: Clean before + run: ./run git-clean + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - run: ./run ide2 build --backend-source current-ci-run --gui2-upload-artifact false + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - if: failure() && runner.os == 'Windows' + name: List files if failed (Windows) + run: Get-ChildItem -Force -Recurse + - if: failure() && runner.os != 'Windows' + name: List files if failed (non-Windows) + run: ls -lAR + - if: "always() && always() && contains(github.event.pull_request.labels.*.name, 'CI: Clean build required')" + name: Clean after + run: ./run git-clean + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + enso-build-ci-gen-job-package-new-ide-macos: + name: Package New IDE (macos) + needs: + - enso-build-ci-gen-job-build-backend-macos + runs-on: + - macos-latest + steps: + - if: startsWith(runner.name, 'GitHub Actions') || startsWith(runner.name, 'Hosted Agent') + name: Setup conda (GH runners only) + uses: s-weigand/setup-conda@v1.0.6 + with: + update-conda: false + conda-channels: anaconda, conda-forge + - if: startsWith(runner.name, 'GitHub Actions') || startsWith(runner.name, 'Hosted Agent') + name: Installing wasm-pack + uses: jetli/wasm-pack-action@v0.3.0 + with: + version: v0.10.2 + - name: Expose Artifact API and context information. + uses: actions/github-script@v6 + with: + script: "\n core.exportVariable(\"ACTIONS_RUNTIME_TOKEN\", process.env[\"ACTIONS_RUNTIME_TOKEN\"])\n core.exportVariable(\"ACTIONS_RUNTIME_URL\", process.env[\"ACTIONS_RUNTIME_URL\"])\n core.exportVariable(\"GITHUB_RETENTION_DAYS\", process.env[\"GITHUB_RETENTION_DAYS\"])\n console.log(context)\n " + - if: runner.os == 'Windows' + name: Workaround for https://github.com/actions/checkout/issues/590 (Windows) + run: '"c:\Program Files\Git\bin\bash.exe" -c "git checkout -f $(git -c user.name=x -c user.email=x@x commit-tree $(git hash-object -t tree /dev/null) < /dev/null) || :"' + shell: cmd + - if: runner.os != 'Windows' + name: Workaround for https://github.com/actions/checkout/issues/590 (non-Windows) + run: "git checkout -f $(git -c user.name=x -c user.email=x@x commit-tree $(git hash-object -t tree /dev/null) < /dev/null) || :" + shell: bash + - name: Checking out the repository + uses: actions/checkout@v2 + with: + clean: false + submodules: recursive + - name: Build Script Setup + run: ./run --help + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - if: "contains(github.event.pull_request.labels.*.name, 'CI: Clean build required')" + name: Clean before + run: ./run git-clean + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - run: ./run ide2 build --backend-source current-ci-run --gui2-upload-artifact false + env: + APPLEID: ${{ secrets.APPLE_NOTARIZATION_USERNAME }} + APPLEIDPASS: ${{ secrets.APPLE_NOTARIZATION_PASSWORD }} + CSC_IDENTITY_AUTO_DISCOVERY: "true" + CSC_KEY_PASSWORD: ${{ secrets.APPLE_CODE_SIGNING_CERT_PASSWORD }} + CSC_LINK: ${{ secrets.APPLE_CODE_SIGNING_CERT }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - if: failure() && runner.os == 'Windows' + name: List files if failed (Windows) + run: Get-ChildItem -Force -Recurse + - if: failure() && runner.os != 'Windows' + name: List files if failed (non-Windows) + run: ls -lAR + - if: "always() && always() && contains(github.event.pull_request.labels.*.name, 'CI: Clean build required')" + name: Clean after + run: ./run git-clean + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + enso-build-ci-gen-job-package-new-ide-windows: + name: Package New IDE (windows) + needs: + - enso-build-ci-gen-job-build-backend-windows + runs-on: + - self-hosted + - Windows + - engine + steps: + - if: startsWith(runner.name, 'GitHub Actions') || startsWith(runner.name, 'Hosted Agent') + name: Setup conda (GH runners only) + uses: s-weigand/setup-conda@v1.0.6 + with: + update-conda: false + conda-channels: anaconda, conda-forge + - if: startsWith(runner.name, 'GitHub Actions') || startsWith(runner.name, 'Hosted Agent') + name: Installing wasm-pack + uses: jetli/wasm-pack-action@v0.3.0 + with: + version: v0.10.2 + - name: Expose Artifact API and context information. + uses: actions/github-script@v6 + with: + script: "\n core.exportVariable(\"ACTIONS_RUNTIME_TOKEN\", process.env[\"ACTIONS_RUNTIME_TOKEN\"])\n core.exportVariable(\"ACTIONS_RUNTIME_URL\", process.env[\"ACTIONS_RUNTIME_URL\"])\n core.exportVariable(\"GITHUB_RETENTION_DAYS\", process.env[\"GITHUB_RETENTION_DAYS\"])\n console.log(context)\n " + - if: runner.os == 'Windows' + name: Workaround for https://github.com/actions/checkout/issues/590 (Windows) + run: '"c:\Program Files\Git\bin\bash.exe" -c "git checkout -f $(git -c user.name=x -c user.email=x@x commit-tree $(git hash-object -t tree /dev/null) < /dev/null) || :"' + shell: cmd + - if: runner.os != 'Windows' + name: Workaround for https://github.com/actions/checkout/issues/590 (non-Windows) + run: "git checkout -f $(git -c user.name=x -c user.email=x@x commit-tree $(git hash-object -t tree /dev/null) < /dev/null) || :" + shell: bash + - name: Checking out the repository + uses: actions/checkout@v2 + with: + clean: false + submodules: recursive + - name: Build Script Setup + run: ./run --help + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - if: "contains(github.event.pull_request.labels.*.name, 'CI: Clean build required')" + name: Clean before + run: ./run git-clean + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - run: ./run ide2 build --backend-source current-ci-run --gui2-upload-artifact false + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + WIN_CSC_KEY_PASSWORD: ${{ secrets.MICROSOFT_CODE_SIGNING_CERT_PASSWORD }} + WIN_CSC_LINK: ${{ secrets.MICROSOFT_CODE_SIGNING_CERT }} + - if: failure() && runner.os == 'Windows' + name: List files if failed (Windows) + run: Get-ChildItem -Force -Recurse + - if: failure() && runner.os != 'Windows' + name: List files if failed (non-Windows) + run: ls -lAR + - if: "always() && always() && contains(github.event.pull_request.labels.*.name, 'CI: Clean build required')" + name: Clean after + run: ./run git-clean + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + enso-build-ci-gen-job-package-old-ide-linux: + name: Package Old IDE (linux) needs: - enso-build-ci-gen-job-build-backend-linux - enso-build-ci-gen-job-build-wasm-linux @@ -544,8 +959,8 @@ jobs: run: ./run git-clean env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - enso-build-ci-gen-job-package-ide-macos: - name: Package IDE (macos) + enso-build-ci-gen-job-package-old-ide-macos: + name: Package Old IDE (macos) needs: - enso-build-ci-gen-job-build-backend-macos - enso-build-ci-gen-job-build-wasm-linux @@ -608,8 +1023,8 @@ jobs: run: ./run git-clean env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - enso-build-ci-gen-job-package-ide-windows: - name: Package IDE (windows) + enso-build-ci-gen-job-package-old-ide-windows: + name: Package Old IDE (windows) needs: - enso-build-ci-gen-job-build-backend-windows - enso-build-ci-gen-job-build-wasm-linux diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index e617efc486..4b9676db48 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -106,7 +106,7 @@ jobs: run: ./run git-clean env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - run: ./run --upload-artifacts ${{ runner.os == 'Linux' }} wasm build + - run: ./run wasm build --wasm-upload-artifact ${{ runner.os == 'Linux' }} env: ENSO_AG_GRID_LICENSE_KEY: ${{ secrets.ENSO_AG_GRID_LICENSE_KEY }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.gitignore b/.gitignore index 4365896ac5..4876cf4bb9 100644 --- a/.gitignore +++ b/.gitignore @@ -91,14 +91,17 @@ bench-report*.xml ## Binaries ## ############## +/built-distribution/ /enso /project-manager -*.exe /enso.exp /enso.lib + +*.dll +*.exe *.pdb +*.so *.jar -/built-distribution/ ######### diff --git a/.prettierignore b/.prettierignore index 2230d27e90..b4299bd8fb 100644 --- a/.prettierignore +++ b/.prettierignore @@ -40,5 +40,9 @@ ci-build/ enso/ # Popular IDEs -.idea .bsp +.idea +.vscode + +# Stray files generated during build +svm_*.md diff --git a/.vscode/settings.json b/.vscode/settings.json index 4b5b41a39e..3b160f135b 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,10 +1,15 @@ { - "rust-analyzer.linkedProjects": ["./app/gui2/rust-ffi/Cargo.toml"], + "rust-analyzer.linkedProjects": [ + "./app/gui2/rust-ffi/Cargo.toml" + ], "vue.complete.casing.status": false, "vue.complete.casing.props": "camel", "vue.complete.casing.tags": "pascal", "auto-snippets.snippets": [ - { "language": "vue", "snippet": "Vue single-file component" } + { + "language": "vue", + "snippet": "Vue single-file component" + } ], "typescript.tsdk": "node_modules/typescript/lib", "eslint.experimental.useFlatConfig": true, diff --git a/app/gui2/package.json b/app/gui2/package.json index cb094ecf57..3c3f44f05f 100644 --- a/app/gui2/package.json +++ b/app/gui2/package.json @@ -9,7 +9,7 @@ }, "scripts": { "dev": "vite", - "build": "run-p typecheck build-only", + "build": "npm --workspace enso-authentication run compile && run-p typecheck build-only", "preview": "vite preview", "test:unit": "vitest", "test:e2e": "playwright test", diff --git a/app/gui2/shared/yjsModel.ts b/app/gui2/shared/yjsModel.ts index 8dfeb1f3f5..32a82ed0de 100644 --- a/app/gui2/shared/yjsModel.ts +++ b/app/gui2/shared/yjsModel.ts @@ -97,12 +97,12 @@ export class DistributedModule { } insertNewNode(offset: number, content: string, meta: NodeMetadata): ExprId { - const range = [offset, offset + content.length] + const range = [offset, offset + content.length] as const const newId = random.uuidv4() as ExprId this.transact(() => { this.doc.contents.insert(offset, content + '\n') - const start = Y.createRelativePositionFromTypeIndex(this.doc.contents, range[0]!) - const end = Y.createRelativePositionFromTypeIndex(this.doc.contents, range[1]!) + const start = Y.createRelativePositionFromTypeIndex(this.doc.contents, range[0], -1) + const end = Y.createRelativePositionFromTypeIndex(this.doc.contents, range[1]) this.doc.idMap.set(newId, encodeRange([start, end])) this.doc.metadata.set(newId, meta) }) @@ -281,7 +281,7 @@ export class IdMap { // For all remaining expressions, we need to write them into the map. if (!this.accessed.has(expr)) return const range = IdMap.rangeForKey(key) - const start = Y.createRelativePositionFromTypeIndex(this.contents, range[0]) + const start = Y.createRelativePositionFromTypeIndex(this.contents, range[0], -1) const end = Y.createRelativePositionFromTypeIndex(this.contents, range[1]) const encoded = encodeRange([start, end]) this.yMap.set(expr, encoded) diff --git a/app/gui2/src/main.ts b/app/gui2/src/main.ts index ee805d010f..23b1351cfb 100644 --- a/app/gui2/src/main.ts +++ b/app/gui2/src/main.ts @@ -5,6 +5,7 @@ const INITIAL_URL_KEY = `Enso-initial-url` import './assets/main.css' import { basicSetup } from 'codemirror' +import * as dashboard from 'enso-authentication' import { isMac } from 'lib0/environment' import { decodeQueryParams } from 'lib0/url' import { createPinia } from 'pinia' @@ -46,8 +47,6 @@ function stopApp() { const appRunner = { runApp, stopApp } -const dashboard = await import('enso-authentication') - /** The entrypoint into the IDE. */ function main() { /** Note: Signing out always redirects to `/`. It is impossible to make this work, diff --git a/app/gui2/src/stores/graph.ts b/app/gui2/src/stores/graph.ts index 65ca3f2d20..2b6fe5159c 100644 --- a/app/gui2/src/stores/graph.ts +++ b/app/gui2/src/stores/graph.ts @@ -154,7 +154,7 @@ export const useGraphStore = defineStore('graph', () => { rootSpan: stmt.expression, position: meta == null ? Vec2.Zero() : new Vec2(meta.x, -meta.y), docRange: [ - Y.createRelativePositionFromTypeIndex(text, stmt.exprOffset), + Y.createRelativePositionFromTypeIndex(text, stmt.exprOffset, -1), Y.createRelativePositionFromTypeIndex(text, stmt.exprOffset + stmt.expression.length), ], } diff --git a/app/gui2/ydoc-server/languageServerSession.ts b/app/gui2/ydoc-server/languageServerSession.ts index 09816c88ce..807dd840f9 100644 --- a/app/gui2/ydoc-server/languageServerSession.ts +++ b/app/gui2/ydoc-server/languageServerSession.ts @@ -349,16 +349,18 @@ class ModulePersistence extends ObservableV2<{ removed: () => void }> { newContent += synced.code } - newContent += META_TAG + '\n' - const metaStartLine = (newContent.match(/\n/g) ?? []).length + let metaContent = META_TAG + '\n' - if (idMapKeys != null || (contentDelta && contentDelta.length > 0)) { + if ( + idMapKeys != null || + synced.idMapJson == null || + (contentDelta && contentDelta.length > 0) + ) { const idMapJson = json.stringify(idMapToArray(this.doc.idMap)) - allEdits.push(...applyDiffAsTextEdits(metaStartLine, synced.idMapJson ?? '', idMapJson)) - newContent += idMapJson + '\n' + metaContent += idMapJson + '\n' } else { - newContent += synced.idMapJson + '\n' + metaContent += (synced.idMapJson ?? '[]') + '\n' } const nodeMetadata = this.syncedMeta.ide.node @@ -385,14 +387,15 @@ class ModulePersistence extends ObservableV2<{ removed: () => void }> { } const metadataJson = json.stringify(this.syncedMeta) - allEdits.push( - ...applyDiffAsTextEdits(metaStartLine + 1, synced.metadataJson ?? '', metadataJson), - ) - newContent += metadataJson + metaContent += metadataJson } else { - newContent += synced.metadataJson + metaContent += synced.metadataJson ?? '{}' } + const oldMetaContent = this.syncedContent.slice(synced.code.length) + allEdits.push(...applyDiffAsTextEdits(metaStartLine, oldMetaContent, metaContent)) + newContent += metaContent + const newVersion = computeTextChecksum(newContent) if (DEBUG_LOG_SYNC) { diff --git a/app/ide-desktop/lib/client/electron-builder-config.ts b/app/ide-desktop/lib/client/electron-builder-config.ts index c2aeacf33b..c000af77ed 100644 --- a/app/ide-desktop/lib/client/electron-builder-config.ts +++ b/app/ide-desktop/lib/client/electron-builder-config.ts @@ -99,7 +99,7 @@ export function createElectronBuilderConfig(passedArgs: Arguments): electronBuil extraMetadata: { version: BUILD_INFO.version, }, - copyright: 'Copyright © 2022 ${author}.', + copyright: `Copyright © ${new Date().getFullYear()} ${common.COMPANY_NAME}`, artifactName: 'enso-${os}-${version}.${ext}', /** Definitions of URL {@link electronBuilder.Protocol} schemes used by the IDE. * diff --git a/app/ide-desktop/lib/client/src/bin/server.ts b/app/ide-desktop/lib/client/src/bin/server.ts index 5aa4869242..d541dd8768 100644 --- a/app/ide-desktop/lib/client/src/bin/server.ts +++ b/app/ide-desktop/lib/client/src/bin/server.ts @@ -100,6 +100,7 @@ export class Server { logger.error(`Error creating server:`, err.http) reject(err) } + // Prepare the YDoc server access point for the new Vue-based GUI. if (httpServer) { // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, // @typescript-eslint/no-unsafe-call diff --git a/app/ide-desktop/lib/common/src/index.ts b/app/ide-desktop/lib/common/src/index.ts index fea522f613..339a1d53a1 100644 --- a/app/ide-desktop/lib/common/src/index.ts +++ b/app/ide-desktop/lib/common/src/index.ts @@ -18,6 +18,9 @@ export const DEEP_LINK_SCHEME = 'enso' /** Name of the product. */ export const PRODUCT_NAME = 'Enso' +/** Company name, used as the copyright holder. */ +export const COMPANY_NAME = 'New Byte Order sp. z o.o.' + /** COOP, COEP, and CORP headers: https://web.dev/coop-coep/ * * These are required to increase the resolution of `performance.now()` timers, diff --git a/app/ide-desktop/lib/dashboard/src/authentication/package.json b/app/ide-desktop/lib/dashboard/src/authentication/package.json index 7e69e68319..f5cfbe9367 100644 --- a/app/ide-desktop/lib/dashboard/src/authentication/package.json +++ b/app/ide-desktop/lib/dashboard/src/authentication/package.json @@ -9,6 +9,7 @@ "./tailwind.css": "./tailwind.css" }, "scripts": { + "compile": "tsc", "typecheck": "tsc --noEmit" }, "dependencies": { diff --git a/app/ide-desktop/lib/types/modules.d.ts b/app/ide-desktop/lib/types/modules.d.ts index 2e26ee8905..2966c84042 100644 --- a/app/ide-desktop/lib/types/modules.d.ts +++ b/app/ide-desktop/lib/types/modules.d.ts @@ -185,11 +185,7 @@ declare module 'create-servers' { option: CreateServersOptions, // The types come from a third-party API and cannot be changed. // eslint-disable-next-line no-restricted-syntax - handler: ( - // eslint-disable-next-line no-restricted-syntax - err: HttpError | undefined, - servers: CreatedServers - ) => void + handler: (err: HttpError | undefined, servers: CreatedServers) => void ): unknown } diff --git a/build-config.yaml b/build-config.yaml index e3f4211690..67067d5910 100644 --- a/build-config.yaml +++ b/build-config.yaml @@ -6,7 +6,7 @@ required-versions: # NB. The Rust version is pinned in rust-toolchain.toml. # NB. The Node version is pinned in .node-version. cargo-watch: ^8.1.1 - wasm-pack: ^0.10.2 + wasm-pack: ^0.12.1 # TODO [mwu]: Script can install `flatc` later on (if `conda` is present), so this is not required. However it should # be required, if `conda` is missing. # flatc: =1.12.0 diff --git a/build/build/paths.yaml b/build/build/paths.yaml index 48cf3c6bf7..4a105fadfc 100644 --- a/build/build/paths.yaml +++ b/build/build/paths.yaml @@ -15,6 +15,8 @@ shader-tools.yml: app/: gui/: + gui2/: # The new, Vue-based GUI. + dist/: ide-desktop/: lib/: client/: @@ -57,6 +59,9 @@ index.js: style.css: + gui2/: + assets/: + # Final WASM artifacts in `dist` directory. wasm/: dynamic-assets/: # Assets used by the WASM application. diff --git a/build/build/src/ci_gen.rs b/build/build/src/ci_gen.rs index 287487f0c4..97e119b1b5 100644 --- a/build/build/src/ci_gen.rs +++ b/build/build/src/ci_gen.rs @@ -445,6 +445,7 @@ pub fn gui() -> Result { workflow.add(PRIMARY_OS, job::Lint); workflow.add(PRIMARY_OS, job::WasmTest); workflow.add(PRIMARY_OS, job::NativeTest); + workflow.add(PRIMARY_OS, job::NewGuiTest); // FIXME: Integration tests are currently always failing. // The should be reinstated when fixed. @@ -461,10 +462,14 @@ pub fn gui() -> Result { let _wasm_job = workflow.add(os, job::BuildWasm); } let project_manager_job = workflow.add(os, job::BuildBackend); - workflow.add_customized(os, job::PackageIde, |job| { + workflow.add_customized(os, job::PackageOldIde, |job| { job.needs.insert(wasm_job_linux.clone()); - job.needs.insert(project_manager_job); + job.needs.insert(project_manager_job.clone()); }); + workflow.add_customized(os, job::PackageNewIde, |job| { + job.needs.insert(project_manager_job.clone()); + }); + workflow.add(os, job::NewGuiBuild); } Ok(workflow) } diff --git a/build/build/src/ci_gen/job.rs b/build/build/src/ci_gen/job.rs index ca446d7797..cf60b4f5eb 100644 --- a/build/build/src/ci_gen/job.rs +++ b/build/build/src/ci_gen/job.rs @@ -105,6 +105,22 @@ impl JobArchetype for NativeTest { } } +#[derive(Clone, Copy, Debug)] +pub struct NewGuiTest; +impl JobArchetype for NewGuiTest { + fn job(&self, os: OS) -> Job { + plain_job(&os, "New (Vue) GUI tests", "gui2 test") + } +} + +#[derive(Clone, Copy, Debug)] +pub struct NewGuiBuild; +impl JobArchetype for NewGuiBuild { + fn job(&self, os: OS) -> Job { + plain_job(&os, "New (Vue) GUI build", "gui2 build") + } +} + #[derive(Clone, Copy, Debug)] pub struct WasmTest; impl JobArchetype for WasmTest { @@ -132,7 +148,7 @@ impl JobArchetype for BuildWasm { plain_job_customized( &os, "Build GUI (WASM)", - " --upload-artifacts ${{ runner.os == 'Linux' }} wasm build", + "wasm build --wasm-upload-artifact ${{ runner.os == 'Linux' }}", |step| vec![step.with_secret_exposed(crate::env::ENSO_AG_GRID_LICENSE_KEY)], ) } @@ -222,18 +238,31 @@ pub fn expose_os_specific_signing_secret(os: OS, step: Step) -> Step { } #[derive(Clone, Copy, Debug)] -pub struct PackageIde; -impl JobArchetype for PackageIde { +pub struct PackageOldIde; +impl JobArchetype for PackageOldIde { fn job(&self, os: OS) -> Job { plain_job_customized( &os, - "Package IDE", + "Package Old IDE", "ide build --wasm-source current-ci-run --backend-source current-ci-run", |step| vec![expose_os_specific_signing_secret(os, step)], ) } } +#[derive(Clone, Copy, Debug)] +pub struct PackageNewIde; +impl JobArchetype for PackageNewIde { + fn job(&self, os: OS) -> Job { + plain_job_customized( + &os, + "Package New IDE", + "ide2 build --backend-source current-ci-run --gui2-upload-artifact false", + |step| vec![expose_os_specific_signing_secret(os, step)], + ) + } +} + #[derive(Clone, Copy, Debug)] pub struct CiCheckBackend; impl JobArchetype for CiCheckBackend { diff --git a/build/build/src/ide/web.rs b/build/build/src/ide/web.rs index 6c12317475..1c0bed7658 100644 --- a/build/build/src/ide/web.rs +++ b/build/build/src/ide/web.rs @@ -4,6 +4,7 @@ use crate::ide::web::env::CSC_KEY_PASSWORD; use crate::paths::generated; use crate::project::gui::BuildInfo; use crate::project::wasm; +use crate::project::IsArtifact; use crate::project::ProcessWrapper; use anyhow::Context; @@ -165,9 +166,7 @@ impl> ContentEnvironment { build_info: &BuildInfo, output_path: Output, ) -> Result { - // wasm build already does this, running `npm install` twice concurrently leads to broken - // builds. - // self.npm()?.install().run_ok().await?; + crate::web::install(&ide.repo_root).await?; let asset_dir = TempDir::new()?; let assets_download = download_js_assets(&asset_dir); let fonts_download = fonts::install_html_fonts(&ide.cache, &ide.octocrab, &asset_dir); @@ -240,11 +239,11 @@ impl FallibleManipulator for ProjectManagerInfo { #[derive(Clone, Derivative)] #[derivative(Debug)] pub struct IdeDesktop { - pub build_sbt: generated::RepoRootBuildSbt, - pub package_dir: generated::RepoRoot, + pub build_sbt: generated::RepoRootBuildSbt, + pub repo_root: generated::RepoRoot, #[derivative(Debug = "ignore")] - pub octocrab: Octocrab, - pub cache: ide_ci::cache::Cache, + pub octocrab: Octocrab, + pub cache: ide_ci::cache::Cache, } impl IdeDesktop { @@ -255,7 +254,7 @@ impl IdeDesktop { ) -> Self { Self { build_sbt: repo_root.build_sbt.clone(), - package_dir: repo_root.clone(), + repo_root: repo_root.clone(), octocrab, cache, } @@ -265,13 +264,13 @@ impl IdeDesktop { let mut command = Npm.cmd()?; command.arg("--color").arg("always"); command.arg("--yes"); - command.current_dir(&self.package_dir); + command.current_dir(&self.repo_root); command.stdin(Stdio::null()); // nothing in that process subtree should require input Ok(command) } pub fn write_build_info(&self, info: &BuildInfo) -> Result { - let path = self.package_dir.join(&*BUILD_INFO); + let path = self.repo_root.join(&*BUILD_INFO); path.write_as_json(info) } @@ -340,7 +339,7 @@ impl IdeDesktop { err))] pub async fn dist( &self, - gui: &crate::project::gui::Artifact, + gui: &impl IsArtifact, project_manager: &crate::project::backend::Artifact, output_path: impl AsRef, target_os: OS, @@ -354,11 +353,12 @@ impl IdeDesktop { graalvm.install_if_missing(&self.cache).await?; } - self.npm()?.install().run_ok().await?; + + crate::web::install(&self.repo_root).await?; let pm_bundle = ProjectManagerInfo::new(project_manager)?; let client_build = self .npm()? - .set_env(env::ENSO_BUILD_GUI, gui.as_path())? + .set_env(env::ENSO_BUILD_GUI, gui.as_ref())? .set_env(env::ENSO_BUILD_IDE, output_path.as_ref())? .try_applying(&pm_bundle)? .workspace(Workspaces::Enso) @@ -389,7 +389,7 @@ impl IdeDesktop { self.npm()? .try_applying(&icons)? // .env("DEBUG", "electron-builder") - .set_env(env::ENSO_BUILD_GUI, gui.as_path())? + .set_env(env::ENSO_BUILD_GUI, gui.as_ref())? .set_env(env::ENSO_BUILD_IDE, output_path.as_ref())? .set_env(env::ENSO_BUILD_PROJECT_MANAGER, project_manager.as_ref())? .set_env_opt(env::PYTHON_PATH, python_path.as_ref())? @@ -416,7 +416,7 @@ impl IdeDesktop { get_project_manager: BoxFuture<'static, Result>, ide_options: Vec, ) -> Result { - let npm_install_job = self.npm()?.install().run_ok(); + let npm_install_job = crate::web::install(&self.repo_root); // TODO: This could be possibly optimized by awaiting WASM a bit later, and passing its // future to the ContentEnvironment. However, the code would get a little tricky. // Should be reconsidered in the future, based on actual timings. diff --git a/build/build/src/lib.rs b/build/build/src/lib.rs index 7ed991a1e8..8889a0b4e2 100644 --- a/build/build/src/lib.rs +++ b/build/build/src/lib.rs @@ -16,6 +16,7 @@ #![feature(once_cell)] #![feature(duration_constants)] #![feature(slice_take)] +#![feature(future_join)] // === Standard Linter Configuration === #![deny(non_ascii_idents)] #![warn(unsafe_code)] @@ -66,6 +67,7 @@ pub mod repo; pub mod rust; pub mod source; pub mod version; +pub mod web; /// Get version of Enso from the `build.sbt` file contents. pub fn get_enso_version(build_sbt_contents: &str) -> Result { diff --git a/build/build/src/project.rs b/build/build/src/project.rs index 608fe40eb0..2f44348838 100644 --- a/build/build/src/project.rs +++ b/build/build/src/project.rs @@ -27,7 +27,9 @@ use octocrab::models::repos::Asset; pub mod backend; pub mod engine; pub mod gui; +pub mod gui2; pub mod ide; +pub mod ide2; pub mod project_manager; pub mod runtime; pub mod wasm; @@ -48,7 +50,7 @@ pub fn path_to_extract() -> Option { /// A built target, contained under a single directory. /// /// The `AsRef` trait must return that directory path. -pub trait IsArtifact: Clone + AsRef + Sized + Send + Sync + 'static {} +pub trait IsArtifact: Clone + AsRef + Debug + Sized + Send + Sync + 'static {} /// Plain artifact is just a folder with... things. #[derive(Clone, Derivative)] @@ -89,9 +91,6 @@ pub struct Context { /// Stores things like downloaded release assets to save time. pub cache: Cache, - /// Whether built artifacts should be uploaded as part of CI run. Works only in CI environment. - pub upload_artifacts: bool, - /// Directory being an `enso` repository's working copy. /// /// The directory is not required to be a git repository. It is allowed to use source tarballs @@ -126,6 +125,7 @@ pub trait IsTarget: Clone + Debug + Sized + Send + Sync + 'static { /// Create a full artifact description from an on-disk representation. fn adapt_artifact(self, path: impl AsRef) -> BoxFuture<'static, Result>; + /// Produce the target artifacts, according to the job description. fn get( &self, context: Context, @@ -133,10 +133,10 @@ pub trait IsTarget: Clone + Debug + Sized + Send + Sync + 'static { ) -> BoxFuture<'static, Result> { let GetTargetJob { destination, inner } = job; match inner { - Source::BuildLocally(inputs) => - self.build(context, WithDestination { inner: inputs, destination }), + Source::BuildLocally(local_build) => + self.build(context, WithDestination::new(local_build, destination)), Source::External(external) => - self.get_external(context, WithDestination { inner: external, destination }), + self.get_external(context, WithDestination::new(external, destination)), } } @@ -178,8 +178,8 @@ pub trait IsTarget: Clone + Debug + Sized + Send + Sync + 'static { job: BuildTargetJob, ) -> BoxFuture<'static, Result> { let span = debug_span!("Building.", ?self, ?context, ?job).entered(); - let upload_artifacts = context.upload_artifacts; - let artifact_fut = self.build_internal(context, job); + let upload_artifacts = job.should_upload_artifact; + let artifact_fut = self.build_internal(context, job.map(|job| job.input)); let this = self.clone(); async move { let artifact = artifact_fut.await.context(format!("Failed to build {this:?}."))?; @@ -208,7 +208,7 @@ pub trait IsTarget: Clone + Debug + Sized + Send + Sync + 'static { fn build_internal( &self, context: Context, - job: BuildTargetJob, + job: WithDestination, ) -> BoxFuture<'static, Result>; /// Upload artifact to the current GitHub Actions run. @@ -226,7 +226,7 @@ pub trait IsTarget: Clone + Debug + Sized + Send + Sync + 'static { ci_run: CiRunSource, output_path: impl AsRef + Send + Sync + 'static, ) -> BoxFuture<'static, Result> { - let Context { octocrab, cache, upload_artifacts: _, repo_root: _ } = context; + let Context { octocrab, cache, repo_root: _ } = context; let CiRunSource { run_id, artifact_name, repository } = ci_run; let repository = repository.handle(&octocrab); let span = info_span!("Downloading CI Artifact.", %artifact_name, %repository, target = output_path.as_ref().as_str()); @@ -285,7 +285,7 @@ pub trait IsTarget: Clone + Debug + Sized + Send + Sync + 'static { source: ReleaseSource, destination: PathBuf, ) -> BoxFuture<'static, Result> { - let Context { octocrab, cache, upload_artifacts: _, repo_root: _ } = context; + let Context { octocrab, cache, repo_root: _ } = context; let span = info_span!("Downloading built target from a release asset.", asset_id = source.asset_id.0, repo = %source.repository); @@ -403,19 +403,17 @@ pub fn perhaps_watch( job: GetTargetJob, watch_input: T::WatchInput, ) -> BoxFuture<'static, Result>> { - match job.inner { + let WithDestination { inner, destination } = job; + match inner { Source::BuildLocally(local) => target .watch(context, WatchTargetJob { watch_input, - build: WithDestination { inner: local, destination: job.destination }, + build: WithDestination::new(local, destination), }) .map_ok(PerhapsWatched::Watched) .boxed(), Source::External(external) => target - .get_external(context, WithDestination { - inner: external, - destination: job.destination, - }) + .get_external(context, WithDestination { inner: external, destination }) .map_ok(PerhapsWatched::Static) .boxed(), } diff --git a/build/build/src/project/backend.rs b/build/build/src/project/backend.rs index 633dae0c1e..540212a872 100644 --- a/build/build/src/project/backend.rs +++ b/build/build/src/project/backend.rs @@ -6,7 +6,6 @@ use crate::paths::TargetTriple; use crate::project::Context; use crate::project::IsArtifact; use crate::project::IsTarget; -use crate::source::BuildTargetJob; use crate::source::WithDestination; use crate::version::Versions; @@ -147,7 +146,7 @@ impl IsTarget for Backend { fn build_internal( &self, context: Context, - job: BuildTargetJob, + job: WithDestination, ) -> BoxFuture<'static, Result> { let WithDestination { inner, destination } = job; let target_os = self.target_os; diff --git a/build/build/src/project/gui.rs b/build/build/src/project/gui.rs index e235ef1658..f56d390ab0 100644 --- a/build/build/src/project/gui.rs +++ b/build/build/src/project/gui.rs @@ -9,7 +9,7 @@ use crate::project::IsWatchable; use crate::project::IsWatcher; use crate::project::PerhapsWatched; use crate::project::Wasm; -use crate::source::BuildTargetJob; +use crate::source::BuildSource; use crate::source::GetTargetJob; use crate::source::WatchTargetJob; use crate::source::WithDestination; @@ -120,7 +120,7 @@ impl IsTarget for Gui { fn build_internal( &self, context: Context, - job: BuildTargetJob, + job: WithDestination, ) -> BoxFuture<'static, Result> { let WithDestination { inner, destination } = job; async move { @@ -134,7 +134,7 @@ impl IsTarget for Gui { } let ide = ide_desktop_from_context(&context); - ide.npm()?.install().run_ok().await?; + crate::web::install(&ide.repo_root).await?; let wasm = Wasm.get(context, inner.wasm); ide.build_content(wasm, &inner.build_info.await?, &destination).await?; @@ -169,8 +169,12 @@ impl IsWatchable for Gui { context: Context, job: WatchTargetJob, ) -> BoxFuture<'static, Result> { - let WatchTargetJob { watch_input, build: WithDestination { inner, destination } } = job; - let BuildInput { build_info, wasm } = inner; + let WatchTargetJob { + watch_input, + build: + WithDestination { inner: BuildSource { input, should_upload_artifact: _ }, destination }, + } = job; + let BuildInput { build_info, wasm } = input; let perhaps_watched_wasm = perhaps_watch(Wasm, context.clone(), wasm, watch_input.wasm); let ide = ide_desktop_from_context(&context); async move { @@ -194,8 +198,12 @@ impl Gui { context: Context, job: WatchTargetJob, ) -> GuiBuildWithWatchedWasm { - let WatchTargetJob { watch_input, build: WithDestination { inner, destination } } = job; - let BuildInput { build_info, wasm } = inner; + let WatchTargetJob { + watch_input, + build: + WithDestination { inner: BuildSource { input, should_upload_artifact: _ }, destination }, + } = job; + let BuildInput { build_info, wasm } = input; let WatchInput { wasm: wasm_watch_input } = watch_input; let perhaps_watched_wasm = perhaps_watch(Wasm, context, wasm, wasm_watch_input); GuiBuildWithWatchedWasm { perhaps_watched_wasm, build_info, destination } diff --git a/build/build/src/project/gui2.rs b/build/build/src/project/gui2.rs new file mode 100644 index 0000000000..6e2cf30ebe --- /dev/null +++ b/build/build/src/project/gui2.rs @@ -0,0 +1,139 @@ +//! Build logic for the "new GUI" (gui2) project. +//! +//! The new GUI is Vue.js-based and located under `app/gui2`. + +use crate::prelude::*; + +use crate::paths::generated::RepoRootAppGui2Dist; +use crate::paths::generated::RepoRootDistGui2Assets; +use crate::project::Context; +use crate::project::IsArtifact; +use crate::project::IsTarget; +use crate::source::WithDestination; + +use ide_ci::ok_ready_boxed; +use ide_ci::programs::node::NpmCommand; +use ide_ci::programs::Npm; + + + +// =============== +// === Scripts === +// =============== + +/// The scripts defined in `package.json`. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, strum::AsRefStr)] +#[strum(serialize_all = "kebab-case")] +pub enum Scripts { + Dev, + Build, + Preview, + #[strum(serialize = "test:unit")] + TestUnit, + #[strum(serialize = "test:e2e")] + TestE2e, + BuildOnly, + TypeCheck, + Lint, + Format, + BuildRustFfi, +} + +pub fn script(repo_root: impl AsRef, script: Scripts) -> Result { + let mut ret = Npm.cmd()?; + ret.current_dir(repo_root).workspace(crate::web::Workspace::EnsoGui2).run(script.as_ref()); + Ok(ret) +} + + + +// ================ +// === Commands === +// ================ + +/// Run steps that should be done along with the "lint" +pub fn lint(repo_root: impl AsRef) -> BoxFuture<'static, Result> { + let repo_root = repo_root.as_ref().to_owned(); + async move { + crate::web::install(&repo_root).await?; + script(&repo_root, Scripts::Lint)?.run_ok().await + } + .boxed() +} + +/// Run unit tests. +pub fn unit_tests(repo_root: impl AsRef) -> BoxFuture<'static, Result> { + let repo_root = repo_root.as_ref().to_owned(); + async move { + crate::web::install(&repo_root).await?; + script(&repo_root, Scripts::TestUnit)?.arg("run").run_ok().await + } + .boxed() +} + + + +// ================ +// === Artifact === +// ================ + +/// The [artifact](IsArtifact) for the new GUI. +#[derive(Clone, Debug, PartialEq, Eq, Hash, Deref)] +pub struct Artifact(pub RepoRootAppGui2Dist); + +impl AsRef for Artifact { + fn as_ref(&self) -> &Path { + self.0.as_path() + } +} + +impl IsArtifact for Artifact {} + +impl Artifact { + pub fn new(path: impl AsRef) -> Self { + Artifact(RepoRootAppGui2Dist::new_root(path.as_ref())) + } +} + + + +// ============== +// === Target === +// ============== + +/// The [target](IsTarget) for the new GUI. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct Gui2; + +impl IsTarget for Gui2 { + type BuildInput = (); + type Artifact = Artifact; + + fn artifact_name(&self) -> String { + "gui2".to_owned() + } + + fn adapt_artifact(self, path: impl AsRef) -> BoxFuture<'static, Result> { + ok_ready_boxed(Artifact::new(path)) + } + + fn build_internal( + &self, + context: Context, + job: WithDestination, + ) -> BoxFuture<'static, Result> { + let WithDestination { inner: _, destination } = job; + async move { + let repo_root = &context.repo_root; + crate::web::install(repo_root).await?; + script(repo_root, Scripts::Build)?.run_ok().await?; + ide_ci::fs::mirror_directory( + &repo_root.app.gui_2.dist, + &destination.join(RepoRootDistGui2Assets::segment_name()), + ) + .await?; + Ok(Artifact::new(destination)) + } + .boxed() + } +} diff --git a/build/build/src/project/ide.rs b/build/build/src/project/ide.rs index 91314fd603..6060dde34e 100644 --- a/build/build/src/project/ide.rs +++ b/build/build/src/project/ide.rs @@ -4,6 +4,7 @@ use crate::project::gui::ide_desktop_from_context; use crate::project::gui::GuiBuildWithWatchedWasm; use crate::project::Context; use crate::project::Gui; +use crate::project::IsArtifact; use crate::source::WatchTargetJob; use ide_ci::actions::artifacts::upload_compressed_directory; @@ -25,7 +26,7 @@ pub struct Artifact { } impl Artifact { - fn new( + pub fn new( target_os: OS, target_arch: Arch, version: &Version, @@ -60,12 +61,14 @@ impl Artifact { } } - pub async fn upload_as_ci_artifact(&self) -> Result { + pub async fn upload_as_ci_artifact(&self, prefix: impl AsRef) -> Result { if is_in_env() { - upload_compressed_directory(&self.unpacked, format!("ide-unpacked-{TARGET_OS}")) + let prefix = prefix.as_ref(); + upload_compressed_directory(&self.unpacked, format!("{prefix}-unpacked-{TARGET_OS}")) .await?; - upload_single_file(&self.image, format!("ide-{TARGET_OS}")).await?; - upload_single_file(&self.image_checksum, format!("ide-{TARGET_OS}")).await?; + let packed_artifact_name = format!("{prefix}-{TARGET_OS}"); + upload_single_file(&self.image, &packed_artifact_name).await?; + upload_single_file(&self.image_checksum, &packed_artifact_name).await?; } else { info!("Not in the CI environment, will not upload the artifacts.") } @@ -91,14 +94,16 @@ impl Artifact { #[derive(derivative::Derivative)] #[derivative(Debug)] -pub struct BuildInput { +pub struct BuildInput { #[derivative(Debug(format_with = "std::fmt::Display::fmt"))] pub version: Version, #[derivative(Debug = "ignore")] pub project_manager: BoxFuture<'static, Result>, #[derivative(Debug = "ignore")] - pub gui: BoxFuture<'static, Result>, + pub gui: BoxFuture<'static, Result>, pub electron_target: Option, + /// The name base used to generate CI run artifact names. + pub artifact_name: String, } #[derive(Clone, Copy, Debug)] @@ -117,10 +122,10 @@ impl Ide { pub fn build( &self, context: &Context, - input: BuildInput, + input: BuildInput, output_path: impl AsRef + Send + Sync + 'static, ) -> BoxFuture<'static, Result> { - let BuildInput { version, project_manager, gui, electron_target } = input; + let BuildInput { version, project_manager, gui, electron_target, artifact_name: _ } = input; let ide_desktop = ide_desktop_from_context(context); let target_os = self.target_os; let target_arch = self.target_arch; @@ -155,30 +160,3 @@ impl Ide { .boxed() } } - - -// impl IsTarget for Ide { -// type BuildInput = BuildInput; -// type Output = Artifact; -// -// fn artifact_name(&self) -> &str { -// // Version is not part of the name intentionally. We want to refer to PM bundles as -// // artifacts without knowing their version. -// static NAME: LazyLock = LazyLock::new(|| format!("gui-{}", TARGET_OS)); -// &*NAME -// } -// -// fn build( -// &self, -// input: Self::BuildInput, -// output_path: impl AsRef + Send + Sync + 'static, -// ) -> BoxFuture<'static, Result> { -// let ide_desktop = crate::ide::web::IdeDesktop::new(&input.repo_root.app.ide_desktop); -// async move { -// let (gui, project_manager) = try_join(input.gui, input.project_manager).await?; -// ide_desktop.dist(&gui, &project_manager, &output_path).await?; -// Ok(Artifact::new(&input.version, output_path.as_ref())) -// } -// .boxed() -// } -// } diff --git a/build/build/src/project/ide2.rs b/build/build/src/project/ide2.rs new file mode 100644 index 0000000000..3eed69f3c9 --- /dev/null +++ b/build/build/src/project/ide2.rs @@ -0,0 +1,9 @@ +#[allow(unused_imports)] +use crate::prelude::*; + + +// ============== +// === Export === +// ============== + +pub use crate::project::ide::Artifact; diff --git a/build/build/src/project/runtime.rs b/build/build/src/project/runtime.rs index 1148b93615..bc76ada698 100644 --- a/build/build/src/project/runtime.rs +++ b/build/build/src/project/runtime.rs @@ -9,7 +9,6 @@ use crate::paths::TargetTriple; use crate::project::Context; use crate::project::IsArtifact; use crate::project::IsTarget; -use crate::source::BuildTargetJob; use crate::source::WithDestination; use crate::version::Versions; @@ -42,7 +41,7 @@ impl IsTarget for Runtime { fn build_internal( &self, context: Context, - job: BuildTargetJob, + job: WithDestination, ) -> BoxFuture<'static, Result> { let config = BuildConfigurationFlags { build_engine_package: true, diff --git a/build/build/src/project/wasm.rs b/build/build/src/project/wasm.rs index 66033ac083..db2a57e48f 100644 --- a/build/build/src/project/wasm.rs +++ b/build/build/src/project/wasm.rs @@ -7,7 +7,7 @@ use crate::project::Context; use crate::project::IsArtifact; use crate::project::IsTarget; use crate::project::IsWatchable; -use crate::source::BuildTargetJob; +use crate::source::BuildSource; use crate::source::WatchTargetJob; use crate::source::WithDestination; @@ -205,9 +205,9 @@ impl IsTarget for Wasm { fn build_internal( &self, context: Context, - job: BuildTargetJob, + job: WithDestination, ) -> BoxFuture<'static, Result> { - let Context { octocrab: _, cache, upload_artifacts: _, repo_root } = context; + let Context { octocrab: _, cache, repo_root } = context; let WithDestination { inner, destination } = job; let span = info_span!("Building WASM.", repo = %repo_root.display(), @@ -249,6 +249,7 @@ impl IsTarget for Wasm { info!("Building wasm."); let temp_dir = tempdir()?; let temp_dist = RepoRootDistWasm::new_root(temp_dir.path()); + crate::web::install(&repo_root).await?; ensogl_pack::build( ensogl_pack::WasmPackOutputs { out_dir: temp_dist.path.clone(), @@ -327,11 +328,21 @@ impl IsWatchable for Wasm { .instrument(debug_span!("Initial single build of WASM before setting up cargo-watch.")); async move { - let first_build_output = first_build_job.await?; + // Make sure that `npm install` was run, so we can spawned process to skip it. + // This prevents issues with multiple `npm install` invocations running in parallel. + let npm_install = crate::web::install(&context.repo_root); + let (first_build_output, npm_install) = + futures::future::join(first_build_job, npm_install).await; + npm_install?; + let first_build_output = first_build_output?; let WatchTargetJob { watch_input: WatchInput { cargo_watch_options: cargo_watch_flags }, - build: WithDestination { inner, destination }, + build: + WithDestination { + inner: BuildSource { input, should_upload_artifact: _ }, + destination, + }, } = job; let BuildInput { crate_path, @@ -344,7 +355,7 @@ impl IsWatchable for Wasm { uncollapsed_log_level, wasm_size_limit, system_shader_tools: _, - } = inner; + } = input; let current_exe = std::env::current_exe()?; @@ -379,8 +390,8 @@ impl IsWatchable for Wasm { .arg(current_exe) .arg("--skip-version-check") // We already checked in the parent process. .args(["--cache-path", context.cache.path().as_str()]) - .args(["--upload-artifacts", context.upload_artifacts.to_string().as_str()]) - .args(["--repo-path", context.repo_root.as_str()]); + .args(["--repo-path", context.repo_root.as_str()]) + .args(["--skip-npm-install", "false"]); // === Build Script command and its options === watch_cmd diff --git a/build/build/src/source.rs b/build/build/src/source.rs index 6c2779b68d..d87d950f2a 100644 --- a/build/build/src/source.rs +++ b/build/build/src/source.rs @@ -10,6 +10,7 @@ use octocrab::models::RunId; +/// Denotes an external source from which a target artifact can be obtained. #[derive(Clone, Derivative)] #[derivative(Debug)] pub enum ExternalSource { @@ -29,11 +30,23 @@ impl ExternalSource { } } +/// Describes how to build a target and whether to upload the resulting artifact. +#[derive(Clone, Debug)] +pub struct BuildSource { + /// Data needed to build the target. + pub input: Target::BuildInput, + /// Whether to upload the resulting artifact as CI artifact. + pub should_upload_artifact: bool, +} + +/// Describes how to get a target. #[derive(Derivative)] #[derivative(Debug)] pub enum Source { + /// Build the target locally from the sources. #[derivative(Debug = "transparent")] - BuildLocally(Target::BuildInput), + BuildLocally(BuildSource), + /// Download the target from an external source. #[derivative(Debug = "transparent")] External(ExternalSource), } @@ -83,6 +96,10 @@ impl WithDestination> { } impl WithDestination { + pub fn new(inner: T, destination: impl Into) -> Self { + Self { inner, destination: destination.into() } + } + pub fn map(self, f: impl FnOnce(T) -> U) -> WithDestination { WithDestination { inner: f(self.inner), destination: self.destination } } @@ -90,7 +107,7 @@ impl WithDestination { pub type GetTargetJob = WithDestination>; pub type FetchTargetJob = WithDestination; -pub type BuildTargetJob = WithDestination<::BuildInput>; +pub type BuildTargetJob = WithDestination>; #[derive(Debug)] pub struct WatchTargetJob { diff --git a/build/build/src/web.rs b/build/build/src/web.rs new file mode 100644 index 0000000000..28d1e33f92 --- /dev/null +++ b/build/build/src/web.rs @@ -0,0 +1,87 @@ +//! Module for managing the web parts of our codebase. +//! This refers to JS/TS components, such as the Vue.js-based GUI, as well as the Electron client +//! application. + +use crate::prelude::*; + +use ide_ci::programs::Npm; + + + +/// Result of root-level `npm install` call. Should not be accessed directly. +static ONCE_INSTALL: tokio::sync::OnceCell = tokio::sync::OnceCell::const_new(); + +/// This method invokes `npm install` in the root repository. +/// +/// It ensures that at most one such invocation is running at any given time. +/// If an invocation is already running, it waits for it to finish and then +/// shares its result. +/// +/// This method relies on the internal global state. It must not be called multiple times +/// with different `repo_root` arguments. +/// +/// It is useful, because otherwise the build script might end up invoking `npm install` multiple +/// times, sometimes even in parallel. Unfortunately, in such cases, `npm` might fail with errors. +pub fn install(repo_root: impl AsRef) -> BoxFuture<'static, Result> { + let root_path = repo_root.as_ref().to_owned(); + async move { + let ret = ONCE_INSTALL + .get_or_init(async || Npm.cmd()?.with_current_dir(&root_path).install().run_ok().await) + .await; + ret.as_ref().copied().map_err(|e| anyhow!("Failed to install NPM dependencies: {e}")) + } + .boxed() +} + +/// Mark the root repository's NPM as installed. +/// +/// If invoked before `install`, any subsequent `install` call will return `Ok`. +/// If it was already installed and failed, the failure will still be returned. +pub fn assume_installed() { + let _ = ONCE_INSTALL.set(Ok(())).inspect_err(|e| { + warn!("Failed to mark NPM as installed, due to an error during a previous run: {e}"); + }); +} + +/// The scripts that can be invoked in the root repository's NPM. +/// +/// The list should be kept in sync with the `scripts` section of the root repository's +/// `package.json`. +#[allow(missing_docs)] +#[derive(Clone, Copy, Debug, PartialEq, Eq, strum::AsRefStr)] +#[strum(serialize_all = "kebab-case")] +pub enum Script { + Format, + Prettier, + Lint, + Test, + Typecheck, + CiCheck, +} + +/// Invoke the given script in the context of the root repository's NPM. +pub fn run_script(repo_root: impl AsRef, script: Script) -> BoxFuture<'static, Result> { + let root_path = repo_root.as_ref().to_owned(); + async move { Npm.cmd()?.with_current_dir(&root_path).run(script.as_ref()).run_ok().await } + .boxed() +} + +/// The list of NPM workspaces that are part of the root repository. +#[derive(Debug, Clone, Copy, PartialEq, Eq, strum::AsRefStr)] +#[strum(serialize_all = "kebab-case")] +pub enum Workspace { + EnsoIdeDesktop, + Enso, + EnsoContent, + EnsoDashboard, + EnsoIcons, + EnsoAuthentication, + EnsoGui2, + EnsoglRunner, +} + +impl AsRef for Workspace { + fn as_ref(&self) -> &OsStr { + AsRef::::as_ref(self).as_ref() + } +} diff --git a/build/ci-gen/src/main.rs b/build/ci-gen/src/main.rs index 1222166c15..16a5096a17 100644 --- a/build/ci-gen/src/main.rs +++ b/build/ci-gen/src/main.rs @@ -19,6 +19,8 @@ pub mod prelude { /// Generate the comment that is at the top of each generated workflow file. fn preamble(source: &str) -> String { + // To make output consistent across platforms. + let source = source.replace('\\', "/"); format!( "# This file is auto-generated. Do not edit it manually!\n\ # Edit the {source} module instead and run `cargo run --package {}`.", diff --git a/build/ci_utils/src/program/command.rs b/build/ci_utils/src/program/command.rs index 2dcd87cfb0..35386833ac 100644 --- a/build/ci_utils/src/program/command.rs +++ b/build/ci_utils/src/program/command.rs @@ -246,12 +246,21 @@ pub trait IsCommandWrapper { + /// Value-based variant of [`Self::current_dir`], for convenience. fn with_current_dir(self, dir: impl AsRef) -> Self where Self: Sized { let mut this = self; this.current_dir(dir); this } + + /// Value-based variant of [`Self::with_stdin`], for convenience. + fn with_stdin(self, stdin: Stdio) -> Self + where Self: Sized { + let mut this = self; + this.stdin(stdin); + this + } } impl> IsCommandWrapper for T { diff --git a/build/cli/src/arg.rs b/build/cli/src/arg.rs index 8f9079678c..793d082ee6 100644 --- a/build/cli/src/arg.rs +++ b/build/cli/src/arg.rs @@ -20,7 +20,9 @@ pub mod backend; pub mod engine; pub mod git_clean; pub mod gui; +pub mod gui2; pub mod ide; +pub mod ide2; pub mod java_gen; pub mod project_manager; pub mod release; @@ -75,10 +77,10 @@ pub trait IsTargetSource { const SOURCE_NAME: &'static str; const PATH_NAME: &'static str; const OUTPUT_PATH_NAME: &'static str; - // const UPLOAD_ASSET_NAME: &'static str; const RUN_ID_NAME: &'static str; const RELEASE_DESIGNATOR_NAME: &'static str; const ARTIFACT_NAME_NAME: &'static str; + const UPLOAD_ARTIFACT_NAME: &'static str; const DEFAULT_OUTPUT_PATH: &'static str; type BuildInput: Clone + Debug + PartialEq + Args + Send + Sync; @@ -99,6 +101,7 @@ macro_rules! source_args_hlp { const RUN_ID_NAME: &'static str = concat!($prefix, "-", "run-id"); const RELEASE_DESIGNATOR_NAME: &'static str = concat!($prefix, "-", "release"); const ARTIFACT_NAME_NAME: &'static str = concat!($prefix, "-", "artifact-name"); + const UPLOAD_ARTIFACT_NAME: &'static str = concat!($prefix, "-", "upload-artifact"); const DEFAULT_OUTPUT_PATH: &'static str = concat!("dist/", $prefix); type BuildInput = $inputs; @@ -111,8 +114,10 @@ macro_rules! source_args_hlp { pub enum Target { /// Build/Test the Rust part of the GUI. Wasm(wasm::Target), - /// Build/Run GUI that consists of WASM and JS parts. This is what we deploy to cloud. + /// Build/Run the legacy Rust-based GUI that consists of WASM and JS parts. Gui(gui::Target), + /// Build/Run the new, Vue-based GUI. + Gui2(gui2::Target), /// Enso Engine Runtime. Runtime(runtime::Target), // /// Project Manager package (just the binary, no Engine) @@ -121,8 +126,10 @@ pub enum Target { // Engine(engine::Target), /// Build/Get Project Manager bundle (includes Enso Engine with GraalVM Runtime). Backend(backend::Target), - /// Build/Run/Test IDE bundle (includes GUI and Project Manager). + /// Build/Run/Test IDE bundle (includes Rust-based GUI and Project Manager). Ide(ide::Target), + /// Build/Run/Test IDE bundle (includes Vue-based GUI and Project Manager). + Ide2(ide2::Target), /// Clean the repository. Keeps the IntelliJ's .idea directory intact. WARNING: This removes /// files that are not under version control in the repository subtree. GitClean(git_clean::Options), @@ -167,10 +174,9 @@ pub struct Cli { #[clap(long, global = true, enso_env())] pub skip_version_check: bool, - /// Whether built artifacts should be uploaded as part of CI run. Ignored in non-CI - /// environment. - #[clap(long, global = true, hide = !ide_ci::actions::workflow::is_in_env(), parse(try_from_str), default_value_t = true, enso_env())] - pub upload_artifacts: bool, + /// Assume that `npm install` was already run in the repository root and skip it. + #[clap(long, global = true, enso_env())] + pub skip_npm_install: bool, #[clap(subcommand)] pub target: Target, @@ -214,13 +220,10 @@ pub struct Source { /// Used when `SourceKind::Build` is used. #[clap(flatten)] - pub build_args: Target::BuildInput, + pub build_args: BuildDescription, #[clap(flatten)] pub output_path: OutputPath, - // - // #[clap(name = Target::UPLOAD_ASSET_NAME, long)] - // pub upload_asset: bool, } /// Discriminator denoting how some target artifact should be obtained. @@ -258,11 +261,20 @@ impl AsRef for OutputPath { } } +#[derive(Args, Clone, PartialEq, Derivative)] +#[derivative(Debug)] +pub struct BuildDescription { + #[clap(flatten)] + pub input: Target::BuildInput, + #[clap(name = Target::UPLOAD_ARTIFACT_NAME, long, enso_env(), default_value_t = ide_ci::actions::workflow::is_in_env())] + pub upload_artifact: bool, +} + #[derive(Args, Clone, PartialEq, Derivative)] #[derivative(Debug)] pub struct BuildJob { #[clap(flatten)] - pub input: Target::BuildInput, + pub input: BuildDescription, #[clap(flatten)] pub output_path: OutputPath, } diff --git a/build/cli/src/arg/gui2.rs b/build/cli/src/arg/gui2.rs new file mode 100644 index 0000000000..5462f078fe --- /dev/null +++ b/build/cli/src/arg/gui2.rs @@ -0,0 +1,35 @@ +use enso_build::prelude::*; + +use crate::arg::BuildJob; +use crate::arg::Source; +use crate::source_args_hlp; + +use clap::Args; +use clap::Subcommand; +use enso_build::project::gui2::Gui2; + + + +source_args_hlp!(Gui2, "gui2", BuildInput); + +#[derive(Args, Clone, Copy, Debug, PartialEq)] +pub struct BuildInput {} + +#[derive(Subcommand, Clone, Debug, PartialEq)] +pub enum Command { + /// Builds the GUI from the local sources. + Build(BuildJob), + /// Gets the GUI, either by compiling it from scratch or downloading from an external source. + Get(Source), + /// Runs the GUI's unit tests. + Test, + /// Run linter on the GUI's sources. + Lint, +} + +#[derive(Args, Clone, Debug)] +pub struct Target { + /// Command for GUI package. + #[clap(subcommand)] + pub command: Command, +} diff --git a/build/cli/src/arg/ide.rs b/build/cli/src/arg/ide.rs index 38b0de8d6a..219ec9f79c 100644 --- a/build/cli/src/arg/ide.rs +++ b/build/cli/src/arg/ide.rs @@ -1,5 +1,6 @@ use crate::prelude::*; +use crate::arg::IsTargetSource; use crate::arg::OutputPath; use crate::arg::Source; use crate::arg::WatchJob; @@ -14,10 +15,10 @@ use octocrab::models::ReleaseId; -source_args_hlp!(Target, "ide", BuildInput); +source_args_hlp!(Target, "ide", BuildInput); #[derive(Args, Clone, Debug, PartialEq)] -pub struct BuildInput { +pub struct BuildInput { #[clap(flatten)] pub gui: Source, #[clap(flatten)] @@ -36,18 +37,18 @@ pub enum Command { /// application. Build { #[clap(flatten)] - params: BuildInput, + params: BuildInput, }, Upload { #[clap(flatten)] - params: BuildInput, + params: BuildInput, #[clap(long, env = *enso_build::env::ENSO_RELEASE_ID)] release_id: ReleaseId, }, /// Like `Build` but automatically starts the IDE. Start { #[clap(flatten)] - params: BuildInput, + params: BuildInput, /// Additional option to be passed to Enso IDE. Can be used multiple times to pass many /// arguments. #[clap(long, allow_hyphen_values = true, enso_env())] diff --git a/build/cli/src/arg/ide2.rs b/build/cli/src/arg/ide2.rs new file mode 100644 index 0000000000..c80d3623cf --- /dev/null +++ b/build/cli/src/arg/ide2.rs @@ -0,0 +1,29 @@ +use crate::prelude::*; + +use crate::source_args_hlp; + +use clap::Args; +use clap::Subcommand; +use enso_build::project::gui2::Gui2; + + + +source_args_hlp!(Target, "ide2", BuildInput); + +pub type BuildInput = crate::arg::ide::BuildInput; + +#[derive(Subcommand, Clone, Debug)] +pub enum Command { + /// Builds both Project Manager and GUI, puts them together into a single, client Electron + /// application. + Build { + #[clap(flatten)] + params: BuildInput, + }, +} + +#[derive(Args, Clone, Debug)] +pub struct Target { + #[clap(subcommand)] + pub command: Command, +} diff --git a/build/cli/src/lib.rs b/build/cli/src/lib.rs index 30be152f8e..f364229734 100644 --- a/build/cli/src/lib.rs +++ b/build/cli/src/lib.rs @@ -38,9 +38,11 @@ use crate::arg::BuildJob; use crate::arg::Cli; use crate::arg::IsTargetSource; use crate::arg::IsWatchableSource; +use crate::arg::OutputPath; use crate::arg::Target; use crate::arg::WatchJob; use anyhow::Context; +use arg::BuildDescription; use clap::Parser; use derivative::Derivative; use enso_build::context::BuildContext; @@ -53,15 +55,20 @@ use enso_build::project::backend; use enso_build::project::backend::Backend; use enso_build::project::gui; use enso_build::project::gui::Gui; +use enso_build::project::gui2; +use enso_build::project::gui2::Gui2; use enso_build::project::ide; use enso_build::project::ide::Ide; +use enso_build::project::ide2; use enso_build::project::runtime; use enso_build::project::runtime::Runtime; use enso_build::project::wasm; use enso_build::project::wasm::Wasm; +use enso_build::project::IsArtifact; use enso_build::project::IsTarget; use enso_build::project::IsWatchable; use enso_build::project::IsWatcher; +use enso_build::source::BuildSource; use enso_build::source::BuildTargetJob; use enso_build::source::CiRunSource; use enso_build::source::ExternalSource; @@ -85,7 +92,6 @@ use ide_ci::programs::git; use ide_ci::programs::git::clean; use ide_ci::programs::rustc; use ide_ci::programs::Cargo; -use ide_ci::programs::Npm; use std::time::Duration; use tempfile::tempdir; use tokio::process::Child; @@ -131,7 +137,6 @@ impl Processor { inner: project::Context { cache: Cache::new(&cli.cache_path).await?, octocrab, - upload_artifacts: cli.upload_artifacts, repo_root: enso_build::paths::new_repo_root(absolute_repo_path, &triple), }, triple, @@ -154,9 +159,13 @@ impl Processor { { let span = info_span!("Resolving.", ?target, ?source).entered(); let destination = source.output_path.output_path; + let should_upload_artifact = source.build_args.upload_artifact; let source = match source.source { - arg::SourceKind::Build => - T::resolve(self, source.build_args).map_ok(Source::BuildLocally).boxed(), + arg::SourceKind::Build => T::resolve(self, source.build_args.input) + .map_ok(move |input| { + Source::BuildLocally(BuildSource { input, should_upload_artifact }) + }) + .boxed(), arg::SourceKind::Local => ok_ready_boxed(Source::External(ExternalSource::LocalFile(source.path))), arg::SourceKind::CiRun => { @@ -243,10 +252,16 @@ impl Processor { &self, job: BuildJob, ) -> BoxFuture<'static, Result>> { - let BuildJob { input, output_path } = job; + let BuildJob { input: BuildDescription { input, upload_artifact }, output_path } = job; let input = self.resolve_inputs::(input); async move { - Ok(WithDestination { destination: output_path.output_path, inner: input.await? }) + Ok(WithDestination::new( + BuildSource { + input: input.await?, + should_upload_artifact: upload_artifact, + }, + output_path.output_path, + )) } .boxed() } @@ -320,17 +335,6 @@ impl Processor { } } - // pub fn handle_engine(&self, engine: arg::engine::Target) -> BoxFuture<'static, Result> { - // self.get(engine.source).void_ok().boxed() - // } - // - // pub fn handle_project_manager( - // &self, - // project_manager: arg::project_manager::Target, - // ) -> BoxFuture<'static, Result> { - // self.get(project_manager.source).void_ok().boxed() - // } - pub fn handle_gui(&self, gui: arg::gui::Target) -> BoxFuture<'static, Result> { match gui.command { arg::gui::Command::Build(job) => self.build(job), @@ -339,6 +343,15 @@ impl Processor { } } + pub fn handle_gui2(&self, gui: arg::gui2::Target) -> BoxFuture<'static, Result> { + match gui.command { + arg::gui2::Command::Build(job) => self.build(job), + arg::gui2::Command::Get(source) => self.get(source).void_ok().boxed(), + arg::gui2::Command::Test => gui2::unit_tests(&self.repo_root), + arg::gui2::Command::Lint => gui2::lint(&self.repo_root), + } + } + pub fn handle_runtime(&self, gui: arg::runtime::Target) -> BoxFuture<'static, Result> { // todo!() match gui.command { @@ -439,8 +452,7 @@ impl Processor { }; let context = self.prepare_backend_context(config); async move { - let mut context = context.await?; - context.upload_artifacts = true; + let context = context.await?; context.build().await } .void_ok() @@ -461,7 +473,7 @@ impl Processor { let paths = paths?; let inner = crate::project::Context { repo_root: paths.repo_root.clone(), - upload_artifacts: true, + // upload_artifacts: true, octocrab, cache: Cache::new_default().await?, }; @@ -472,9 +484,9 @@ impl Processor { pub fn handle_ide(&self, ide: arg::ide::Target) -> BoxFuture<'static, Result> { match ide.command { - arg::ide::Command::Build { params } => self.build_ide(params).void_ok().boxed(), + arg::ide::Command::Build { params } => self.build_old_ide(params).void_ok().boxed(), arg::ide::Command::Upload { params, release_id } => { - let build_job = self.build_ide(params); + let build_job = self.build_old_ide(params); let release = ide_ci::github::release::Handle::new( &self.octocrab, self.remote_repo.clone(), @@ -489,7 +501,7 @@ impl Processor { .boxed() } arg::ide::Command::Start { params, ide_option } => { - let build_job = self.build_ide(params); + let build_job = self.build_old_ide(params); async move { let ide = build_job.await?; ide.start_unpacked(ide_option).run_ok().await?; @@ -547,6 +559,12 @@ impl Processor { } } + pub fn handle_ide2(&self, ide: arg::ide2::Target) -> BoxFuture<'static, Result> { + match ide.command { + arg::ide2::Command::Build { params } => self.build_new_ide(params).void_ok().boxed(), + } + } + /// Spawns a Project Manager. pub fn spawn_project_manager( &self, @@ -566,10 +584,27 @@ impl Processor { } .boxed() } - pub fn build_ide( &self, - params: arg::ide::BuildInput, + input: ide::BuildInput, + output_path: OutputPath, + ) -> BoxFuture<'static, Result> { + let target = Ide { target_os: self.triple.os, target_arch: self.triple.arch }; + let artifact_name_prefix = input.artifact_name.clone(); + let build_job = target.build(&self.context, input, output_path); + async move { + let artifacts = build_job.await?; + if is_in_env() { + artifacts.upload_as_ci_artifact(artifact_name_prefix).await?; + } + Ok(artifacts) + } + .boxed() + } + + pub fn build_old_ide( + &self, + params: arg::ide::BuildInput, ) -> BoxFuture<'static, Result> { let arg::ide::BuildInput { gui, project_manager, output_path, electron_target } = params; let input = ide::BuildInput { @@ -577,17 +612,39 @@ impl Processor { project_manager: self.get(project_manager), version: self.triple.versions.version.clone(), electron_target, + artifact_name: "ide".into(), }; - let target = Ide { target_os: self.triple.os, target_arch: self.triple.arch }; - let build_job = target.build(&self.context, input, output_path); - async move { - let artifacts = build_job.await?; - if is_in_env() { - artifacts.upload_as_ci_artifact().await?; + self.build_ide(input, output_path) + } + + pub fn build_new_ide( + &self, + params: arg::ide2::BuildInput, + ) -> BoxFuture<'static, Result> { + let arg::ide::BuildInput { gui, project_manager, output_path, electron_target } = params; + + let build_info_get = self.js_build_info(); + let build_info_path = self.context.inner.repo_root.join(&*enso_build::ide::web::BUILD_INFO); + + let build_info = async move { + let build_info = build_info_get.await?; + build_info_path.write_as_json(&build_info) + }; + + let gui = self.get(gui); + + let input = ide::BuildInput { + gui: async move { + build_info.await?; + gui.await } - Ok(artifacts) - } - .boxed() + .boxed(), + project_manager: self.get(project_manager), + version: self.triple.versions.version.clone(), + electron_target, + artifact_name: "ide2".into(), + }; + self.build_ide(input, output_path) } pub fn target(&self) -> Result { @@ -655,6 +712,19 @@ impl Resolvable for Gui { } } +impl Resolvable for Gui2 { + fn prepare_target(_context: &Processor) -> Result { + Ok(Gui2) + } + + fn resolve( + _ctx: &Processor, + _from: ::BuildInput, + ) -> BoxFuture<'static, Result<::BuildInput>> { + ok_ready_boxed(()) + } +} + impl Resolvable for Runtime { fn prepare_target(_context: &Processor) -> Result { Ok(Runtime {}) @@ -696,42 +766,9 @@ impl Resolvable for Backend { Ok(backend::BuildInput { external_runtime, versions }) }) .boxed() - // ok_ready_boxed(backend::BuildInput { versions: ctx.triple.versions.clone() }) } } -// impl Resolvable for ProjectManager { -// fn prepare_target(_context: &Processor) -> Result { -// Ok(ProjectManager) -// } -// -// fn resolve( -// ctx: &Processor, -// _from: ::BuildInput, -// ) -> BoxFuture<'static, Result<::BuildInput>> { -// ok_ready_boxed(project_manager::BuildInput { -// repo_root: ctx.repo_root().path, -// versions: ctx.triple.versions.clone(), -// }) -// } -// } -// -// impl Resolvable for Engine { -// fn prepare_target(_context: &Processor) -> Result { -// Ok(Engine) -// } -// -// fn resolve( -// ctx: &Processor, -// _from: ::BuildInput, -// ) -> BoxFuture<'static, Result<::BuildInput>> { -// ok_ready_boxed(engine::BuildInput { -// repo_root: ctx.repo_root().path, -// versions: ctx.triple.versions.clone(), -// }) -// } -// } - pub trait WatchResolvable: Resolvable + IsWatchableSource + IsWatchable { fn resolve_watch( ctx: &Processor, @@ -777,6 +814,10 @@ pub async fn main_internal(config: Option) -> Result debug!("Parsed CLI arguments: {cli:#?}"); + if cli.skip_npm_install { + enso_build::web::assume_installed(); + } + if !cli.skip_version_check { // Let's be helpful! let error_message = "Program requirements were not fulfilled. Please do one of the \ @@ -796,13 +837,14 @@ pub async fn main_internal(config: Option) -> Result match cli.target { Target::Wasm(wasm) => ctx.handle_wasm(wasm).await?, Target::Gui(gui) => ctx.handle_gui(gui).await?, + Target::Gui2(gui2) => ctx.handle_gui2(gui2).await?, Target::Runtime(runtime) => ctx.handle_runtime(runtime).await?, // Target::ProjectManager(project_manager) => // ctx.handle_project_manager(project_manager).await?, // Target::Engine(engine) => ctx.handle_engine(engine).await?, Target::Backend(backend) => ctx.handle_backend(backend).await?, Target::Ide(ide) => ctx.handle_ide(ide).await?, - // TODO: consider if out-of-source ./dist should be removed + Target::Ide2(ide2) => ctx.handle_ide2(ide2).await?, Target::GitClean(options) => { let crate::arg::git_clean::Options { dry_run, cache, build_script } = options; let mut exclusions = vec![".idea"]; @@ -852,12 +894,13 @@ pub async fn main_internal(config: Option) -> Result .run_ok() .await?; - Npm.cmd()?.install().run_ok().await?; - Npm.cmd()?.run("ci-check").run_ok().await?; + enso_build::web::install(&ctx.repo_root).await?; + enso_build::web::run_script(&ctx.repo_root, enso_build::web::Script::CiCheck).await?; } Target::Fmt => { - Npm.cmd()?.install().run_ok().await?; - let prettier = Npm.cmd()?.run("format").run_ok(); + enso_build::web::install(&ctx.repo_root).await?; + let prettier = + enso_build::web::run_script(&ctx.repo_root, enso_build::web::Script::Format); let our_formatter = enso_formatter::process_path(&ctx.repo_root, enso_formatter::Action::Format); let (r1, r2) = join!(prettier, our_formatter).await; diff --git a/lib/rust/ensogl/pack/src/lib.rs b/lib/rust/ensogl/pack/src/lib.rs index 8b5e8c40b9..92170ddfb0 100644 --- a/lib/rust/ensogl/pack/src/lib.rs +++ b/lib/rust/ensogl/pack/src/lib.rs @@ -61,7 +61,7 @@ //! 1. If the `dist/index.js` file does not exist, or its modification date is older than //! `this_crate/js` sources: //! -//! 1. `npm run install` is run in the `this_crate/js` directory. +//! 1. `npm install` is assumed to have been already run in the `this_crate/js` directory. //! //! 2. The `this_crate/js/runner` is compiled to `target/ensogl-pack/dist/index.cjs`. This is the //! main file which is capable of loading WASM file, displaying a loading screen, running @@ -351,7 +351,6 @@ pub async fn compile_this_crate_ts_sources(paths: &Paths) -> Result<()> { println!("compile_this_crate_ts_sources"); if check_if_ts_needs_rebuild(paths)? { info!("EnsoGL Pack TypeScript sources changed, recompiling."); - ide_ci::programs::Npm.cmd()?.install().current_dir(&paths.workspace).run_ok().await?; let run_script = async move |script_name, script_args: &[&str]| { ide_ci::programs::Npm .cmd()? @@ -378,6 +377,7 @@ pub async fn compile_this_crate_ts_sources(paths: &Paths) -> Result<()> { } /// Run wasm-pack to build the wasm artifact. +#[context("Failed to run wasm-pack.")] pub async fn run_wasm_pack( paths: &Paths, provider: impl FnOnce(WasmPackOutputs) -> Result, diff --git a/package-lock.json b/package-lock.json index 3387b2ee3e..da08164981 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3021,6 +3021,51 @@ } } }, + "node_modules/@swc/core-darwin-arm64": { + "version": "1.3.85", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-darwin-x64": { + "version": "1.3.85", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-x64-gnu": { + "version": "1.3.85", + "cpu": [ + "x64" + ], + "dev": true, + "license": "Apache-2.0 AND MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, "node_modules/@swc/core-win32-x64-msvc": { "version": "1.3.85", "cpu": [