Better component handling in the project manager (#1399)

This commit is contained in:
Radosław Waśko 2021-01-15 16:26:51 +01:00 committed by GitHub
parent 197190ceeb
commit 10bccf6b56
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
28 changed files with 830 additions and 707 deletions

View File

@ -31,7 +31,7 @@ jobs:
with: with:
path: repo path: repo
- name: Enable Developer Command Prompt (Windows) - name: Enable Developer Command Prompt (Windows)
uses: ilammy/msvc-dev-cmd@v1.4.1 uses: ilammy/msvc-dev-cmd@v1.5.0
- name: Disable TCP/UDP Offloading (macOS) - name: Disable TCP/UDP Offloading (macOS)
if: runner.os == 'macOS' if: runner.os == 'macOS'
shell: bash shell: bash
@ -147,19 +147,19 @@ jobs:
shell: bash shell: bash
run: | run: |
sleep 1 sleep 1
sbt 'set launcherDistributionRoot := file("${{ env.LAUNCHER_DIST_DIR }}"); buildLauncherDistribution' sbt buildLauncherDistribution
- name: Prepare Engine Distribution - name: Prepare Engine Distribution
working-directory: repo working-directory: repo
shell: bash shell: bash
run: | run: |
sleep 1 sleep 1
sbt 'set engineDistributionRoot := file("${{ env.ENGINE_DIST_DIR }}"); buildEngineDistribution' sbt buildEngineDistribution
- name: Prepare Project Manager Distribution - name: Prepare Project Manager Distribution
working-directory: repo working-directory: repo
shell: bash shell: bash
run: | run: |
sleep 1 sleep 1
sbt 'set projectManagerDistributionRoot := file("${{ env.PROJECTMANAGER_DIST_DIR }}"); buildProjectManagerDistribution' sbt buildProjectManagerDistribution
# Ensure that the versions encoded in the binary and in the release match # Ensure that the versions encoded in the binary and in the release match
- name: Check Versions (Unix) - name: Check Versions (Unix)
@ -202,17 +202,17 @@ jobs:
- name: Upload the Engine Artifact - name: Upload the Engine Artifact
uses: actions/upload-artifact@v2 uses: actions/upload-artifact@v2
with: with:
name: ${{ env.ENGINE_DIST_ROOT }} name: ${{ env.ENGINE_DIST_NAME }}
path: repo/${{ env.ENGINE_DIST_ROOT }} path: repo/${{ env.ENGINE_DIST_ROOT }}
- name: Upload the Launcher Artifact - name: Upload the Launcher Artifact
uses: actions/upload-artifact@v2 uses: actions/upload-artifact@v2
with: with:
name: ${{ env.LAUNCHER_DIST_ROOT }} name: ${{ env.LAUNCHER_DIST_NAME }}
path: repo/${{ env.LAUNCHER_DIST_ROOT }} path: repo/${{ env.LAUNCHER_DIST_ROOT }}
- name: Upload the Project Manager Artifact - name: Upload the Project Manager Artifact
uses: actions/upload-artifact@v2 uses: actions/upload-artifact@v2
with: with:
name: ${{ env.PROJECTMANAGER_DIST_ROOT }} name: ${{ env.PROJECTMANAGER_DIST_NAME }}
path: repo/${{ env.PROJECTMANAGER_DIST_ROOT }} path: repo/${{ env.PROJECTMANAGER_DIST_ROOT }}
- name: Upload the Manifest Artifact - name: Upload the Manifest Artifact
uses: actions/upload-artifact@v2 uses: actions/upload-artifact@v2
@ -237,12 +237,36 @@ jobs:
# Without specifying options, it downloads all artifacts # Without specifying options, it downloads all artifacts
- uses: actions/download-artifact@v2 - uses: actions/download-artifact@v2
with: with:
path: artifacts path: repo/built-distribution
# This jobs can be used to debug errors, it may be removed # This jobs can be used to debug errors, it may be removed
- name: Display Structure of Downloaded Files - name: Display Structure of Downloaded Files
run: ls -R run: ls -R
working-directory: artifacts working-directory: repo/built-distribution
- name: Setup GraalVM Environment
uses: ayltai/setup-graalvm@v1
with:
graalvm-version: ${{ env.graalVersion }}
java-version: ${{ env.javaVersion }}
native-image: true
- name: Set Up SBT
shell: bash
run: |
curl -fsSL -o sbt.tgz https://github.com/sbt/sbt/releases/download/v${{env.sbtVersion}}/sbt-${{env.sbtVersion}}.tgz
tar -xzf sbt.tgz
echo $GITHUB_WORKSPACE/sbt/bin/ >> $GITHUB_PATH
# Caches
- name: Cache SBT
uses: actions/cache@v2
with:
path: |
~/.sbt
~/.ivy2/cache
~/.cache
key: ${{ runner.os }}-sbt-${{ hashFiles('**build.sbt') }}
restore-keys: ${{ runner.os }}-sbt-
- name: Save Version to Environment - name: Save Version to Environment
shell: bash shell: bash
@ -252,87 +276,19 @@ jobs:
echo "Preparing release for $DIST_VERSION" echo "Preparing release for $DIST_VERSION"
echo "DIST_VERSION=$DIST_VERSION" >> $GITHUB_ENV echo "DIST_VERSION=$DIST_VERSION" >> $GITHUB_ENV
- name: Download GraalVM for Bundles
shell: bash
run: |
curl -fsSL -o graalvm-linux.tar.gz "https://github.com/graalvm/graalvm-ce-builds/releases/download/vm-${{ env.graalVersion }}/graalvm-ce-java${{ env.javaVersion }}-linux-amd64-${{ env.graalVersion }}.tar.gz"
echo "Linux JVM downloaded"
curl -fsSL -o graalvm-macos.tar.gz "https://github.com/graalvm/graalvm-ce-builds/releases/download/vm-${{ env.graalVersion }}/graalvm-ce-java${{ env.javaVersion }}-darwin-amd64-${{ env.graalVersion }}.tar.gz"
echo "MacOS JVM downloaded"
curl -fsSL -o graalvm-windows.zip "https://github.com/graalvm/graalvm-ce-builds/releases/download/vm-${{ env.graalVersion }}/graalvm-ce-java${{ env.javaVersion }}-windows-amd64-${{ env.graalVersion }}.zip"
echo "Windows JVM downloaded"
mkdir graalvm-linux
mkdir graalvm-macos
mkdir graalvm-windows
(cd graalvm-linux && tar xf ../graalvm-linux.tar.gz)
echo "Linux JVM extracted"
(cd graalvm-macos && tar xf ../graalvm-macos.tar.gz)
echo "MacOS JVM extracted"
(cd graalvm-windows && unzip -q ../graalvm-windows.zip)
echo "Windows JVM extracted"
# As the download-artifact action does not preserve the executable bits,
# we fix them here, so that the release assets are easy to use.
- name: Fix Package Structure
shell: bash
run: |
chmod +x artifacts/enso-engine-${{ env.DIST_VERSION }}-linux-amd64/enso-${{ env.DIST_VERSION }}/bin/enso
chmod +x artifacts/enso-engine-${{ env.DIST_VERSION }}-macos-amd64/enso-${{ env.DIST_VERSION }}/bin/enso
chmod +x artifacts/enso-launcher-${{ env.DIST_VERSION }}-linux-amd64/enso/bin/enso
chmod +x artifacts/enso-launcher-${{ env.DIST_VERSION }}-macos-amd64/enso/bin/enso
chmod +x artifacts/enso-project-manager-${{ env.DIST_VERSION }}-linux-amd64/enso/bin/project-manager
chmod +x artifacts/enso-project-manager-${{ env.DIST_VERSION }}-macos-amd64/enso/bin/project-manager
mkdir artifacts/enso-launcher-${{ env.DIST_VERSION }}-linux-amd64/enso/config
mkdir artifacts/enso-launcher-${{ env.DIST_VERSION }}-linux-amd64/enso/dist
mkdir artifacts/enso-launcher-${{ env.DIST_VERSION }}-linux-amd64/enso/runtime
mkdir artifacts/enso-launcher-${{ env.DIST_VERSION }}-macos-amd64/enso/config
mkdir artifacts/enso-launcher-${{ env.DIST_VERSION }}-macos-amd64/enso/dist
mkdir artifacts/enso-launcher-${{ env.DIST_VERSION }}-macos-amd64/enso/runtime
mkdir artifacts/enso-launcher-${{ env.DIST_VERSION }}-windows-amd64/enso/config
mkdir artifacts/enso-launcher-${{ env.DIST_VERSION }}-windows-amd64/enso/dist
mkdir artifacts/enso-launcher-${{ env.DIST_VERSION }}-windows-amd64/enso/runtime
- name: Prepare Packages - name: Prepare Packages
shell: bash shell: bash
working-directory: repo
run: | run: |
(cd artifacts/enso-engine-${{ env.DIST_VERSION }}-linux-amd64/ && tar -czf ../../enso-engine-${{ env.DIST_VERSION }}-linux-amd64.tar.gz enso-${{ env.DIST_VERSION }} ) sleep 1
echo "Linux Engine packaged" sbt makePackages
(cd artifacts/enso-engine-${{ env.DIST_VERSION }}-macos-amd64/ && tar -czf ../../enso-engine-${{ env.DIST_VERSION }}-macos-amd64.tar.gz enso-${{ env.DIST_VERSION }} )
echo "MacOS Engine packaged"
(cd artifacts/enso-engine-${{ env.DIST_VERSION }}-windows-amd64/ && zip -q -r ../../enso-engine-${{ env.DIST_VERSION }}-windows-amd64.zip enso-${{ env.DIST_VERSION }} )
echo "Windows Engine packaged"
(cd artifacts/enso-launcher-${{ env.DIST_VERSION }}-linux-amd64/ && tar -czf ../../enso-launcher-${{ env.DIST_VERSION }}-linux-amd64.tar.gz enso )
echo "Linux Launcher packaged"
(cd artifacts/enso-launcher-${{ env.DIST_VERSION }}-macos-amd64/ && tar -czf ../../enso-launcher-${{ env.DIST_VERSION }}-macos-amd64.tar.gz enso )
echo "MacOS Launcher packaged"
(cd artifacts/enso-launcher-${{ env.DIST_VERSION }}-windows-amd64/ && zip -q -r ../../enso-launcher-${{ env.DIST_VERSION }}-windows-amd64.zip enso )
echo "Windows Launcher packaged"
(cd artifacts/enso-project-manager-${{ env.DIST_VERSION }}-linux-amd64/ && tar -czf ../../enso-project-manager-${{ env.DIST_VERSION }}-linux-amd64.tar.gz enso )
echo "Linux Project Manager packaged"
(cd artifacts/enso-project-manager-${{ env.DIST_VERSION }}-macos-amd64/ && tar -czf ../../enso-project-manager-${{ env.DIST_VERSION }}-macos-amd64.tar.gz enso )
echo "MacOS Project Manager packaged"
(cd artifacts/enso-project-manager-${{ env.DIST_VERSION }}-windows-amd64/ && zip -q -r ../../enso-project-manager-${{ env.DIST_VERSION }}-windows-amd64.zip enso )
echo "Windows Project Manager packaged"
- name: Prepare Bundles - name: Prepare Bundles
shell: bash shell: bash
working-directory: repo
run: | run: |
cp -r artifacts/enso-engine-${{ env.DIST_VERSION }}-linux-amd64/enso-${{ env.DIST_VERSION }} artifacts/enso-launcher-${{ env.DIST_VERSION }}-linux-amd64/enso/dist/${{ env.DIST_VERSION }} sleep 1
cp -r artifacts/enso-engine-${{ env.DIST_VERSION }}-macos-amd64/enso-${{ env.DIST_VERSION }} artifacts/enso-launcher-${{ env.DIST_VERSION }}-macos-amd64/enso/dist/${{ env.DIST_VERSION }} sbt makeBundles
cp -r artifacts/enso-engine-${{ env.DIST_VERSION }}-windows-amd64/enso-${{ env.DIST_VERSION }} artifacts/enso-launcher-${{ env.DIST_VERSION }}-windows-amd64/enso/dist/${{ env.DIST_VERSION }}
mv graalvm-linux/graalvm-ce-java${{ env.javaVersion }}-${{ env.graalVersion }} artifacts/enso-launcher-${{ env.DIST_VERSION }}-linux-amd64/enso/runtime
mv graalvm-macos/graalvm-ce-java${{ env.javaVersion }}-${{ env.graalVersion }} artifacts/enso-launcher-${{ env.DIST_VERSION }}-macos-amd64/enso/runtime
mv graalvm-windows/graalvm-ce-java${{ env.javaVersion }}-${{ env.graalVersion }} artifacts/enso-launcher-${{ env.DIST_VERSION }}-windows-amd64/enso/runtime
echo "Bundles prepared"
(cd artifacts/enso-launcher-${{ env.DIST_VERSION }}-linux-amd64/ && tar -czf ../../enso-bundle-${{ env.DIST_VERSION }}-linux-amd64.tar.gz enso )
echo "Linux Bundle packaged"
(cd artifacts/enso-launcher-${{ env.DIST_VERSION }}-macos-amd64/ && tar -czf ../../enso-bundle-${{ env.DIST_VERSION }}-macos-amd64.tar.gz enso )
echo "MacOS Bundle packaged"
(cd artifacts/enso-launcher-${{ env.DIST_VERSION }}-windows-amd64/ && zip -q -r ../../enso-bundle-${{ env.DIST_VERSION }}-windows-amd64.zip enso )
echo "Windows Bundle packaged"
- name: Create Release - name: Create Release
id: create_release id: create_release
@ -357,7 +313,7 @@ jobs:
shell: bash shell: bash
run: | run: |
sleep 1 sleep 1
docker build -t ensosharedwus2acr.azurecr.io/runtime:${{ env.DIST_VERSION }} -f ./repo/tools/ci/docker/Dockerfile ./artifacts/enso-engine-${{ env.DIST_VERSION }}-linux-amd64/enso-${{ env.DIST_VERSION }}/component docker build -t ensosharedwus2acr.azurecr.io/runtime:${{ env.DIST_VERSION }} -f ./repo/tools/ci/docker/Dockerfile ./repo/built-distribution/enso-engine-${{ env.DIST_VERSION }}-linux-amd64/enso-${{ env.DIST_VERSION }}/component
docker push ensosharedwus2acr.azurecr.io/runtime:${{ env.DIST_VERSION }} docker push ensosharedwus2acr.azurecr.io/runtime:${{ env.DIST_VERSION }}
# Publish the launcher packages to the backup/fallback S3 bucket # Publish the launcher packages to the backup/fallback S3 bucket
@ -373,25 +329,26 @@ jobs:
- name: Upload the Linux Launcher Package to S3 - name: Upload the Linux Launcher Package to S3
shell: bash shell: bash
run: > run: >
aws s3 cp enso-launcher-${{ env.DIST_VERSION }}-linux-amd64.tar.gz aws s3 cp repo/built-distribution/enso-launcher-${{ env.DIST_VERSION
s3://launcherfallback/launcher/enso-${{ env.DIST_VERSION }}/ --profile }}-linux-amd64.tar.gz s3://launcherfallback/launcher/enso-${{
s3-upload --acl public-read env.DIST_VERSION }}/ --profile s3-upload --acl public-read
- name: Upload the macOS Launcher Package to S3 - name: Upload the macOS Launcher Package to S3
shell: bash shell: bash
run: > run: >
aws s3 cp enso-launcher-${{ env.DIST_VERSION }}-macos-amd64.tar.gz aws s3 cp repo/built-distribution/enso-launcher-${{ env.DIST_VERSION
s3://launcherfallback/launcher/enso-${{ env.DIST_VERSION }}/ --profile }}-macos-amd64.tar.gz s3://launcherfallback/launcher/enso-${{
s3-upload --acl public-read env.DIST_VERSION }}/ --profile s3-upload --acl public-read
- name: Upload the Windows Launcher Package to S3 - name: Upload the Windows Launcher Package to S3
shell: bash shell: bash
run: > run: >
aws s3 cp enso-launcher-${{ env.DIST_VERSION }}-windows-amd64.zip aws s3 cp repo/built-distribution/enso-launcher-${{ env.DIST_VERSION
s3://launcherfallback/launcher/enso-${{ env.DIST_VERSION }}/ --profile }}-windows-amd64.zip s3://launcherfallback/launcher/enso-${{
s3-upload --acl public-read env.DIST_VERSION }}/ --profile s3-upload --acl public-read
- name: Upload the Launcher Manifest to S3 - name: Upload the Launcher Manifest to S3
shell: bash shell: bash
run: > run: >
aws s3 cp artifacts/launcher-manifest/launcher-manifest.yaml aws s3 cp
repo/built-distribution/launcher-manifest/launcher-manifest.yaml
s3://launcherfallback/launcher/enso-${{ env.DIST_VERSION s3://launcherfallback/launcher/enso-${{ env.DIST_VERSION
}}/launcher-manifest.yaml --profile s3-upload --acl public-read }}/launcher-manifest.yaml --profile s3-upload --acl public-read
- name: Update the Release List in S3 - name: Update the Release List in S3
@ -422,7 +379,9 @@ jobs:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with: with:
upload_url: ${{ steps.create_release.outputs.upload_url }} upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: enso-engine-${{ env.DIST_VERSION }}-linux-amd64.tar.gz asset_path:
repo/built-distribution/enso-engine-${{ env.DIST_VERSION
}}-linux-amd64.tar.gz
asset_name: enso-engine-${{ env.DIST_VERSION }}-linux-amd64.tar.gz asset_name: enso-engine-${{ env.DIST_VERSION }}-linux-amd64.tar.gz
asset_content_type: application/x-tar asset_content_type: application/x-tar
- name: Publish the Engine (MacOS) - name: Publish the Engine (MacOS)
@ -431,7 +390,9 @@ jobs:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with: with:
upload_url: ${{ steps.create_release.outputs.upload_url }} upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: enso-engine-${{ env.DIST_VERSION }}-macos-amd64.tar.gz asset_path:
repo/built-distribution/enso-engine-${{ env.DIST_VERSION
}}-macos-amd64.tar.gz
asset_name: enso-engine-${{ env.DIST_VERSION }}-macos-amd64.tar.gz asset_name: enso-engine-${{ env.DIST_VERSION }}-macos-amd64.tar.gz
asset_content_type: application/x-tar asset_content_type: application/x-tar
- name: Publish the Engine (Windows) - name: Publish the Engine (Windows)
@ -440,7 +401,9 @@ jobs:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with: with:
upload_url: ${{ steps.create_release.outputs.upload_url }} upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: enso-engine-${{ env.DIST_VERSION }}-windows-amd64.zip asset_path:
repo/built-distribution/enso-engine-${{ env.DIST_VERSION
}}-windows-amd64.zip
asset_name: enso-engine-${{ env.DIST_VERSION }}-windows-amd64.zip asset_name: enso-engine-${{ env.DIST_VERSION }}-windows-amd64.zip
asset_content_type: application/zip asset_content_type: application/zip
@ -450,7 +413,9 @@ jobs:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with: with:
upload_url: ${{ steps.create_release.outputs.upload_url }} upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: enso-launcher-${{ env.DIST_VERSION }}-linux-amd64.tar.gz asset_path:
repo/built-distribution/enso-launcher-${{ env.DIST_VERSION
}}-linux-amd64.tar.gz
asset_name: enso-launcher-${{ env.DIST_VERSION }}-linux-amd64.tar.gz asset_name: enso-launcher-${{ env.DIST_VERSION }}-linux-amd64.tar.gz
asset_content_type: application/x-tar asset_content_type: application/x-tar
- name: Publish the Launcher (MacOS) - name: Publish the Launcher (MacOS)
@ -459,7 +424,9 @@ jobs:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with: with:
upload_url: ${{ steps.create_release.outputs.upload_url }} upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: enso-launcher-${{ env.DIST_VERSION }}-macos-amd64.tar.gz asset_path:
repo/built-distribution/enso-launcher-${{ env.DIST_VERSION
}}-macos-amd64.tar.gz
asset_name: enso-launcher-${{ env.DIST_VERSION }}-macos-amd64.tar.gz asset_name: enso-launcher-${{ env.DIST_VERSION }}-macos-amd64.tar.gz
asset_content_type: application/x-tar asset_content_type: application/x-tar
- name: Publish the Launcher (Windows) - name: Publish the Launcher (Windows)
@ -468,7 +435,9 @@ jobs:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with: with:
upload_url: ${{ steps.create_release.outputs.upload_url }} upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: enso-launcher-${{ env.DIST_VERSION }}-windows-amd64.zip asset_path:
repo/built-distribution/enso-launcher-${{ env.DIST_VERSION
}}-windows-amd64.zip
asset_name: enso-launcher-${{ env.DIST_VERSION }}-windows-amd64.zip asset_name: enso-launcher-${{ env.DIST_VERSION }}-windows-amd64.zip
asset_content_type: application/zip asset_content_type: application/zip
@ -479,7 +448,8 @@ jobs:
with: with:
upload_url: ${{ steps.create_release.outputs.upload_url }} upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: asset_path:
enso-project-manager-${{ env.DIST_VERSION }}-linux-amd64.tar.gz repo/built-distribution/enso-project-manager-${{ env.DIST_VERSION
}}-linux-amd64.tar.gz
asset_name: asset_name:
enso-project-manager-${{ env.DIST_VERSION }}-linux-amd64.tar.gz enso-project-manager-${{ env.DIST_VERSION }}-linux-amd64.tar.gz
asset_content_type: application/x-tar asset_content_type: application/x-tar
@ -490,7 +460,8 @@ jobs:
with: with:
upload_url: ${{ steps.create_release.outputs.upload_url }} upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: asset_path:
enso-project-manager-${{ env.DIST_VERSION }}-macos-amd64.tar.gz repo/built-distribution/enso-project-manager-${{ env.DIST_VERSION
}}-macos-amd64.tar.gz
asset_name: asset_name:
enso-project-manager-${{ env.DIST_VERSION }}-macos-amd64.tar.gz enso-project-manager-${{ env.DIST_VERSION }}-macos-amd64.tar.gz
asset_content_type: application/x-tar asset_content_type: application/x-tar
@ -501,7 +472,8 @@ jobs:
with: with:
upload_url: ${{ steps.create_release.outputs.upload_url }} upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: asset_path:
enso-project-manager-${{ env.DIST_VERSION }}-windows-amd64.zip repo/built-distribution/enso-project-manager-${{ env.DIST_VERSION
}}-windows-amd64.zip
asset_name: asset_name:
enso-project-manager-${{ env.DIST_VERSION }}-windows-amd64.zip enso-project-manager-${{ env.DIST_VERSION }}-windows-amd64.zip
asset_content_type: application/zip asset_content_type: application/zip
@ -512,7 +484,9 @@ jobs:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with: with:
upload_url: ${{ steps.create_release.outputs.upload_url }} upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: enso-bundle-${{ env.DIST_VERSION }}-linux-amd64.tar.gz asset_path:
repo/built-distribution/enso-bundle-${{ env.DIST_VERSION
}}-linux-amd64.tar.gz
asset_name: enso-bundle-${{ env.DIST_VERSION }}-linux-amd64.tar.gz asset_name: enso-bundle-${{ env.DIST_VERSION }}-linux-amd64.tar.gz
asset_content_type: application/x-tar asset_content_type: application/x-tar
- name: Publish the Bundle (MacOS) - name: Publish the Bundle (MacOS)
@ -521,7 +495,9 @@ jobs:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with: with:
upload_url: ${{ steps.create_release.outputs.upload_url }} upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: enso-bundle-${{ env.DIST_VERSION }}-macos-amd64.tar.gz asset_path:
repo/built-distribution/enso-bundle-${{ env.DIST_VERSION
}}-macos-amd64.tar.gz
asset_name: enso-bundle-${{ env.DIST_VERSION }}-macos-amd64.tar.gz asset_name: enso-bundle-${{ env.DIST_VERSION }}-macos-amd64.tar.gz
asset_content_type: application/x-tar asset_content_type: application/x-tar
- name: Publish the Bundle (Windows) - name: Publish the Bundle (Windows)
@ -530,7 +506,9 @@ jobs:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with: with:
upload_url: ${{ steps.create_release.outputs.upload_url }} upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: enso-bundle-${{ env.DIST_VERSION }}-windows-amd64.zip asset_path:
repo/built-distribution/enso-bundle-${{ env.DIST_VERSION
}}-windows-amd64.zip
asset_name: enso-bundle-${{ env.DIST_VERSION }}-windows-amd64.zip asset_name: enso-bundle-${{ env.DIST_VERSION }}-windows-amd64.zip
asset_content_type: application/zip asset_content_type: application/zip
@ -540,7 +518,7 @@ jobs:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with: with:
upload_url: ${{ steps.create_release.outputs.upload_url }} upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: artifacts/manifest/manifest.yaml asset_path: repo/built-distribution/manifest/manifest.yaml
asset_name: manifest.yaml asset_name: manifest.yaml
asset_content_type: application/yaml asset_content_type: application/yaml
- name: Publish the Launcher Manifest - name: Publish the Launcher Manifest
@ -549,6 +527,6 @@ jobs:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with: with:
upload_url: ${{ steps.create_release.outputs.upload_url }} upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: artifacts/launcher-manifest/launcher-manifest.yaml asset_path: repo/built-distribution/launcher-manifest/launcher-manifest.yaml
asset_name: launcher-manifest.yaml asset_name: launcher-manifest.yaml
asset_content_type: application/yaml asset_content_type: application/yaml

View File

@ -39,7 +39,7 @@ jobs:
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
- name: Enable Developer Command Prompt (Windows) - name: Enable Developer Command Prompt (Windows)
uses: ilammy/msvc-dev-cmd@v1.4.1 uses: ilammy/msvc-dev-cmd@v1.5.0
- name: Setup Go - name: Setup Go
uses: actions/setup-go@v2 uses: actions/setup-go@v2
- name: Disable TCP/UDP Offloading (macOS) - name: Disable TCP/UDP Offloading (macOS)
@ -188,7 +188,7 @@ jobs:
shell: bash shell: bash
run: | run: |
sleep 1 sleep 1
sbt 'set launcherDistributionRoot := file("${{ env.LAUNCHER_DIST_DIR }}"); buildLauncherDistribution' sbt buildLauncherDistribution
# The way artifacts are uploaded currently does not preserve the # The way artifacts are uploaded currently does not preserve the
# executable bits for Unix. However putting artifacts into a ZIP would # executable bits for Unix. However putting artifacts into a ZIP would
@ -201,13 +201,13 @@ jobs:
shell: bash shell: bash
run: | run: |
sleep 1 sleep 1
sbt 'set engineDistributionRoot := file("${{ env.ENGINE_DIST_DIR }}"); buildEngineDistribution' sbt buildEngineDistribution
- name: Prepare Project Manager Distribution - name: Prepare Project Manager Distribution
shell: bash shell: bash
run: | run: |
sleep 1 sleep 1
sbt 'set projectManagerDistributionRoot := file("${{ env.PROJECTMANAGER_DIST_DIR }}"); buildProjectManagerDistribution' sbt buildProjectManagerDistribution
# Test Distribution # Test Distribution
- name: Prepare Engine Test Environment - name: Prepare Engine Test Environment
@ -243,17 +243,17 @@ jobs:
- name: Publish the Engine Distribution Artifact - name: Publish the Engine Distribution Artifact
uses: actions/upload-artifact@v2 uses: actions/upload-artifact@v2
with: with:
name: ${{ env.ENGINE_DIST_ROOT }} name: ${{ env.ENGINE_DIST_NAME }}
path: ${{ env.ENGINE_DIST_ROOT }} path: ${{ env.ENGINE_DIST_ROOT }}
- name: Publish the Launcher - name: Publish the Launcher
uses: actions/upload-artifact@v2 uses: actions/upload-artifact@v2
with: with:
name: ${{ env.LAUNCHER_DIST_ROOT }} name: ${{ env.LAUNCHER_DIST_NAME }}
path: ${{ env.LAUNCHER_DIST_ROOT }} path: ${{ env.LAUNCHER_DIST_ROOT }}
- name: Publish the Project Manager - name: Publish the Project Manager
uses: actions/upload-artifact@v2 uses: actions/upload-artifact@v2
with: with:
name: ${{ env.PROJECTMANAGER_DIST_ROOT }} name: ${{ env.PROJECTMANAGER_DIST_NAME }}
path: ${{ env.PROJECTMANAGER_DIST_ROOT }} path: ${{ env.PROJECTMANAGER_DIST_ROOT }}
- name: Prepare the FlatBuffers Schemas for Upload - name: Prepare the FlatBuffers Schemas for Upload

View File

@ -93,6 +93,13 @@ openLegalReviewReport := {
lazy val analyzeDependency = inputKey[Unit]("...") lazy val analyzeDependency = inputKey[Unit]("...")
analyzeDependency := GatherLicenses.analyzeDependency.evaluated analyzeDependency := GatherLicenses.analyzeDependency.evaluated
val packageBuilder = new DistributionPackage.Builder(
ensoVersion = ensoVersion,
graalVersion = graalVersion,
graalJavaVersion = javaVersion,
artifactRoot = file("built-distribution")
)
Global / onChangedBuildSource := ReloadOnSourceChanges Global / onChangedBuildSource := ReloadOnSourceChanges
// ============================================================================ // ============================================================================
@ -201,6 +208,9 @@ lazy val enso = (project in file("."))
testkit testkit
) )
.settings(Global / concurrentRestrictions += Tags.exclusive(Exclusive)) .settings(Global / concurrentRestrictions += Tags.exclusive(Exclusive))
.settings(
commands ++= Seq(packageBuilder.makePackages, packageBuilder.makeBundles)
)
// ============================================================================ // ============================================================================
// === Dependency Versions ==================================================== // === Dependency Versions ====================================================
@ -1338,9 +1348,11 @@ lazy val launcherDistributionRoot =
lazy val projectManagerDistributionRoot = lazy val projectManagerDistributionRoot =
settingKey[File]("Root of built project manager distribution") settingKey[File]("Root of built project manager distribution")
engineDistributionRoot := file("built-distribution/engine") engineDistributionRoot :=
launcherDistributionRoot := file("built-distribution/launcher") packageBuilder.localArtifact("engine") / s"enso-$ensoVersion"
projectManagerDistributionRoot := file("built-distribution/project-manager") launcherDistributionRoot := packageBuilder.localArtifact("launcher") / "enso"
projectManagerDistributionRoot :=
packageBuilder.localArtifact("project-manager") / "enso"
lazy val buildEngineDistribution = lazy val buildEngineDistribution =
taskKey[Unit]("Builds the engine distribution") taskKey[Unit]("Builds the engine distribution")

View File

@ -0,0 +1 @@
Enso Bundle Marker

View File

@ -27,7 +27,7 @@ dependencies, and Enso projects for use by our users.
Explanation of the fallback infrastructure that can be enabled to keep Explanation of the fallback infrastructure that can be enabled to keep
launcher updates functioning even if the primary release provider stops launcher updates functioning even if the primary release provider stops
working. working.
- [**Local Repository:**](local-repository.md) Explanation of local repository
structure that is used for bundling engine with project manager distributions.
- [**Standard Libraries:**](standard-libraries.md) A brief explanation of the - [**Standard Libraries:**](standard-libraries.md) A brief explanation of the
standard libraries for Enso. standard libraries for Enso.
- [**Bundles**](bundles.md) An explanation of distributed bundles that contain
all components necessary to run Enso out of the box.

View File

@ -0,0 +1,71 @@
---
layout: developer-doc
title: Distribution Bundles
category: distribution
tags: [distribution, layout, bundles]
order: 9
---
# Bundles
This document describes how the distributions are bundled to provide releases
that work out-of-the box, allowing to use the latest engine without downloading
any additional dependencies.
<!-- MarkdownTOC levels="2,3" autolink="true" -->
- [Project Manager Bundle](#project-manager-bundle)
- [Launcher Bundles](#launcher-bundles)
<!-- /MarkdownTOC -->
## Project Manager Bundle
The Project Manager is distributed with latest engine version and its
corresponding Graal runtime to avoid having to download them at first startup.
The bundled components are placed in their respective subdirectories (not as
packages, but extracted and ready to use) and a bundle marker file called
`.enso.bundle` must be placed next to these directories so that the Project
Manager can detect the bundle.
The `project-manager` executable looks for the `.enso.bundle` marker in the
parent directory of the directory that it is, itself, located in. So overall,
the bundle should have the following structure (the actual engine and Graal
versions may of course differ):
```
enso
├── bin
│ └── project-manager
├── dist
│ └── 0.2.1-SNAPSHOT
├── other-project-manager-files
└── runtime
└── graalvm-ce-java11-20.2.0
```
If the bundle is detected, the additional `dist` and `runtime` directories are
added as secondary search paths for components. Thus, the `project-manager` can
use both components present in the default
[installed location](distribution.md#installed-enso-distribution-layout) or
those from the bundle. In a situation that the same component were to be
available both in the installed location and the bundle, the installed location
is preferred. New components are installed in the installed location, never next
to the bundles.
In fact, it is possible for the bundle directory to be read-only (which may be
the case for example if the Project Manager bundle is packaged as part of IDE's
AppImage package). In such situation, it will be impossible to uninstall the
bundled components and a relevant error message will be returned.
## Launcher Bundles
Bundles are also distributed for the launcher, but these are implemented using a
different mechanism.
Since the launcher can run in
[portable mode](distribution.md#portable-enso-distribution-layout), the bundled
engine and runtime are simply included within its portable package. They can
then be used from within this portable package or
[installed](distribution.md#installing-from-a-portable-distribution).

View File

@ -3,7 +3,7 @@ layout: developer-doc
title: Fallback Launcher Release Infrastructure title: Fallback Launcher Release Infrastructure
category: distribution category: distribution
tags: [distribution, launcher, fallback] tags: [distribution, launcher, fallback]
order: 6 order: 7
--- ---
# Fallback Launcher Release Infrastructure # Fallback Launcher Release Infrastructure

View File

@ -1,107 +0,0 @@
---
layout: developer-doc
title: Local Repository
category: distribution
tags: [distribution, project-manager, offline, local]
order: 7
---
# Local Repository
A `LocalReleaseProvider` is implemented that allows to install components from
local (offline) repositories. This functionality can be used to allow installing
bundled components.
<!-- MarkdownTOC levels="2,3" autolink="true" -->
- [Local Repository Structure](#local-repository-structure)
- [General Repository Structure](#general-repository-structure)
- [Engine Repository Structure](#engine-repository-structure)
- [GraalVM Repository Structure](#graalvm-repository-structure)
- [Usage in Project Manager](#usage-in-project-manager)
<!-- /MarkdownTOC -->
## Local Repository Structure
This section explains how a local repository has to be structured in order to
work with `LocalReleaseProvider`.
### General Repository Structure
In general a local repository should be a separate directory that contains only
directories corresponding to releases of a single component. Repositories for
separate components should be kept separately. For each provided release it
should contain a directory called after the release's tag. That directory of
each release should just contain assets associated with that release.
### Engine Repository Structure
The engine repository contains a directory `enso-<VERSION>` for each release
that resembles the GitHub release structure. The directory for each release
should contain a `manifest.yaml` file and the package. As separate bundles are
created for each operating system, only the package for the desired operating
system is required (normally releases contain packages for all supported
systems). It's naming scheme is the same as in GitHub releases, that is
`enso-engine-<VERSION>-<OS>-<ARCH>.{zip|tar.gz}`. The extension is `zip` for
Windows and `tar.gz` for other platforms. Currently, the only supported `ARCH`
is `amd64`. The `OS` can be one of `windows`, `linux`, `macos`.
For example, a local engine repository could look like this:
```
localengine
└── enso-0.1.2-rc.9
├── enso-engine-0.1.2-rc.9-linux-amd64.tar.gz
└── manifest.yaml
```
### GraalVM Repository Structure
The GraalVM repository contains a directory `vm-<VERSION>` for each release.
Inside of that directory, a package should be included (again as in case of the
engine, only the package for the current operating system is required). The
package name should be
`graalvm-ce-java<JAVA_VERSION>-<OS>-<ARCH>-<VERSION>.{zip|tar.gz}`. The
extension is `zip` on Windows and `tar.gz` on other platforms. The `ARCH` should
be the same as for the engine. The `OS` can be one of `windows`, `linux`,
`darwin`. **Note that the MacOS package has different naming scheme for GraalVM
than it has for the engine**.
For example, a local GraalVM repository can look like this:
```
localruntime
└── vm-20.2.0
└── graalvm-ce-java11-linux-amd64-20.2.0.tar.gz
```
## Usage in Project Manager
Command line options can be used to enable the local repositories in the project
manager. `--local-engine-repository` sets the path to the engine repository and
`--local-graal-repository` sets the path to the GraalVM repository. The provided
paths should be absolute to be sure that they are resolved correctly.
For example, starting the project manager as written below will first look for
engines and GraalVM runtimes in the provided local repositories (but if they are
not found, online repository will be used as fallback, if it is available).
```bash
./project-manager --local-engine-repository /a/b/bundle/engines --local-graal-repository /a/b/bundle/graalvm
```
For the above command to work properly, the directory `/a/b/bundle/` may have
the following structure:
```
bundle
├── other files (project-manager binary etc.)
├── engines
│ └── enso-0.1.2-rc.9
│ ├── enso-engine-0.1.2-rc.9-linux-amd64.tar.gz
│ └── manifest.yaml
└── graalvm
└── vm-20.2.0
└── graalvm-ce-java11-linux-amd64-20.2.0.tar.gz
```

View File

@ -3,7 +3,7 @@ layout: developer-doc
title: Standard Libraries title: Standard Libraries
category: distribution category: distribution
tags: [distribution, stdlib] tags: [distribution, stdlib]
order: 9 order: 8
--- ---
# Standard Libraries # Standard Libraries

View File

@ -7,12 +7,10 @@ import scala.util.Try
object Cli { object Cli {
val JSON_OPTION = "json" val JSON_OPTION = "json"
val HELP_OPTION = "help" val HELP_OPTION = "help"
val VERBOSE_OPTION = "verbose" val VERBOSE_OPTION = "verbose"
val VERSION_OPTION = "version" val VERSION_OPTION = "version"
val LOCAL_ENGINE_REPOSITORY = "local-engine-repository"
val LOCAL_GRAAL_REPOSITORY = "local-graal-repository"
object option { object option {
@ -37,28 +35,6 @@ object Cli {
.longOpt(JSON_OPTION) .longOpt(JSON_OPTION)
.desc("Switches the --version option to JSON output.") .desc("Switches the --version option to JSON output.")
.build() .build()
val localEngineRepository: cli.Option = cli.Option.builder
.longOpt(LOCAL_ENGINE_REPOSITORY)
.hasArg
.numberOfArgs(1)
.argName("path")
.desc(
"Allows the Project Manager to install engine versions from an " +
"offline repository."
)
.build()
val localGraalRepository: cli.Option = cli.Option.builder
.longOpt(LOCAL_GRAAL_REPOSITORY)
.hasArg
.numberOfArgs(1)
.argName("path")
.desc(
"Allows the Project Manager to install GraalVM versions from an " +
"offline repository."
)
.build()
} }
val options: cli.Options = val options: cli.Options =
@ -67,8 +43,6 @@ object Cli {
.addOption(option.verbose) .addOption(option.verbose)
.addOption(option.version) .addOption(option.version)
.addOption(option.json) .addOption(option.json)
.addOption(option.localEngineRepository)
.addOption(option.localGraalRepository)
/** Parse the command line options. */ /** Parse the command line options. */
def parse(args: Array[String]): Either[String, cli.CommandLine] = { def parse(args: Array[String]): Either[String, cli.CommandLine] = {

View File

@ -1,7 +1,6 @@
package org.enso.projectmanager.boot package org.enso.projectmanager.boot
import java.io.IOException import java.io.IOException
import java.nio.file.{InvalidPathException, Path}
import java.util.concurrent.ScheduledThreadPoolExecutor import java.util.concurrent.ScheduledThreadPoolExecutor
import akka.http.scaladsl.Http import akka.http.scaladsl.Http
@ -15,7 +14,6 @@ import org.enso.projectmanager.boot.Globals.{
SuccessExitCode SuccessExitCode
} }
import org.enso.projectmanager.boot.configuration.ProjectManagerConfig import org.enso.projectmanager.boot.configuration.ProjectManagerConfig
import org.enso.projectmanager.versionmanagement.DefaultDistributionConfiguration
import org.enso.version.VersionDescription import org.enso.version.VersionDescription
import pureconfig.ConfigSource import pureconfig.ConfigSource
import pureconfig.generic.auto._ import pureconfig.generic.auto._
@ -120,44 +118,7 @@ object ProjectManager extends App with LazyLogging {
} else { } else {
val verbosity = options.getOptions.count(_ == Cli.option.verbose) val verbosity = options.getOptions.count(_ == Cli.option.verbose)
logger.info("Starting Project Manager...") logger.info("Starting Project Manager...")
setupLogging(verbosity) *>
def parsePath(string: String) = ZIO.effect(Path.of(string))
def parseOptionalPath(string: Option[String]) =
string.map(parsePath).map(_.map(Some(_))).getOrElse(ZIO.succeed(None))
val initializeLocalRepositories = for {
enginePath <- parseOptionalPath(
Option(options.getOptionValue(Cli.LOCAL_ENGINE_REPOSITORY))
)
graalPath <- parseOptionalPath(
Option(options.getOptionValue(Cli.LOCAL_GRAAL_REPOSITORY))
)
_ <- ZIO.effect(
DefaultDistributionConfiguration.setupLocalRepositories(
enginePath,
graalPath
)
)
} yield ()
val initializeRepositoryOrLogError = initializeLocalRepositories
.catchSome { case error: InvalidPathException =>
ZIO.effectTotal(
logger
.error(s"Could not parse a local repository path: $error", error)
)
}
.catchAll { th =>
ZIO.effectTotal(
logger.error(
"Failed to initialize local repositories, " +
"default ones will be used.",
th
)
)
}
setupLogging(verbosity) *> initializeRepositoryOrLogError *>
mainProcess.fold( mainProcess.fold(
th => { th => {
logger.error("Main process execution failed.", th) logger.error("Main process execution failed.", th)

View File

@ -2,19 +2,8 @@ package org.enso.projectmanager.service.versionmanagement
import org.enso.projectmanager.control.effect.ErrorChannel import org.enso.projectmanager.control.effect.ErrorChannel
import org.enso.projectmanager.service.ProjectServiceFailure import org.enso.projectmanager.service.ProjectServiceFailure
import org.enso.projectmanager.service.ProjectServiceFailure.{ import org.enso.projectmanager.service.ProjectServiceFailure._
BrokenComponentFailure, import org.enso.runtimeversionmanager.components._
ComponentInstallationFailure,
MissingComponentFailure,
ProjectManagerUpgradeRequiredFailure
}
import org.enso.runtimeversionmanager.components.{
BrokenComponentError,
ComponentMissingError,
ComponentsException,
InstallationError,
UpgradeRequiredError
}
object RuntimeVersionManagerErrorRecoverySyntax { object RuntimeVersionManagerErrorRecoverySyntax {
implicit class ErrorRecovery[F[+_, +_]: ErrorChannel, A]( implicit class ErrorRecovery[F[+_, +_]: ErrorChannel, A](
@ -42,6 +31,8 @@ object RuntimeVersionManagerErrorRecoverySyntax {
ProjectManagerUpgradeRequiredFailure( ProjectManagerUpgradeRequiredFailure(
upgradeRequired.expectedVersion upgradeRequired.expectedVersion
) )
case UninstallationError(message) =>
ComponentUninstallationFailure(message)
case _ => mapDefault(componentsException) case _ => mapDefault(componentsException)
} }
case other: Throwable => case other: Throwable =>

View File

@ -1,7 +1,5 @@
package org.enso.projectmanager.versionmanagement package org.enso.projectmanager.versionmanagement
import java.nio.file.Path
import com.typesafe.scalalogging.LazyLogging import com.typesafe.scalalogging.LazyLogging
import org.enso.runtimeversionmanager.Environment import org.enso.runtimeversionmanager.Environment
import org.enso.runtimeversionmanager.components.{ import org.enso.runtimeversionmanager.components.{
@ -19,10 +17,7 @@ import org.enso.runtimeversionmanager.releases.engine.{
EngineRelease, EngineRelease,
EngineRepository EngineRepository
} }
import org.enso.runtimeversionmanager.releases.graalvm.{ import org.enso.runtimeversionmanager.releases.graalvm.GraalCEReleaseProvider
GraalCEReleaseProvider,
GraalVMRuntimeReleaseProvider
}
import org.enso.runtimeversionmanager.runner.JVMSettings import org.enso.runtimeversionmanager.runner.JVMSettings
/** Default distribution configuration to use for the Project Manager in /** Default distribution configuration to use for the Project Manager in
@ -52,15 +47,9 @@ object DefaultDistributionConfiguration
lazy val temporaryDirectoryManager = lazy val temporaryDirectoryManager =
new TemporaryDirectoryManager(distributionManager, resourceManager) new TemporaryDirectoryManager(distributionManager, resourceManager)
private var currentEngineReleaseProvider: ReleaseProvider[EngineRelease] =
EngineRepository.defaultEngineReleaseProvider
/** @inheritdoc */ /** @inheritdoc */
def engineReleaseProvider: ReleaseProvider[EngineRelease] = def engineReleaseProvider: ReleaseProvider[EngineRelease] =
currentEngineReleaseProvider EngineRepository.defaultEngineReleaseProvider
private var runtimeReleaseProvider: GraalVMRuntimeReleaseProvider =
GraalCEReleaseProvider.default
/** @inheritdoc */ /** @inheritdoc */
def makeRuntimeVersionManager( def makeRuntimeVersionManager(
@ -72,7 +61,7 @@ object DefaultDistributionConfiguration
temporaryDirectoryManager = temporaryDirectoryManager, temporaryDirectoryManager = temporaryDirectoryManager,
resourceManager = resourceManager, resourceManager = resourceManager,
engineReleaseProvider = engineReleaseProvider, engineReleaseProvider = engineReleaseProvider,
runtimeReleaseProvider = runtimeReleaseProvider, runtimeReleaseProvider = GraalCEReleaseProvider.default,
installerKind = InstallerKind.ProjectManager installerKind = InstallerKind.ProjectManager
) )
@ -81,35 +70,4 @@ object DefaultDistributionConfiguration
/** @inheritdoc */ /** @inheritdoc */
override def shouldDiscardChildOutput: Boolean = false override def shouldDiscardChildOutput: Boolean = false
/** Sets up local repositories if they were requested.
* @param engineRepositoryPath the path to a local engine repository if one
* should be used
* @param graalRepositoryPath the path to a local GraalVM repository if one
* should be used
*/
def setupLocalRepositories(
engineRepositoryPath: Option[Path],
graalRepositoryPath: Option[Path]
): Unit = {
val engineProviderOverride =
engineRepositoryPath.map(path =>
(path, EngineRepository.fromLocalRepository(path))
)
val graalProviderOverride =
graalRepositoryPath.map(path =>
(path, GraalCEReleaseProvider.fromLocalRepository(path))
)
engineProviderOverride.foreach { case (path, newProvider) =>
logger.debug(s"Using a local engine repository from $path.")
currentEngineReleaseProvider = newProvider
}
graalProviderOverride.foreach { case (path, newProvider) =>
logger.debug(s"Using a local GraalVM repository from $path.")
runtimeReleaseProvider = newProvider
}
}
} }

View File

@ -38,7 +38,7 @@ class TestRuntimeVersionManagementUserInterface(installBroken: Boolean)
true true
/** @inheritdoc */ /** @inheritdoc */
override def logInfo(message: => String): Unit = logger.debug(message) override def logInfo(message: => String): Unit = logger.info(message)
override def startWaitingForResource(resource: Resource): Unit = override def startWaitingForResource(resource: Resource): Unit =
logger.debug(s"Waiting on ${resource.name}") logger.debug(s"Waiting on ${resource.name}")

View File

@ -1,168 +0,0 @@
package org.enso.runtimeversionmanager.components
import java.nio.file.Files
import nl.gn0s1s.bump.SemVer
import org.enso.loggingservice.TestLogger
import org.enso.runtimeversionmanager.FileSystem
import org.enso.runtimeversionmanager.FileSystem.PathSyntax
import org.enso.runtimeversionmanager.releases.{
EnsoReleaseProvider,
Release,
SimpleReleaseProvider
}
import org.enso.runtimeversionmanager.releases.engine.EngineReleaseProvider
import org.enso.runtimeversionmanager.releases.graalvm.GraalCEReleaseProvider
import org.enso.runtimeversionmanager.releases.local.LocalReleaseProvider
import org.enso.runtimeversionmanager.releases.testing.TestArchivePackager
import org.enso.runtimeversionmanager.test.{
FakeReleases,
RuntimeVersionManagerTest
}
import scala.util.{Failure, Try}
class LocalReleaseProviderSpec extends RuntimeVersionManagerTest {
private def localEngines = getTestDirectory / "offline-engine"
private def localRuntimes = getTestDirectory / "offline-graal"
/** Creates a [[RuntimeVersionManager]] that is tied to the local/offline
* repository with fallback to default fake release repository.
*/
private def makeRuntimeManagerWithLocalRepository(): RuntimeVersionManager = {
val engineProvider =
new LocalReleaseProvider(localEngines, FakeReleases.baseEngineProvider)
val runtimeProvider =
new LocalReleaseProvider(localRuntimes, FakeReleases.baseRuntimeProvider)
makeManagers(
engineProvider = new EngineReleaseProvider(engineProvider),
runtimeProvider = new GraalCEReleaseProvider(runtimeProvider)
)._2
}
/** Creates a local repository with engine 1.2.3-local that uses GraalVM 20.20.20-local.
*
* It uses the prepared fake-releases and just re-configures them.
*/
private def prepareLocalRepository(): Unit = {
Files.createDirectories(localEngines)
Files.createDirectories(localRuntimes)
{
val engineVersion = SemVer(1, 2, 3, Some("local"))
val engineName = s"enso-$engineVersion"
val destinationRoot = localEngines / engineName
Files.createDirectories(destinationRoot)
val manifest =
"""minimum-launcher-version: 0.0.1
|minimum-project-manager-version: 0.0.1
|graal-vm-version: 20.20.20-local
|graal-java-version: 11
|""".stripMargin
val sourceArchive =
EnsoReleaseProvider.packageNameForComponent("engine", SemVer(0, 0, 0))
val tmpArchive = getTestDirectory / sourceArchive
FileSystem.copyDirectory(
FakeReleases.engineRoot / "enso-0.0.0" / sourceArchive / "enso-0.0.0",
tmpArchive / engineName
)
FileSystem.writeTextFile(
tmpArchive / engineName / "manifest.yaml",
manifest
)
FileSystem.writeTextFile(destinationRoot / "manifest.yaml", manifest)
val archive =
EnsoReleaseProvider.packageNameForComponent("engine", engineVersion)
TestArchivePackager.packArchive(
tmpArchive,
destinationRoot / archive
)
}
{
val sourceVersion =
GraalVMVersion(SemVer(2, 0, 0), "11")
val targetVersion =
GraalVMVersion(SemVer(20, 20, 20, Some("local")), "11")
val destinationRoot = localRuntimes / s"vm-${targetVersion.graalVersion}"
Files.createDirectories(destinationRoot)
val sourceRoot =
FakeReleases.runtimeRoot / s"vm-${sourceVersion.graalVersion}"
TestArchivePackager.packArchive(
sourceRoot / GraalCEReleaseProvider.packageFileNameForCurrentOS(
sourceVersion
),
destinationRoot / GraalCEReleaseProvider.packageFileNameForCurrentOS(
targetVersion
)
)
}
}
"LocalReleaseProvider" should {
"install a release from a local repository" in {
prepareLocalRepository()
val runtimeVersionManager = makeRuntimeManagerWithLocalRepository()
val engineVersion = SemVer(1, 2, 3, Some("local"))
val runtimeVersion =
GraalVMVersion(SemVer(20, 20, 20, Some("local")), "11")
runtimeVersionManager.findOrInstallEngine(engineVersion)
runtimeVersionManager
.listInstalledEngines()
.map(_.version) shouldEqual Seq(engineVersion)
runtimeVersionManager
.listInstalledGraalRuntimes()
.map(_.version) shouldEqual Seq(runtimeVersion)
}
"install a release from the fallback repository" in {
val runtimeVersionManager = makeRuntimeManagerWithLocalRepository()
val engineVersion = SemVer(0, 0, 0)
runtimeVersionManager.findOrInstallEngine(engineVersion)
runtimeVersionManager
.listInstalledEngines()
.map(_.version) shouldEqual Seq(engineVersion)
}
"include releases from both local and fallback" in {
val localVersion = "enso-1.2.3-local"
Files.createDirectories(localEngines / localVersion)
val releaseProvider = new LocalReleaseProvider(
localEngines,
FakeReleases.baseEngineProvider
)
val tags = releaseProvider.listReleases().get.map(_.tag)
tags should contain(localVersion)
tags should contain("enso-0.0.0")
}
"work in 'offline-mode' if fallback is unavailable" in {
val unavailableProvider = new SimpleReleaseProvider {
override def releaseForTag(tag: String): Try[Release] =
Failure(new RuntimeException("Repository unavailable."))
override def listReleases(): Try[Seq[Release]] =
Failure(new RuntimeException("Repository unavailable."))
}
val localVersion = "enso-1.2.3-local"
Files.createDirectories(localEngines / localVersion)
val releaseProvider =
new LocalReleaseProvider(localEngines, unavailableProvider)
val logs = TestLogger.gatherLogs {
releaseProvider.listReleases().get.map(_.tag) shouldEqual Seq(
localVersion
)
Thread.sleep(500) // making sure the log is processed
}
val expectedMessage =
"The remote release provider failed with java.lang.RuntimeException: " +
"Repository unavailable., but locally bundled releases are available."
logs.map(_.message) should contain(expectedMessage)
}
}
}

View File

@ -1,14 +1,18 @@
package org.enso.runtimeversionmanager.components package org.enso.runtimeversionmanager.components
import java.nio.file.{Files, Path}
import nl.gn0s1s.bump.SemVer import nl.gn0s1s.bump.SemVer
import org.enso.runtimeversionmanager.components import org.enso.runtimeversionmanager.FileSystem.PathSyntax
import org.enso.runtimeversionmanager.config.GlobalConfigurationManager import org.enso.runtimeversionmanager.config.GlobalConfigurationManager
import org.enso.runtimeversionmanager.test.{ import org.enso.runtimeversionmanager.test.{
RuntimeVersionManagerTest, RuntimeVersionManagerTest,
TestRuntimeVersionManagementUserInterface TestRuntimeVersionManagementUserInterface
} }
import org.enso.runtimeversionmanager.{components, FileSystem, OS}
import org.enso.testkit.OsSpec
class RuntimeVersionManagerSpec extends RuntimeVersionManagerTest { class RuntimeVersionManagerSpec extends RuntimeVersionManagerTest with OsSpec {
"RuntimeVersionManager" should { "RuntimeVersionManager" should {
"find the latest engine version in semver ordering " + "find the latest engine version in semver ordering " +
@ -176,5 +180,139 @@ class RuntimeVersionManagerSpec extends RuntimeVersionManagerTest {
} }
upgradeException.expectedVersion shouldEqual bigVersion upgradeException.expectedVersion shouldEqual bigVersion
} }
"support bundled components" in {
val engineVersion = SemVer(0, 1, 0)
val runtimeVersion = GraalVMVersion(SemVer(1, 0, 0), "11")
prepareBundle(
engines = Seq(engineVersion),
runtimes = Seq(runtimeVersion)
)
val manager = makeRuntimeVersionManager()
val engine = manager.findEngine(engineVersion).value
engine.version shouldEqual engineVersion
engine.isMarkedBroken shouldEqual false
engine.ensureValid()
manager.findGraalRuntime(engine).value.version shouldEqual runtimeVersion
manager.findGraalRuntime(runtimeVersion).value.ensureValid()
}
"fail to uninstall a read-only bundled component" taggedAs OsUnix in {
val engineVersion = SemVer(0, 1, 0)
val runtimeVersion = GraalVMVersion(SemVer(1, 0, 0), "11")
prepareBundle(
engines = Seq(engineVersion),
runtimes = Seq(runtimeVersion)
)
val manager = makeRuntimeVersionManager()
def installedEngines = manager.listInstalledEngines().map(_.version)
def installedRuntimes =
manager.listInstalledGraalRuntimes().map(_.version)
val enginePath = getTestDirectory / "dist" / "0.1.0"
val runtimePath = getTestDirectory / "runtime" / "graalvm-ce-java11-1.0.0"
enginePath.toFile.setWritable(false)
try {
intercept[UninstallationError] {
manager.uninstallEngine(engineVersion)
}
installedEngines shouldEqual Seq(engineVersion)
installedRuntimes shouldEqual Seq(runtimeVersion)
} finally {
enginePath.toFile.setWritable(true)
}
runtimePath.toFile.setWritable(false)
try {
manager.uninstallEngine(engineVersion)
installedEngines shouldEqual Seq()
installedRuntimes shouldEqual Seq(runtimeVersion)
manager.cleanupRuntimes()
installedRuntimes shouldEqual Seq(runtimeVersion)
} finally {
runtimePath.toFile.setWritable(true)
}
manager.cleanupRuntimes()
installedRuntimes shouldEqual Seq()
}
"include both bundled and installed components in list" in {
prepareBundle(
engines = Seq(SemVer(0, 0, 1)),
runtimes = Seq(GraalVMVersion(SemVer(1, 0, 0), "11"))
)
val manager = makeRuntimeVersionManager()
manager.findOrInstallEngine(SemVer(0, 1, 0))
manager
.listInstalledEngines()
.map(_.version) should contain theSameElementsAs Seq(
SemVer(0, 0, 1),
SemVer(0, 1, 0)
)
val runtimeVersions = manager.listInstalledGraalRuntimes().map(_.version)
runtimeVersions.map(_.graalVersion) should contain theSameElementsAs Seq(
SemVer(1, 0, 0),
SemVer(2, 0, 0)
)
runtimeVersions.map(_.java).toSet shouldEqual Set("11")
}
}
private def prepareBundle(
engines: Seq[SemVer],
runtimes: Seq[GraalVMVersion]
): Unit = {
FileSystem.writeTextFile(
getTestDirectory / ".enso.bundle",
"Enso Bundle Marker"
)
for (engineVersion <- engines) {
fakeInstallEngine(getTestDirectory / "dist", engineVersion)
}
for (runtimeVersion <- runtimes) {
fakeInstallRuntime(getTestDirectory / "runtime", runtimeVersion)
}
}
private def fakeInstallEngine(searchPath: Path, version: SemVer): Unit = {
val manifest = """minimum-launcher-version: 0.0.1
|minimum-project-manager-version: 0.0.1
|graal-vm-version: 1.0.0
|graal-java-version: 11""".stripMargin
val root = searchPath / version.toString
Files.createDirectories(root)
FileSystem.writeTextFile(root / "manifest.yaml", manifest)
val components = root / "component"
Files.createDirectories(components)
makePlaceholder(components / "runner.jar")
FileSystem.writeTextFile(components / "runtime.jar", "placeholder")
}
private def fakeInstallRuntime(
searchPath: Path,
version: GraalVMVersion
): Unit = {
val root =
searchPath / s"graalvm-ce-java${version.java}-${version.graalVersion}"
val bin =
if (OS.operatingSystem == OS.MacOS) root / "Contents" / "Home" / "bin"
else root / "bin"
Files.createDirectories(bin)
val executable = if (OS.isWindows) "java.exe" else "java"
makePlaceholder(bin / executable)
}
private def makePlaceholder(path: Path): Unit = {
FileSystem.writeTextFile(path, "placeholder")
path.toFile.setExecutable(true)
} }
} }

View File

@ -1,8 +1,8 @@
package org.enso.runtimeversionmanager.distributuion package org.enso.runtimeversionmanager.distributuion
import java.nio.file.Path import java.nio.file.{Files, Path}
import org.enso.runtimeversionmanager.Environment import org.enso.runtimeversionmanager.{Environment, FileSystem}
import org.enso.runtimeversionmanager.FileSystem.PathSyntax import org.enso.runtimeversionmanager.FileSystem.PathSyntax
import org.enso.runtimeversionmanager.distribution.{ import org.enso.runtimeversionmanager.distribution.{
DistributionManager, DistributionManager,
@ -12,6 +12,7 @@ import org.enso.runtimeversionmanager.test.{
FakeEnvironment, FakeEnvironment,
WithTemporaryDirectory WithTemporaryDirectory
} }
import org.scalatest.OptionValues
import org.scalatest.matchers.should.Matchers import org.scalatest.matchers.should.Matchers
import org.scalatest.wordspec.AnyWordSpec import org.scalatest.wordspec.AnyWordSpec
@ -19,7 +20,8 @@ class DistributionManagerSpec
extends AnyWordSpec extends AnyWordSpec
with Matchers with Matchers
with WithTemporaryDirectory with WithTemporaryDirectory
with FakeEnvironment { with FakeEnvironment
with OptionValues {
"DistributionManager" should { "DistributionManager" should {
"detect portable distribution" in { "detect portable distribution" in {
@ -35,6 +37,7 @@ class DistributionManagerSpec
distributionManager.paths.runtimes shouldEqual distributionManager.paths.runtimes shouldEqual
getTestDirectory / "runtime" getTestDirectory / "runtime"
distributionManager.paths.engines shouldEqual getTestDirectory / "dist" distributionManager.paths.engines shouldEqual getTestDirectory / "dist"
distributionManager.paths.bundle shouldEqual None
} }
"detect installed distribution" in { "detect installed distribution" in {
@ -45,6 +48,21 @@ class DistributionManagerSpec
val distributionManager = new PortableDistributionManager(fakeEnvironment) val distributionManager = new PortableDistributionManager(fakeEnvironment)
distributionManager.isRunningPortable shouldEqual false distributionManager.isRunningPortable shouldEqual false
distributionManager.paths.bundle shouldEqual None
}
"detect bundles" in {
val executable = fakeExecutablePath()
FileSystem.writeTextFile(getTestDirectory / ".enso.bundle", "placeholder")
val fakeEnvironment = new Environment {
override def getPathToRunningExecutable: Path = executable
}
val distributionManager = new PortableDistributionManager(fakeEnvironment)
val bundle = distributionManager.paths.bundle.value
assert(Files.isSameFile(bundle.engines, getTestDirectory / "dist"))
assert(Files.isSameFile(bundle.runtimes, getTestDirectory / "runtime"))
} }
"respect environment variable overrides " + "respect environment variable overrides " +

View File

@ -65,3 +65,7 @@ case class UpgradeRequiredError(
s"Minimum version required to use this engine is " + s"Minimum version required to use this engine is " +
s"$expectedVersion." s"$expectedVersion."
) )
/** Indicates uninstallation failure. */
case class UninstallationError(message: String)
extends ComponentsException(message)

View File

@ -48,14 +48,14 @@ case class Engine(version: SemVer, path: Path, manifest: Manifest) {
Failure( Failure(
CorruptedComponentError( CorruptedComponentError(
s"Engine's runner.jar (expected at " + s"Engine's runner.jar (expected at " +
s"${runnerPath.toAbsolutePath.normalize} is missing." s"`${runnerPath.toAbsolutePath.normalize}`) is missing."
) )
) )
else if (!Files.exists(runtimePath)) else if (!Files.exists(runtimePath))
Failure( Failure(
CorruptedComponentError( CorruptedComponentError(
s"Engine's runtime.jar (expected at " + s"`Engine's runtime.jar (expected at " +
s"${runtimePath.toAbsolutePath.normalize} is missing." s"${runtimePath.toAbsolutePath.normalize}`) is missing."
) )
) )
else Success(()) else Success(())

View File

@ -43,7 +43,7 @@ case class GraalRuntime(version: GraalVMVersion, path: Path) {
Failure( Failure(
CorruptedComponentError( CorruptedComponentError(
s"Runtime's java executable (expected at " + s"Runtime's java executable (expected at " +
s"${javaExecutable.toAbsolutePath.normalize}) is missing." s"`${javaExecutable.toAbsolutePath.normalize}`) is missing."
) )
) )
else if (!Files.isExecutable(javaExecutable)) else if (!Files.isExecutable(javaExecutable))

View File

@ -60,13 +60,11 @@ class RuntimeVersionManager(
*/ */
def findGraalRuntime(version: GraalVMVersion): Option[GraalRuntime] = { def findGraalRuntime(version: GraalVMVersion): Option[GraalRuntime] = {
val name = graalRuntimeNameForVersion(version) val name = graalRuntimeNameForVersion(version)
val path = distributionManager.paths.runtimes / name firstExisting(distributionManager.paths.runtimeSearchPaths.map(_ / name))
if (Files.exists(path)) { .map { path =>
// TODO [RW] for now an exception is thrown if the installation is // TODO [RW] for now an exception is thrown if the installation is
// corrupted, in #1052 offer to repair the broken installation // corrupted, in #1052 offer to repair the broken installation
loadGraalRuntime(path) loadGraalRuntime(path).recoverWith { case e: Exception =>
.map(Some(_))
.recoverWith { case e: Exception =>
Failure( Failure(
UnrecognizedComponentError( UnrecognizedComponentError(
s"The runtime $version is already installed, but cannot be " + s"The runtime $version is already installed, but cannot be " +
@ -77,9 +75,8 @@ class RuntimeVersionManager(
e e
) )
) )
} }.get
.get }
} else None
} }
/** Executes the provided action with a requested engine version. /** Executes the provided action with a requested engine version.
@ -187,16 +184,21 @@ class RuntimeVersionManager(
} }
} }
/** Returns the first path from the sequence that exists on the file system,
* or None if no path from the sequence exists.
*/
private def firstExisting(paths: Seq[Path]): Option[Path] =
paths.find(Files.exists(_))
/** Finds an installed engine with the given `version` and reports any errors. /** Finds an installed engine with the given `version` and reports any errors.
*/ */
private def getEngine(version: SemVer): Try[Engine] = { private def getEngine(version: SemVer): Try[Engine] = {
val name = engineNameForVersion(version) val name = engineNameForVersion(version)
val path = distributionManager.paths.engines / name firstExisting(distributionManager.paths.engineSearchPaths.map(_ / name))
if (Files.exists(path)) { .map(loadEngine)
// TODO [RW] right now we return an exception, in the future (#1052) we .getOrElse {
// will try recovery Failure(ComponentMissingError(s"Engine $version is not installed."))
loadEngine(path) }
} else Failure(ComponentMissingError(s"Engine $version is not installed."))
} }
/** Finds an engine with the given `version` or returns None if it is not /** Finds an engine with the given `version` or returns None if it is not
@ -224,9 +226,10 @@ class RuntimeVersionManager(
Failure( Failure(
UnrecognizedComponentError( UnrecognizedComponentError(
s"The engine $version is already installed, but cannot be " + s"The engine $version is already installed, but cannot be " +
s"loaded due to $e. Until the launcher gets an auto-repair " + s"loaded due to $e " +
s"feature, please try running `enso uninstall engine $version` " + s"Please try reinstalling by running " +
s"followed by `enso install engine $version`.", s"`enso uninstall engine $version` followed by " +
s"`enso install engine $version`.",
e e
) )
) )
@ -270,21 +273,38 @@ class RuntimeVersionManager(
/** Lists all installed GrallVM runtimes. */ /** Lists all installed GrallVM runtimes. */
def listInstalledGraalRuntimes(): Seq[GraalRuntime] = def listInstalledGraalRuntimes(): Seq[GraalRuntime] =
FileSystem findComponents(distributionManager.paths.runtimeSearchPaths)
.listDirectory(distributionManager.paths.runtimes)
.filter(isNotIgnoredDirectory)
.map(path => (path, loadGraalRuntime(path))) .map(path => (path, loadGraalRuntime(path)))
.flatMap(handleErrorsAsWarnings[GraalRuntime]("A runtime")) .flatMap(handleErrorsAsWarnings[GraalRuntime]("A runtime"))
/** Lists all installed engines. */ /** Lists all installed engines. */
def listInstalledEngines(): Seq[Engine] = { def listInstalledEngines(): Seq[Engine] = {
FileSystem findComponents(distributionManager.paths.engineSearchPaths)
.listDirectory(distributionManager.paths.engines)
.filter(isNotIgnoredDirectory)
.map(path => (path, loadEngine(path))) .map(path => (path, loadEngine(path)))
.flatMap(handleErrorsAsWarnings[Engine]("An engine")) .flatMap(handleErrorsAsWarnings[Engine]("An engine"))
} }
/** Returns components found in `searchPaths`.
*
* If there are duplicate components in multiple paths, the one from the
* earliest search path is kept.
*/
private def findComponents(searchPaths: Seq[Path]): Seq[Path] =
searchPaths
.foldLeft(Map.empty[String, Path]) { case (map, searchPath) =>
val componentsHere =
FileSystem.listDirectory(searchPath).filter(isNotIgnoredDirectory)
componentsHere.foldLeft(map) { case (map, componentPath) =>
val componentName = componentPath.getFileName.toString
map.updatedWith(componentName) {
case Some(alreadyPresent) => Some(alreadyPresent)
case None => Some(componentPath)
}
}
}
.values
.toSeq
private def isNotIgnoredDirectory(path: Path): Boolean = { private def isNotIgnoredDirectory(path: Path): Boolean = {
val fileName = path.getFileName.toString val fileName = path.getFileName.toString
val isIgnored = FileSystem.ignoredFileNames.contains(fileName) val isIgnored = FileSystem.ignoredFileNames.contains(fileName)
@ -328,11 +348,32 @@ class RuntimeVersionManager(
throw ComponentMissingError(s"Enso Engine $version is not installed.") throw ComponentMissingError(s"Enso Engine $version is not installed.")
} }
if (!Files.isWritable(engine.path)) {
val message =
s"$engine cannot be uninstalled because it is placed in a " +
s"read-only location (bundled versions cannot be uninstalled)."
logger.error(message)
throw UninstallationError(message)
}
safelyRemoveComponent(engine.path) safelyRemoveComponent(engine.path)
userInterface.logInfo(s"Uninstalled $engine.") userInterface.logInfo(s"Uninstalled $engine.")
cleanupGraalRuntimes() internalCleanupGraalRuntimes()
} }
/** Removes runtimes that are not used by any installed engines.
*
* Runtimes are automatically cleaned after installation, so currently this
* function is only useful for tests.
*/
def cleanupRuntimes(): Unit = {
resourceManager.withResources(
userInterface,
Resource.AddOrRemoveComponents -> LockType.Exclusive
) {
internalCleanupGraalRuntimes()
}
}
/** Checks if the component version specified in the release's manifest is /** Checks if the component version specified in the release's manifest is
* compatible with the current installer version. * compatible with the current installer version.
*/ */
@ -690,14 +731,21 @@ class RuntimeVersionManager(
* *
* The caller must hold [[Resource.AddOrRemoveComponents]] exclusively. * The caller must hold [[Resource.AddOrRemoveComponents]] exclusively.
*/ */
private def cleanupGraalRuntimes(): Unit = { private def internalCleanupGraalRuntimes(): Unit = {
for (runtime <- listInstalledGraalRuntimes()) { for (runtime <- listInstalledGraalRuntimes()) {
if (findEnginesUsingRuntime(runtime).isEmpty) { if (findEnginesUsingRuntime(runtime).isEmpty) {
userInterface.logInfo( userInterface.logInfo(
s"Removing $runtime, because it is not used by any installed Enso " + s"Removing $runtime, because it is not used by any installed Enso " +
s"versions." s"versions."
) )
safelyRemoveComponent(runtime.path) if (Files.isWritable(runtime.path)) {
safelyRemoveComponent(runtime.path)
} else {
logger.warn(
s"$runtime cannot be uninstalled because it is placed in a " +
s"read-only location."
)
}
} }
} }
} }

View File

@ -14,9 +14,12 @@ import scala.util.control.NonFatal
* @param dataRoot the root of the data directory; for a portable distribution * @param dataRoot the root of the data directory; for a portable distribution
* this is the root of the distribution, for a locally * this is the root of the distribution, for a locally
* installed distribution, it corresponds to `ENSO_DATA_DIR` * installed distribution, it corresponds to `ENSO_DATA_DIR`
* @param runtimes location of runtimes, corresponding to `runtime` directory * @param runtimes primary location of runtimes, corresponding to `runtime`
* @param engines location of engine versions, corresponding to `dist` * directory
* @param engines primary location of engine versions, corresponding to `dist`
* directory * directory
* @param bundle optional bundle description, containing secondary engine and
* runtime directories
* @param config location of configuration * @param config location of configuration
* @param locks a directory for storing lockfiles that are used to synchronize * @param locks a directory for storing lockfiles that are used to synchronize
* access to the various components * access to the various components
@ -29,6 +32,7 @@ case class DistributionPaths(
dataRoot: Path, dataRoot: Path,
runtimes: Path, runtimes: Path,
engines: Path, engines: Path,
bundle: Option[Bundle],
config: Path, config: Path,
locks: Path, locks: Path,
logs: Path, logs: Path,
@ -42,12 +46,36 @@ case class DistributionPaths(
| dataRoot = $dataRoot, | dataRoot = $dataRoot,
| runtimes = $runtimes, | runtimes = $runtimes,
| engines = $engines, | engines = $engines,
| bundle = $bundle,
| config = $config, | config = $config,
| locks = $locks, | locks = $locks,
| tmp = $unsafeTemporaryDirectory | tmp = $unsafeTemporaryDirectory
|)""".stripMargin |)""".stripMargin
/** Sequence of paths to search for engine installations, in order of
* precedence.
*/
def engineSearchPaths: Seq[Path] = Seq(engines) ++ bundle.map(_.engines).toSeq
/** Sequence of paths to search for runtime installations, in order of
* precedence.
*/
def runtimeSearchPaths: Seq[Path] =
Seq(runtimes) ++ bundle.map(_.runtimes).toSeq
} }
/** Paths to secondary directories for additionally bundled engine
* distributions.
*
* These paths are only relevant for an installed distribution which may also
* use some locally bundled distributions (possibly located on a read-only
* filesystem).
*
* For portable distributions, bundled packages are already included in the
* primary directory.
*/
case class Bundle(engines: Path, runtimes: Path)
/** A helper class that encapsulates management of paths to components of the /** A helper class that encapsulates management of paths to components of the
* distribution. * distribution.
* *
@ -82,6 +110,7 @@ class DistributionManager(val env: Environment) {
dataRoot = dataRoot, dataRoot = dataRoot,
runtimes = dataRoot / RUNTIMES_DIRECTORY, runtimes = dataRoot / RUNTIMES_DIRECTORY,
engines = dataRoot / ENGINES_DIRECTORY, engines = dataRoot / ENGINES_DIRECTORY,
bundle = detectBundle(),
config = configRoot, config = configRoot,
locks = runRoot / LOCK_DIRECTORY, locks = runRoot / LOCK_DIRECTORY,
logs = LocallyInstalledDirectories.logDirectory, logs = LocallyInstalledDirectories.logDirectory,
@ -89,6 +118,32 @@ class DistributionManager(val env: Environment) {
) )
} }
/** Name of the file that should be placed in the distribution root to mark it
* as running in portable mode.
*/
private val BUNDLE_MARK_FILENAME = ".enso.bundle"
/** Root directory of a bundle.
*
* If the bundle is present, it will be located next to the `bin/` directory
* that contains the executable that we are currently running.
*/
private def possibleBundleRoot =
env.getPathToRunningExecutable.getParent.getParent
/** Checks if [[possibleBundleRoot]] contains the bundle mark file and returns
* directories for the bundle if it was found.
*/
private def detectBundle(): Option[Bundle] =
if (Files.exists(possibleBundleRoot / BUNDLE_MARK_FILENAME))
Some(
Bundle(
engines = possibleBundleRoot / ENGINES_DIRECTORY,
runtimes = possibleBundleRoot / RUNTIMES_DIRECTORY
)
)
else None
/** Removes unused lockfiles. /** Removes unused lockfiles.
*/ */
def tryCleaningUnusedLockfiles(): Unit = { def tryCleaningUnusedLockfiles(): Unit = {

View File

@ -59,6 +59,11 @@ class PortableDistributionManager(env: Environment)
portable portable
} }
/** Detects paths for the portable distribution.
*
* A portable distribution does not include bundle paths, because if
* anything was bundled with it, it is already part of its primary installation.
*/
override protected def detectPaths(): DistributionPaths = override protected def detectPaths(): DistributionPaths =
if (isRunningPortable) { if (isRunningPortable) {
val root = env.getPathToRunningExecutable.getParent.getParent val root = env.getPathToRunningExecutable.getParent.getParent
@ -66,6 +71,7 @@ class PortableDistributionManager(env: Environment)
dataRoot = root, dataRoot = root,
runtimes = root / RUNTIMES_DIRECTORY, runtimes = root / RUNTIMES_DIRECTORY,
engines = root / ENGINES_DIRECTORY, engines = root / ENGINES_DIRECTORY,
bundle = None,
config = root / CONFIG_DIRECTORY, config = root / CONFIG_DIRECTORY,
locks = root / LOCK_DIRECTORY, locks = root / LOCK_DIRECTORY,
logs = root / LOG_DIRECTORY, logs = root / LOG_DIRECTORY,

View File

@ -1,10 +1,7 @@
package org.enso.runtimeversionmanager.releases.engine package org.enso.runtimeversionmanager.releases.engine
import java.nio.file.Path
import org.enso.runtimeversionmanager.releases.ReleaseProvider import org.enso.runtimeversionmanager.releases.ReleaseProvider
import org.enso.runtimeversionmanager.releases.github.GithubReleaseProvider import org.enso.runtimeversionmanager.releases.github.GithubReleaseProvider
import org.enso.runtimeversionmanager.releases.local.LocalReleaseProvider
/** Represents the default Enso repository providing releases of the engine. */ /** Represents the default Enso repository providing releases of the engine. */
object EngineRepository { object EngineRepository {
@ -13,15 +10,4 @@ object EngineRepository {
/** Default provider of engine releases. */ /** Default provider of engine releases. */
def defaultEngineReleaseProvider: ReleaseProvider[EngineRelease] = def defaultEngineReleaseProvider: ReleaseProvider[EngineRelease] =
new EngineReleaseProvider(githubRepository) new EngineReleaseProvider(githubRepository)
/** Creates an engine provider that uses a local repository first, falling
* back to the default one.
*/
def fromLocalRepository(
releaseDirectory: Path
): ReleaseProvider[EngineRelease] = {
val mergedRepository =
new LocalReleaseProvider(releaseDirectory, githubRepository)
new EngineReleaseProvider(mergedRepository)
}
} }

View File

@ -6,7 +6,6 @@ import org.enso.cli.task.TaskProgress
import org.enso.runtimeversionmanager.OS import org.enso.runtimeversionmanager.OS
import org.enso.runtimeversionmanager.components.GraalVMVersion import org.enso.runtimeversionmanager.components.GraalVMVersion
import org.enso.runtimeversionmanager.releases.github.GithubReleaseProvider import org.enso.runtimeversionmanager.releases.github.GithubReleaseProvider
import org.enso.runtimeversionmanager.releases.local.LocalReleaseProvider
import org.enso.runtimeversionmanager.releases.{ import org.enso.runtimeversionmanager.releases.{
ReleaseProviderException, ReleaseProviderException,
SimpleReleaseProvider SimpleReleaseProvider
@ -59,17 +58,6 @@ object GraalCEReleaseProvider {
*/ */
val default = new GraalCEReleaseProvider(githubRepository) val default = new GraalCEReleaseProvider(githubRepository)
/** Creates a GraalVM provider that uses a local repository first, falling
* back to the default one.
*/
def fromLocalRepository(
releaseDirectory: Path
): GraalVMRuntimeReleaseProvider = {
val mergedRepository =
new LocalReleaseProvider(releaseDirectory, githubRepository)
new GraalCEReleaseProvider(mergedRepository)
}
/** Generates the name of the package for the currently running OS and a /** Generates the name of the package for the currently running OS and a
* specified release version. * specified release version.
*/ */

View File

@ -1,112 +0,0 @@
package org.enso.runtimeversionmanager.releases.local
import java.nio.file.Path
import cats.syntax.traverse._
import com.typesafe.scalalogging.Logger
import org.enso.cli.task.TaskProgress
import org.enso.runtimeversionmanager.FileSystem
import org.enso.runtimeversionmanager.releases.{
Asset,
Release,
SimpleReleaseProvider
}
import scala.io.Source
import scala.util.{Try, Using}
/** A [[SimpleReleaseProvider]] that uses a repository located on a local file
* system as its primary source and falls back to some other specified
* repository in other cases.
*
* It can be used to implement bundling some versions with an installer - it
* can point to this local repository, so that any required bundled versions
* are installed from the bundle and any other versions are handled using the
* default repository.
*
* It is given a `releaseDirectory` that should contain separate directories
* for each local release. The name of each subdirectory corresponds to its
* release tag and every file in that subdirectory is considered as an asset of
* that release.
*
* It loads the list of releases at construction and thus may throw an error if
* it cannot access the provided directory.
*/
class LocalReleaseProvider(
releaseDirectory: Path,
fallback: SimpleReleaseProvider
) extends SimpleReleaseProvider {
private val logger = Logger[LocalReleaseProvider]
private val localDirectories: Seq[Path] =
FileSystem.listDirectory(releaseDirectory).filter { dir =>
val isIgnoredFile =
FileSystem.ignoredFileNames.contains(dir.getFileName.toString)
!isIgnoredFile
}
/** @inheritdoc */
override def releaseForTag(tag: String): Try[Release] = {
val localPath = localDirectories.find(_.getFileName.toString == tag)
localPath
.map(wrapLocalDirectory)
.getOrElse { fallback.releaseForTag(tag) }
}
/** @inheritdoc */
override def listReleases(): Try[Seq[Release]] = {
val remote = fallback
.listReleases()
.recover { error =>
logger.warn(
s"The remote release provider failed with $error, but " +
s"locally bundled releases are available."
)
Seq.empty
}
.get
findLocalReleases() map { local =>
val localTags = local.map(_.tag).toSet
val remoteDeduplicated = remote.filter { remoteRelease =>
val hasLocalCorrespondent = localTags.contains(remoteRelease.tag)
!hasLocalCorrespondent
}
local ++ remoteDeduplicated
}
}
/** An asset that is on the local filesystem. */
private case class LocalAsset(assetPath: Path) extends Asset {
/** @inheritdoc */
override def fileName: String = assetPath.getFileName.toString
/** @inheritdoc */
override def downloadTo(path: Path): TaskProgress[Unit] =
TaskProgress.runImmediately {
FileSystem.copyFile(assetPath, path)
}
/** @inheritdoc */
override def fetchAsText(): TaskProgress[String] =
TaskProgress.fromTry {
Using(Source.fromFile(assetPath.toFile)) { src =>
src.getLines().mkString("\n")
}
}
}
private case class LocalRelease(
override val tag: String,
override val assets: Seq[LocalAsset]
) extends Release
/** Creates a [[LocalRelease]] defined by a local directory. */
private def wrapLocalDirectory(path: Path): Try[Release] = Try {
val tag = path.getFileName.toString
val assets = FileSystem.listDirectory(path).map(LocalAsset)
LocalRelease(tag, assets)
}
private def findLocalReleases(): Try[Seq[Release]] =
localDirectories.map(wrapLocalDirectory).toList.sequence[Try, Release]
}

View File

@ -1,7 +1,10 @@
import sbt.{file, singleFileFinder, File, IO} import sbt.internal.util.ManagedLogger
import sbt._
import sbt.io.syntax.fileToRichFile import sbt.io.syntax.fileToRichFile
import sbt.util.{CacheStore, CacheStoreFactory, FileInfo, Tracked} import sbt.util.{CacheStore, CacheStoreFactory, FileInfo, Tracked}
import scala.sys.process._
object DistributionPackage { object DistributionPackage {
def copyDirectoryIncremental( def copyDirectoryIncremental(
source: File, source: File,
@ -144,4 +147,315 @@ object DistributionPackage {
cacheFactory.make("launcher-rootfiles") cacheFactory.make("launcher-rootfiles")
) )
} }
sealed trait OS {
def name: String
def graalName: String = name
def executableName(base: String): String = base
def archiveExt: String = ".tar.gz"
def isUNIX: Boolean = true
}
object OS {
case object Linux extends OS {
override def name: String = "linux"
}
case object MacOS extends OS {
override def name: String = "macos"
override def graalName: String = "darwin"
}
case object Windows extends OS {
override def name: String = "windows"
override def executableName(base: String): String = base + ".exe"
override def archiveExt: String = ".zip"
override def isUNIX: Boolean = false
}
val platforms = Seq(Linux, MacOS, Windows)
}
sealed trait Architecture {
def name: String
}
object Architecture {
case object X64 extends Architecture {
override def name: String = "amd64"
}
val archs = Seq(X64)
}
/** A helper class that manages building distribution artifacts. */
class Builder(
ensoVersion: String,
graalVersion: String,
graalJavaVersion: String,
artifactRoot: File
) {
def artifactName(
component: String,
os: OS,
architecture: Architecture
): String =
s"enso-$component-$ensoVersion-${os.name}-${architecture.name}"
def graalInPackageName: String =
s"graalvm-ce-java$graalJavaVersion-$graalVersion"
private def extractZip(archive: File, root: File): Unit = {
IO.createDirectory(root)
val exitCode = Process(
Seq("unzip", "-q", archive.toPath.toAbsolutePath.normalize.toString),
cwd = Some(root)
).!
if (exitCode != 0) {
throw new RuntimeException(s"Cannot extract $archive.")
}
}
private def extractTarGz(archive: File, root: File): Unit = {
IO.createDirectory(root)
val exitCode = Process(
Seq(
"tar",
"xf",
archive.toPath.toAbsolutePath.toString
),
cwd = Some(root)
).!
if (exitCode != 0) {
throw new RuntimeException(s"Cannot extract $archive.")
}
}
private def extract(archive: File, root: File): Unit = {
if (archive.getName.endsWith("zip")) {
extractZip(archive, root)
} else {
extractTarGz(archive, root)
}
}
def copyGraal(
log: ManagedLogger,
os: OS,
architecture: Architecture,
runtimeDir: File
): Unit = {
val packageName = s"graalvm-${os.name}-${architecture.name}-" +
s"$graalVersion-$graalJavaVersion"
val root = artifactRoot / packageName
if (!root.exists()) {
log.info(
s"Downloading GraalVM $graalVersion Java $graalJavaVersion " +
s"for $os $architecture"
)
val graalUrl =
s"https://github.com/graalvm/graalvm-ce-builds/releases/download/" +
s"vm-$graalVersion/" +
s"graalvm-ce-java$graalJavaVersion-${os.graalName}-" +
s"${architecture.name}-$graalVersion${os.archiveExt}"
val archive = artifactRoot / (packageName + os.archiveExt)
val exitCode = (url(graalUrl) #> archive).!
if (exitCode != 0) {
throw new RuntimeException(s"Graal download from $graalUrl failed.")
}
extract(archive, root)
}
IO.copyDirectory(
root / graalInPackageName,
runtimeDir / graalInPackageName
)
}
def copyEngine(os: OS, architecture: Architecture, distDir: File): Unit = {
val engine = builtArtifact("engine", os, architecture)
if (!engine.exists()) {
throw new IllegalStateException(
s"Cannot create bundle for $os / $architecture because corresponding " +
s"engine has not been built."
)
}
IO.copyDirectory(engine / s"enso-$ensoVersion", distDir / ensoVersion)
}
def makeExecutable(file: File): Unit = {
val ownerOnly = false
file.setExecutable(true, ownerOnly)
}
def fixLauncher(root: File, os: OS): Unit = {
makeExecutable(root / "enso" / "bin" / os.executableName("enso"))
IO.createDirectories(
Seq("dist", "config", "runtime").map(root / "enso" / _)
)
}
def makeArchive(root: File, rootDir: String, target: File): Unit = {
val exitCode = if (target.getName.endsWith("zip")) {
Process(
Seq(
"zip",
"-q",
"-r",
target.toPath.toAbsolutePath.normalize.toString,
rootDir
),
cwd = Some(root)
).!
} else {
Process(
Seq(
"tar",
"-czf",
target.toPath.toAbsolutePath.normalize.toString,
rootDir
),
cwd = Some(root)
).!
}
if (exitCode != 0) {
throw new RuntimeException(s"Failed to create archive $target")
}
}
/** Path to an arbitrary built artifact. */
def builtArtifact(
component: String,
os: OS,
architecture: Architecture
): File = artifactRoot / artifactName(component, os, architecture)
/** Path to the artifact that is built on this local machine. */
def localArtifact(component: String): File = {
val architecture = Architecture.X64
val os =
if (Platform.isWindows) OS.Windows
else if (Platform.isLinux) OS.Linux
else if (Platform.isMacOS) OS.MacOS
else throw new IllegalStateException("Unknown OS")
artifactRoot / artifactName(component, os, architecture)
}
/** Path to a built archive.
*
* These archives are built by [[makePackages]] and [[makeBundles]].
*/
def builtArchive(
component: String,
os: OS,
architecture: Architecture
): File =
artifactRoot / (artifactName(
component,
os,
architecture
) + os.archiveExt)
/** Creates compressed and ready for release packages for the launcher and
* engine.
*
* A project manager package is not created, as we release only its bundle.
* See [[makeBundles]].
*
* It does not trigger any builds. Instead, it uses available artifacts
* placed in `artifactRoot`. These artifacts may be created using the
* `enso/build*Distribution` tasks or they may come from other workers (as
* is the case in the release CI where the artifacts are downloaded from
* other jobs).
*/
def makePackages = Command.command("makePackages") { state =>
val log = state.log
for {
os <- OS.platforms
arch <- Architecture.archs
} {
val launcher = builtArtifact("launcher", os, arch)
if (launcher.exists()) {
fixLauncher(launcher, os)
val archive = builtArchive("launcher", os, arch)
makeArchive(launcher, "enso", archive)
log.info(s"Created $archive")
}
val engine = builtArtifact("engine", os, arch)
if (engine.exists()) {
if (os.isUNIX) {
makeExecutable(engine / s"enso-$ensoVersion" / "bin" / "enso")
}
val archive = builtArchive("engine", os, arch)
makeArchive(engine, s"enso-$ensoVersion", archive)
log.info(s"Created $archive")
}
}
state
}
private def cleanDirectory(dir: File): Unit = {
for (f <- IO.listFiles(dir)) {
IO.delete(f)
}
}
/** Creates launcher and project-manager bundles that include the component
* itself, the engine and a Graal runtime.
*
* It will download the GraalVM runtime and cache it in `artifactRoot` so
* further invocations for the same version will not need to download it.
*
* It does not trigger any builds. Instead, it uses available artifacts
* placed in `artifactRoot`. These artifacts may be created using the
* `enso/build*Distribution` tasks or they may come from other workers (as
* is the case in the release CI where the artifacts are downloaded from
* other jobs).
*/
def makeBundles = Command.command("makeBundles") { state =>
val log = state.log
for {
os <- OS.platforms
arch <- Architecture.archs
} {
val launcher = builtArtifact("launcher", os, arch)
if (launcher.exists()) {
fixLauncher(launcher, os)
copyEngine(os, arch, launcher / "enso" / "dist")
copyGraal(log, os, arch, launcher / "enso" / "runtime")
val archive = builtArchive("bundle", os, arch)
makeArchive(launcher, "enso", archive)
cleanDirectory(launcher / "enso" / "dist")
cleanDirectory(launcher / "enso" / "runtime")
log.info(s"Created $archive")
}
val pm = builtArtifact("project-manager", os, arch)
if (pm.exists()) {
if (os.isUNIX) {
makeExecutable(pm / "enso" / "bin" / "project-manager")
}
copyEngine(os, arch, pm / "enso" / "dist")
copyGraal(log, os, arch, pm / "enso" / "runtime")
IO.copyFile(
file("distribution/enso.bundle.template"),
pm / "enso" / ".enso.bundle"
)
val archive = builtArchive("project-manager", os, arch)
makeArchive(pm, "enso", archive)
cleanDirectory(pm / "enso" / "dist")
cleanDirectory(pm / "enso" / "runtime")
log.info(s"Created $archive")
}
}
state
}
}
} }

View File

@ -1,14 +1,21 @@
#!/usr/bin/env bash #!/usr/bin/env bash
DIST_ARCH=amd64 DIST_ARCH=amd64
LAUNCHER_DIST_ROOT=enso-launcher-$DIST_VERSION-$DIST_OS-$DIST_ARCH BUILD_ROOT=built-distribution
LAUNCHER_DIST_NAME=enso-launcher-$DIST_VERSION-$DIST_OS-$DIST_ARCH
LAUNCHER_DIST_ROOT=$BUILD_ROOT/$LAUNCHER_DIST_NAME
LAUNCHER_DIST_DIR=$LAUNCHER_DIST_ROOT/enso LAUNCHER_DIST_DIR=$LAUNCHER_DIST_ROOT/enso
ENGINE_DIST_ROOT=enso-engine-$DIST_VERSION-$DIST_OS-$DIST_ARCH ENGINE_DIST_NAME=enso-engine-$DIST_VERSION-$DIST_OS-$DIST_ARCH
ENGINE_DIST_ROOT=$BUILD_ROOT/$ENGINE_DIST_NAME
ENGINE_DIST_DIR=$ENGINE_DIST_ROOT/enso-$DIST_VERSION ENGINE_DIST_DIR=$ENGINE_DIST_ROOT/enso-$DIST_VERSION
PROJECTMANAGER_DIST_ROOT=enso-project-manager-$DIST_VERSION-$DIST_OS-$DIST_ARCH PROJECTMANAGER_DIST_NAME=enso-project-manager-$DIST_VERSION-$DIST_OS-$DIST_ARCH
PROJECTMANAGER_DIST_ROOT=$BUILD_ROOT/$PROJECTMANAGER_DIST_NAME
PROJECTMANAGER_DIST_DIR=$PROJECTMANAGER_DIST_ROOT/enso PROJECTMANAGER_DIST_DIR=$PROJECTMANAGER_DIST_ROOT/enso
echo "LAUNCHER_DIST_NAME=$LAUNCHER_DIST_NAME" >> $GITHUB_ENV
echo "LAUNCHER_DIST_DIR=$LAUNCHER_DIST_DIR" >> $GITHUB_ENV echo "LAUNCHER_DIST_DIR=$LAUNCHER_DIST_DIR" >> $GITHUB_ENV
echo "LAUNCHER_DIST_ROOT=$LAUNCHER_DIST_ROOT" >> $GITHUB_ENV echo "LAUNCHER_DIST_ROOT=$LAUNCHER_DIST_ROOT" >> $GITHUB_ENV
echo "ENGINE_DIST_NAME=$ENGINE_DIST_NAME" >> $GITHUB_ENV
echo "ENGINE_DIST_DIR=$ENGINE_DIST_DIR" >> $GITHUB_ENV echo "ENGINE_DIST_DIR=$ENGINE_DIST_DIR" >> $GITHUB_ENV
echo "ENGINE_DIST_ROOT=$ENGINE_DIST_ROOT" >> $GITHUB_ENV echo "ENGINE_DIST_ROOT=$ENGINE_DIST_ROOT" >> $GITHUB_ENV
echo "PROJECTMANAGER_DIST_NAME=$PROJECTMANAGER_DIST_NAME" >> $GITHUB_ENV
echo "PROJECTMANAGER_DIST_DIR=$PROJECTMANAGER_DIST_DIR" >> $GITHUB_ENV echo "PROJECTMANAGER_DIST_DIR=$PROJECTMANAGER_DIST_DIR" >> $GITHUB_ENV
echo "PROJECTMANAGER_DIST_ROOT=$PROJECTMANAGER_DIST_ROOT" >> $GITHUB_ENV echo "PROJECTMANAGER_DIST_ROOT=$PROJECTMANAGER_DIST_ROOT" >> $GITHUB_ENV