Skip to content
Closed
Show file tree
Hide file tree
Changes from 2 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
12 changes: 11 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ jobs:
strategy:
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
scala: [2.12.20]
scala: [2.12.20, 3.7.2]
java: [temurin@8, temurin@11, temurin@17, temurin@21]
exclude:
- java: temurin@8
Expand Down Expand Up @@ -173,6 +173,16 @@ jobs:
tar xf targets.tar
rm targets.tar

- name: Download target directories (3.7.2)
uses: actions/download-artifact@v4
with:
name: target-${{ matrix.os }}-3.7.2-${{ matrix.java }}

- name: Inflate target directories (3.7.2)
run: |
tar xf targets.tar
rm targets.tar

- name: Publish project
env:
PGP_PASSPHRASE: ${{ secrets.PGP_PASSPHRASE }}
Expand Down
18 changes: 17 additions & 1 deletion build.sbt
Original file line number Diff line number Diff line change
@@ -1,7 +1,23 @@
lazy val scala212 = "2.12.20"
ThisBuild / crossScalaVersions := Seq(scala212)
lazy val scala3 = "3.7.2"

ThisBuild / crossScalaVersions := Seq(scala212, scala3)
ThisBuild / scalaVersion := scala212

ThisBuild / (pluginCrossBuild / sbtVersion) := {
scalaBinaryVersion.value match {
case "2.12" => "1.5.8"

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
case "2.12" => "1.5.8"
case "2.12" => "1.9.9"

If this plugin wants to run tests on JDK 21, then the minimum version should be 1.9.9.

case _ => "2.0.0-RC4"
}
}

ThisBuild / scriptedSbt := {
scalaBinaryVersion.value match {
case "2.12" => "1.10.10"
case _ => "2.0.0-RC4"
}
}

// So that publishLocal doesn't continuously create new versions
def versionFmt(out: sbtdynver.GitDescribeOutput): String = {
val snapshotSuffix =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@

package com.github.sbt.osgi

import sbt._
import sbt.Keys._
import sbt.*
import sbt.Keys.*
import sbt.plugins.JvmPlugin

object SbtOsgi extends AutoPlugin {
Expand All @@ -43,7 +43,7 @@ object SbtOsgi extends AutoPlugin {
}

lazy val defaultOsgiSettings: Seq[Setting[_]] = {
import OsgiKeys._
import OsgiKeys.*
Seq(
bundle := Osgi.bundleTask(
manifestHeaders.value,
Expand Down Expand Up @@ -86,7 +86,7 @@ object SbtOsgi extends AutoPlugin {
}

lazy val defaultGlobalSettings: Seq[Setting[_]] = {
import OsgiKeys._
import OsgiKeys.*
Seq(
bundle := file(""),
bundleActivator := None,
Expand Down
122 changes: 122 additions & 0 deletions src/main/scala-3/com/github/sbt/osgi/SbtOsgi.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
/*
* Copyright 2011-2013 Typesafe Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.github.sbt.osgi

import sbt._
import sbt.Keys._
import sbt.plugins.JvmPlugin

object SbtOsgi extends AutoPlugin {

override lazy val trigger: PluginTrigger = noTrigger

override lazy val requires: Plugins = JvmPlugin

override lazy val projectSettings: Seq[Def.Setting[_]] = defaultOsgiSettings

override lazy val globalSettings: Seq[Def.Setting[_]] = defaultGlobalSettings

object autoImport {
type OsgiManifestHeaders = com.github.sbt.osgi.OsgiManifestHeaders

val OsgiKeys = com.github.sbt.osgi.OsgiKeys

lazy val osgiSettings: Seq[Setting[_]] = Seq(
Compile / packageBin / packagedArtifact := Def.uncached:
val conv = fileConverter.value
Scoped
.mkTuple2(
(Compile / packageBin / artifact).value,
conv.toVirtualFile(OsgiKeys.bundle.value.toPath)
),
Compile / packageBin / artifact ~= (_.withType("bundle"))
)
}

lazy val defaultOsgiSettings: Seq[Setting[_]] = {
import OsgiKeys._
Seq(
bundle := Def.uncached:
val conv = fileConverter.value
Osgi.bundleTask(
manifestHeaders.value,
additionalHeaders.value,
(Compile / dependencyClasspathAsJars).value.map(_.data).map(r => conv.toPath(r).toFile) ++ (Compile / products).value,
conv.toPath((Compile / packageBin / artifactPath).value).toFile,
(Compile / resourceDirectories).value,
embeddedJars.value,
explodedJars.value,
failOnUndecidedPackage.value,
(Compile / sourceDirectories).value,
(Compile / packageBin / packageOptions).value,
packageWithJVMJar.value,
cacheStrategy.value,
streams.value
),
manifestHeaders := Def.uncached:
OsgiManifestHeaders(
bundleActivator.value,
description.value,
apiURL.value.map(_.toURL),
licenses.value.map(l => (l.spdxId, l.uri.toURL)),
name.value,
bundleRequiredExecutionEnvironment.value,
organizationName.value,
bundleSymbolicName.value,
bundleVersion.value,
dynamicImportPackage.value,
exportPackage.value,
importPackage.value,
fragmentHost.value,
privatePackage.value,
requireBundle.value,
requireCapability.value
),
Compile / sbt.Keys.packageBin := Def.uncached:
val conv = fileConverter.value
conv.toVirtualFile(bundle.value.toPath)
,
bundleSymbolicName := Osgi.defaultBundleSymbolicName(organization.value, normalizedName.value),
privatePackage := bundleSymbolicName(name => List(name + ".*")).value,
bundleVersion := version.value
)
}

lazy val defaultGlobalSettings: Seq[Setting[_]] = {
import OsgiKeys._
Seq(
bundle := file(""),
bundleActivator := None,
bundleSymbolicName := "",
bundleVersion := "",
bundleRequiredExecutionEnvironment := Nil,
dynamicImportPackage := Nil,
exportPackage := Nil,
importPackage := List("*"),
fragmentHost := None,
privatePackage := Nil,
requireBundle := Nil,
failOnUndecidedPackage := false,
requireCapability := Osgi.requireCapabilityTask,
additionalHeaders := Map.empty,
embeddedJars := Nil,
explodedJars := Nil,
packageWithJVMJar := false,
cacheStrategy := None
)
}
}
20 changes: 20 additions & 0 deletions src/main/scala-3/com/github/sbt/osgi/package.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package com.github.sbt.osgi

/*
* Copyright (C) 2025 Romain Reuillon
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

type ManifestAttributes = sbt.PackageOption.ManifestAttributes
2 changes: 1 addition & 1 deletion src/main/scala/com/github/sbt/osgi/Osgi.scala
Original file line number Diff line number Diff line change
Expand Up @@ -307,7 +307,7 @@ private object Osgi {
def seqToStrOpt[A](seq: Seq[A])(f: A => String): Option[String] =
if (seq.isEmpty) None else Some(seq map f mkString ",")

def strToStrOpt(s: String): Option[String] = Option(s).filter(_.trim nonEmpty)
def strToStrOpt(s: String): Option[String] = Option(s).filter(_.trim.nonEmpty)

def includeResourceProperty(resourceDirectories: Seq[File], embeddedJars: Seq[File], explodedJars: Seq[File]) = {
val paths: Seq[String] =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,15 @@

package com.github.sbt.osgi

import aQute.bnd.osgi.Constants._
import java.io.File
import aQute.bnd.osgi.Constants.*
import org.specs2.mutable.Specification
import scala.collection.JavaConverters._

import java.io.File
import scala.collection.JavaConverters.*

class OsgiSpec extends Specification {

import Osgi._
import Osgi.*

"Calling seqToStrOpt" should {
"return None for an empty Seq" in {
Expand Down
115 changes: 115 additions & 0 deletions src/test/scala-3/com/github/sbt/osgi/OsgiSpec.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
/*
* Copyright 2011 Typesafe Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.github.sbt.osgi

import aQute.bnd.osgi.Constants.*
import org.specs2.mutable.Specification

import java.io.File
import scala.collection.JavaConverters.*

class OsgiSpec extends Specification {

import Osgi.*

"Calling seqToStrOpt" should {
"return None for an empty Seq" in {
seqToStrOpt(Nil)(id) must be(None)
}
"return Some wrapping a properly constructed String for a non-empty Seq" in {
seqToStrOpt(Seq("foo", "bar", "baz"))(id) must beSome("foo,bar,baz")
seqToStrOpt(Seq(1, 2, 3))(_.toString) must beSome("1,2,3")
}
}

"Calling headersToProperties" should {
"return the proper properties" in {
val headers = OsgiManifestHeaders(
Some("bundleActivator"),
"bundleDescription",
Some(sbt.url("http://example.example").toURL),
Seq(("License", sbt.url("http://license.license").toURL)),
"bundleName",
Seq("req1", "req2"),
"bundleVendor",
"bundleSymbolicName",
"bundleVersion",
Seq("dynamicImportPackage"),
Seq("exportPackage1", "exportPackage2", "exportPackage3"),
Seq("importPackage"),
None,
Seq("privatePackage"),
Nil,
"requireCapability"
)
val properties = headersToProperties(headers, Map.empty)
properties.asScala must havePairs(
BUNDLE_ACTIVATOR -> "bundleActivator",
BUNDLE_SYMBOLICNAME -> "bundleSymbolicName",
BUNDLE_DESCRIPTION -> "bundleDescription",
BUNDLE_DOCURL -> sbt.url("http://example.example").toString,
BUNDLE_LICENSE -> "http://license.license;description=License",
BUNDLE_NAME -> "bundleName",
BUNDLE_REQUIREDEXECUTIONENVIRONMENT -> "req1,req2",
BUNDLE_VENDOR -> "bundleVendor",
BUNDLE_VERSION -> "bundleVersion",
DYNAMICIMPORT_PACKAGE -> "dynamicImportPackage",
EXPORT_PACKAGE -> "exportPackage1,exportPackage2,exportPackage3",
IMPORT_PACKAGE -> "importPackage",
PRIVATE_PACKAGE -> "privatePackage",
REQUIRE_CAPABILITY -> "requireCapability"
)
properties.asScala must not(haveKey(FRAGMENT_HOST))
properties.asScala must not(haveKey(REQUIRE_BUNDLE))
}
}

"Calling defaultBundleSymbolicName" should {
"concatenate organization and name properly" in {
defaultBundleSymbolicName("a.b.c", "d.c") must beEqualTo("a.b.c.d.c")
defaultBundleSymbolicName("a.b.c", "d-c") must beEqualTo("a.b.c.d.c")
defaultBundleSymbolicName("a.b.c", "c.d") must beEqualTo("a.b.c.d")
defaultBundleSymbolicName("a.b.c", "c-d") must beEqualTo("a.b.c.d")
defaultBundleSymbolicName("", "a") must beEqualTo("a")
defaultBundleSymbolicName("a", "") must beEqualTo("a")
defaultBundleSymbolicName("", "") must beEqualTo("")
}
}

"Calling includeResourceProperty" should {
"add resources and embedded jars and exploded jars to INCLUDERESOURCE" in {
val resourceDir = new File("/resource")
val jar = new File("/aJar.jar")
val anotherJar = new File("/anotherJar.jar")
val actual = includeResourceProperty(Seq(resourceDir), Seq(jar), Seq(anotherJar))
actual must beSome(resourceDir.getAbsolutePath + "," + jar.getAbsolutePath + ",@" + anotherJar.getAbsolutePath)
}
}

"Calling bundleClasspathProperty" should {
"add bundle classes and embedded jars to classpath" in {
val embeddedJars = Seq(new File("/aJar.jar"), new File("/bJar.jar"))
val actual = bundleClasspathProperty(embeddedJars)
actual must beSome(".,aJar.jar,bJar.jar")
}
"remain default if no embedded jars are specified" in {
val embeddedJars = Seq()
val actual = bundleClasspathProperty(embeddedJars)
actual must beNone
}
}
}
Loading