Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ jobs:
- uses: VirtusLab/scala-cli-setup@v1.9.1
with:
jvm: graalvm-java24:24.0.2
apps: 'sbt:1.11.7' # pinned: sbt 2.x breaks BesomPlugin.scala (see language-plugin/resources)
apps: 'sbt:2.0.6'
power: true
- uses: arduino/setup-protoc@v1
- uses: actions/setup-go@v4
Expand Down
8 changes: 4 additions & 4 deletions .github/workflows/test-website.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,15 +22,15 @@ jobs:
- uses: actions/setup-node@v3
with:
node-version: 24
cache: yarn
cache-dependency-path: '**/package-lock.json'
cache: npm
cache-dependency-path: website/package-lock.json
- uses: coursier/cache-action@v6
- uses: coursier/setup-action@v1
with:
apps: mdoc
- name: Install dependencies
run: yarn install --frozen-lockfile
run: npm ci
- name: Test build website
run: yarn build
run: npm run build
- name: Run mdoc
run: just test-markdown
8 changes: 4 additions & 4 deletions .github/workflows/website.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,13 @@ jobs:
- uses: actions/setup-node@v3
with:
node-version: 24
cache: yarn
cache-dependency-path: '**/package-lock.json'
cache: npm
cache-dependency-path: website/package-lock.json

- name: Install dependencies
run: yarn install --frozen-lockfile
run: npm ci
- name: Build website
run: yarn build
run: npm run build

# Popular action to deploy to GitHub Pages:
# Docs: https://github.com/peaceiris/actions-gh-pages#%EF%B8%8F-docusaurus
Expand Down
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -28,4 +28,5 @@ src/main/scala/besom/rpc
/integration-tests/resources/**/project.scala
integration-tests/resources/executors/gradle/build.gradle.kts
integration-tests/resources/executors/maven/pom.xml
integration-tests/resources/executors/sbt/build.sbt
integration-tests/resources/executors/sbt1/build.sbt
integration-tests/resources/executors/sbt2/build.sbt
4 changes: 2 additions & 2 deletions Justfile
Original file line number Diff line number Diff line change
Expand Up @@ -283,7 +283,7 @@ install-language-plugin: build-language-plugin
mkdir -p $output_dir
cp {{language-plugin-output-dir}}/bootstrap.jar $output_dir/
cp {{language-plugin-output-dir}}/pulumi-language-scala $output_dir/
cp {{language-plugin-resources-dir}}/* $output_dir/
cp -R {{language-plugin-resources-dir}}/* $output_dir/
pulumi --non-interactive --logtostderr plugin rm language scala -y
pulumi --non-interactive --logtostderr plugin install language scala {{besom-version}} --file {{language-plugin-output-dir}}/local

Expand All @@ -300,7 +300,7 @@ package-language-plugin $GOOS $GOARCH:
binary_name="pulumi-language-scala.exe"
fi
cp {{language-plugin-output-dir}}/$binary_name $output_dir/
cp {{language-plugin-resources-dir}}/* $output_dir/
cp -R {{language-plugin-resources-dir}}/* $output_dir/
cd $output_dir
tar czvf pulumi-language-scala-v{{besom-version}}-{{GOOS}}-{{GOARCH}}.tar.gz *

Expand Down
35 changes: 31 additions & 4 deletions integration-tests/LanguagePluginTest.test.scala
Original file line number Diff line number Diff line change
Expand Up @@ -31,13 +31,22 @@ class LanguagePluginTest extends munit.FunSuite {
|//> using dep "org.virtuslab::besom-fake-external-resource:2.3.4-TEST"
|""".stripMargin

// The two sbt lines besom supports. sbt compiles the meta-build (the `project` directory) with
// Scala 2.12 on the 1.x line and with Scala 3 on the 2.x line, so each needs its own flavour of
// BesomPlugin.scala - see language-plugin/resources. Both are exercised below to keep the
// language plugin honest about shipping a working variant for either line.
val sbt1Version = "1.11.7"
val sbt2Version = "2.0.6"

// Uses the slash syntax throughout - the sbt 0.13 `in` syntax is deprecated on sbt 1.x and gone on sbt 2.x.
val sbtBuildFile =
s"""|lazy val root = project
| .in(file("."))
| .settings(
| scalaVersion := "$scalaVersion",
| resolvers += Resolver.mavenLocal,
| scalacOptions ++= Seq("-java-output-version", "$javaTargetVersion"),
| javacOptions in (Compile, compile) ++= Seq("-source", "$javaVersion", "-target", "$javaTargetVersion"),
| Compile / compile / javacOptions ++= Seq("-source", "$javaVersion", "-target", "$javaTargetVersion"),
| libraryDependencies ++= Seq(
| "org.virtuslab" %% "besom-core" % "$coreVersion",
| "org.virtuslab" %% "besom-fake-standard-resource" % "1.2.3-TEST",
Expand All @@ -46,6 +55,14 @@ class LanguagePluginTest extends munit.FunSuite {
| )
|""".stripMargin

// build.properties pins the sbt version per project, which is what decides whether the sbt 1.x or
// the sbt 2.x variant of BesomPlugin.scala gets compiled. Both runner scripts honour it, so a single
// installed sbt can drive both tests.
def sbtProjectFiles(sbtVersion: String) = Map(
"build.sbt" -> sbtBuildFile,
"project/build.properties" -> s"sbt.version=$sbtVersion\n"
)

val gradleBuildFile =
s"""|plugins {
| scala
Expand Down Expand Up @@ -279,11 +296,21 @@ class LanguagePluginTest extends munit.FunSuite {

FunFixture[pulumi.FixtureContext](
setup = pulumi.fixture.setup(
executorsDir / "sbt",
projectFiles = Map("build.sbt" -> sbtBuildFile)
executorsDir / "sbt1",
projectFiles = sbtProjectFiles(sbt1Version)
),
teardown = pulumi.fixture.teardown
).test(s"sbt $sbt1Version") { ctx =>
testExecutor(ctx)
}

FunFixture[pulumi.FixtureContext](
setup = pulumi.fixture.setup(
executorsDir / "sbt2",
projectFiles = sbtProjectFiles(sbt2Version)
),
teardown = pulumi.fixture.teardown
).test("sbt") { ctx =>
).test(s"sbt $sbt2Version") { ctx =>
testExecutor(ctx)
}

Expand Down
8 changes: 4 additions & 4 deletions integration-tests/integration.scala
Original file line number Diff line number Diff line change
Expand Up @@ -178,9 +178,9 @@ object pulumi {
object fixture {
def setupProject(testDir: os.Path, projectFiles: Map[String, String] = Map("project.scala" -> defaultProjectFile)): Unit =
projectFiles.foreach { case (name, content) =>
val file = testDir / name
val file = testDir / os.SubPath(name)
println(s"Writing test file: ${file.relativeTo(os.pwd)}")
os.write.over(file, content)
os.write.over(file, content, createFolders = true)
}

def setup(
Expand Down Expand Up @@ -229,9 +229,9 @@ object pulumi {

println(s"Test stack: $stackName")
args.projectFiles.foreach { case (name, content) =>
val file = args.programDir / name
val file = args.programDir / os.SubPath(name)
println(s"Writing test file: ${file.relativeTo(os.pwd)}")
os.write.over(file, content)
os.write.over(file, content, createFolders = true)
}
pulumi.stackInit(stackName).call(cwd = args.programDir, env = allEnv)
ProgramContext(stackName, args.programDir, allEnv)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
name: language-plugin-test
description: sbt executor test
description: sbt1 executor test
runtime:
name: scala
options:
Expand Down
1 change: 1 addition & 0 deletions integration-tests/resources/executors/sbt2/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
project
9 changes: 9 additions & 0 deletions integration-tests/resources/executors/sbt2/Pulumi.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
name: language-plugin-test
description: sbt2 executor test
runtime:
name: scala
options:
# language host debugging options, for full list of options:
# ~/.pulumi/plugins/language-scala-v0.1.0/pulumi-language-scala -h
v: 0
logtostderr: true
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package besom.languageplugin.test.pulumiapp

import besom.*

@main def run = Pulumi.run {
// Making sure the plugin is actually on the classpath
val x = besom.languageplugin.test.resourceplugin.standard.customVal
val y = besom.languageplugin.test.resourceplugin.external.customVal

// Show that we were executed for tests to read
Stack(
log.warn("scala executor test got executed")
)
}
6 changes: 4 additions & 2 deletions language-plugin/pulumi-language-scala/executors/executor.go
Original file line number Diff line number Diff line change
Expand Up @@ -112,8 +112,10 @@ func ResolveBootstrapLibJarPath(languagePluginHomeDir string) string {
return filepath.Join(languagePluginHomeDir, "bootstrap.jar")
}

func SbtBesomPluginPath(languagePluginHomeDir string) string {
return filepath.Join(languagePluginHomeDir, "BesomPlugin.scala")
// SbtBesomPluginPath locates a source file of the sbt plugin shipped with the language plugin.
// The variant is one of the subdirectories of the language plugin home dir, see sbtBesomPluginFiles.
func SbtBesomPluginPath(languagePluginHomeDir string, variant string, fileName string) string {
return filepath.Join(languagePluginHomeDir, variant, fileName)
}

func logDetectedExecutor(executor *ScalaExecutor) {
Expand Down
74 changes: 64 additions & 10 deletions language-plugin/pulumi-language-scala/executors/executor_sbt.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import (
"fmt"
"os"
"io"
"path"
"path/filepath"
"strings"

Expand Down Expand Up @@ -56,28 +55,83 @@ func (sbt) isSbtProject(opts ScalaExecutorOptions) (bool, error) {
return false, nil
}

// sbtBesomPluginFiles describes where each shipped source file has to land in the user's project.
// sbt builds its meta-build (the `project` directory) with Scala 2.12 on the sbt 1.x line and with
// Scala 3 on the sbt 2.x line, and only reads the cross-version source directory matching that
// binary version. Dropping both variants therefore lets a single language plugin release serve both
// sbt lines without having to detect the sbt version: whichever sbt the user runs compiles exactly
// one of them and ignores the other. The shared base goes to the plain `scala` directory, which both
// lines compile.
var sbtBesomPluginFiles = []struct {
// Subdirectory of the language plugin home dir holding the file.
variant string
// File name, the same on both sides of the copy.
fileName string
// Source directory under project/src/main the file is written to.
scalaSourceDir string
}{
{variant: "shared", fileName: "BesomPluginBase.scala", scalaSourceDir: "scala"},
{variant: "sbt1", fileName: "BesomPlugin.scala", scalaSourceDir: "scala-2.12"},
{variant: "sbt2", fileName: "BesomPlugin.scala", scalaSourceDir: "scala-3"},
}

// besomPluginGeneratedMarker appears in the header of every BesomPlugin.scala besom writes out.
const besomPluginGeneratedMarker = "This file was generated by besom"

func (sbt) newSbtExecutor(cmd string, projectRoot fsys.ParentFS, languagePluginHomeDir string) (*ScalaExecutor, error) {
sbtModule := os.Getenv("BESOM_SBT_MODULE")
sbtBesomPluginPath := SbtBesomPluginPath(languagePluginHomeDir)
sbtBesomPluginTargetPath := path.Join(projectRoot.Path(), "project", "BesomPlugin.scala")
bootstrapLibJarPath := ResolveBootstrapLibJarPath(languagePluginHomeDir)
pluginDiscovererOutputPath := PluginDiscovererOutputFilePath(projectRoot)

se := &ScalaExecutor{
Name: "sbt",
Cmd: cmd,
RunArgs: makeArgs(sbtModule, "run"),
BuildArgs: makeArgs(sbtModule, "compile"),
PluginArgs: makeArgs(sbtModule, fmt.Sprintf(`besomExtractPulumiPlugins %s %s`, bootstrapLibJarPath, pluginDiscovererOutputPath)),
VersionArgs: []string{"--numeric-version"},
Name: "sbt",
Cmd: cmd,
RunArgs: makeArgs(sbtModule, "run"),
BuildArgs: makeArgs(sbtModule, "compile"),
PluginArgs: makeArgs(sbtModule, fmt.Sprintf(`besomExtractPulumiPlugins %s %s`, bootstrapLibJarPath, pluginDiscovererOutputPath)),
// --script-version is understood by both the sbt 1.x and the sbt 2.x runner script.
// --numeric-version is sbt 1.x only; sbt 2.x ignores it and drops into an interactive shell.
VersionArgs: []string{"--script-version"},
SetupProject: func() error {
return copyFileIfNotPresent(sbtBesomPluginPath, sbtBesomPluginTargetPath)
if err := removeLegacyBesomPlugin(projectRoot); err != nil {
return err
}
for _, f := range sbtBesomPluginFiles {
srcPath := SbtBesomPluginPath(languagePluginHomeDir, f.variant, f.fileName)
destPath := filepath.Join(projectRoot.Path(), "project", "src", "main", f.scalaSourceDir, f.fileName)
if err := copyFileIfNotPresent(srcPath, destPath); err != nil {
return err
}
}
return nil
},
}

return se, nil
}

// removeLegacyBesomPlugin deletes the project/BesomPlugin.scala that besom versions before
// sbt 2.x support used to write. sbt compiles project/*.scala together with the cross-version
// source directories, so leaving it behind would define BesomPlugin twice and break the
// meta-build. Files the user wrote themselves are left alone.
func removeLegacyBesomPlugin(projectRoot fsys.ParentFS) error {
legacyPath := filepath.Join(projectRoot.Path(), "project", "BesomPlugin.scala")
contents, err := os.ReadFile(legacyPath)
if os.IsNotExist(err) {
return nil
}
if err != nil {
return fmt.Errorf("error reading legacy besom sbt plugin at %s: %w", legacyPath, err)
}
if !strings.Contains(string(contents), besomPluginGeneratedMarker) {
return nil
}
if err := os.Remove(legacyPath); err != nil {
return fmt.Errorf("failed to remove legacy besom sbt plugin at %s: %w", legacyPath, err)
}
return nil
}

func makeArgs(sbtModule string, cmd string) []string {
if sbtModule != "" {
return []string{"-batch", fmt.Sprintf("%s/%s", sbtModule, cmd)}
Expand Down
Loading
Loading