Compare commits
25 Commits
bc08a46f28
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
aa3c3377d0
|
|||
|
96bb989ad5
|
|||
|
98950f21f3
|
|||
|
7110fdb53e
|
|||
|
d52447dc23
|
|||
| b703b09f76 | |||
|
404b2b66b7
|
|||
|
df9d8b2482
|
|||
|
7e2b93560f
|
|||
|
d0431ba1ed
|
|||
|
032f2ca7dc
|
|||
|
8bd952a4c8
|
|||
|
8bb386ebf7
|
|||
|
1dc17ac487
|
|||
|
ec73bdabfa
|
|||
|
e58cc0a1a6
|
|||
| 3efcca8651 | |||
|
df5e191582
|
|||
|
21738818d5
|
|||
|
9215adc740
|
|||
|
a2e8249003
|
|||
|
0d8b0eaecd
|
|||
|
d1ccb1c762
|
|||
|
bc1c7172d4
|
|||
|
b57edb8d3a
|
17
.gitignore
vendored
17
.gitignore
vendored
@@ -1,12 +1,11 @@
|
|||||||
.gradle
|
### Gradle/Java ###
|
||||||
gradlew.bat
|
**/.gradle
|
||||||
build/
|
**/build/
|
||||||
!gradle/wrapper/gradle-wrapper.jar
|
|
||||||
!**/src/main/**/build/
|
!**/src/main/**/build/
|
||||||
!**/src/test/**/build/
|
!**/src/test/**/build/
|
||||||
|
|
||||||
### IntelliJ IDEA ###
|
### IntelliJ IDEA ###
|
||||||
/.idea/
|
**/.idea
|
||||||
*.iws
|
*.iws
|
||||||
*.iml
|
*.iml
|
||||||
*.ipr
|
*.ipr
|
||||||
@@ -40,4 +39,10 @@ bin/
|
|||||||
.vscode/
|
.vscode/
|
||||||
|
|
||||||
### Mac OS ###
|
### Mac OS ###
|
||||||
.DS_Store
|
.DS_Store
|
||||||
|
|
||||||
|
### Go ###
|
||||||
|
go.sum
|
||||||
|
|
||||||
|
### Build ###
|
||||||
|
/hakureiUpdate.sh
|
||||||
5
.idea/.gitignore
generated
vendored
5
.idea/.gitignore
generated
vendored
@@ -1,5 +0,0 @@
|
|||||||
# Default ignored files
|
|
||||||
/shelf/
|
|
||||||
/workspace.xml
|
|
||||||
# Environment-dependent path to Maven home directory
|
|
||||||
/mavenHomeManager.xml
|
|
||||||
8
.idea/dictionaries/project.xml
generated
8
.idea/dictionaries/project.xml
generated
@@ -1,8 +0,0 @@
|
|||||||
<component name="ProjectDictionaryState">
|
|
||||||
<dictionary name="project">
|
|
||||||
<words>
|
|
||||||
<w>buildsrc</w>
|
|
||||||
<w>planterette</w>
|
|
||||||
</words>
|
|
||||||
</dictionary>
|
|
||||||
</component>
|
|
||||||
34
.idea/gradle.xml
generated
34
.idea/gradle.xml
generated
@@ -1,34 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<project version="4">
|
|
||||||
<component name="GradleMigrationSettings" migrationVersion="1" />
|
|
||||||
<component name="GradleSettings">
|
|
||||||
<option name="linkedExternalProjectsSettings">
|
|
||||||
<GradleProjectSettings>
|
|
||||||
<compositeConfiguration>
|
|
||||||
<compositeBuild compositeDefinitionSource="SCRIPT">
|
|
||||||
<builds>
|
|
||||||
<build path="$PROJECT_DIR$/buildSrc" name="buildSrc">
|
|
||||||
<projects>
|
|
||||||
<project path="$PROJECT_DIR$/buildSrc" />
|
|
||||||
</projects>
|
|
||||||
</build>
|
|
||||||
</builds>
|
|
||||||
</compositeBuild>
|
|
||||||
</compositeConfiguration>
|
|
||||||
<option name="externalProjectPath" value="$PROJECT_DIR$" />
|
|
||||||
<option name="gradleHome" value="" />
|
|
||||||
<option name="modules">
|
|
||||||
<set>
|
|
||||||
<option value="$PROJECT_DIR$" />
|
|
||||||
<option value="$PROJECT_DIR$/buildSrc" />
|
|
||||||
<option value="$PROJECT_DIR$/libplt" />
|
|
||||||
<option value="$PROJECT_DIR$/plt" />
|
|
||||||
<option value="$PROJECT_DIR$/plt-build" />
|
|
||||||
<option value="$PROJECT_DIR$/plt-fetch" />
|
|
||||||
<option value="$PROJECT_DIR$/plt-pkg" />
|
|
||||||
</set>
|
|
||||||
</option>
|
|
||||||
</GradleProjectSettings>
|
|
||||||
</option>
|
|
||||||
</component>
|
|
||||||
</project>
|
|
||||||
7
.idea/misc.xml
generated
7
.idea/misc.xml
generated
@@ -1,7 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<project version="4">
|
|
||||||
<component name="ExternalStorageConfigurationManager" enabled="true" />
|
|
||||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_21" default="true" project-jdk-name="21" project-jdk-type="JavaSDK">
|
|
||||||
<output url="file://$PROJECT_DIR$/out" />
|
|
||||||
</component>
|
|
||||||
</project>
|
|
||||||
6
.idea/vcs.xml
generated
6
.idea/vcs.xml
generated
@@ -1,6 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
<project version="4">
|
|
||||||
<component name="VcsDirectoryMappings">
|
|
||||||
<mapping directory="$PROJECT_DIR$" vcs="Git" />
|
|
||||||
</component>
|
|
||||||
</project>
|
|
||||||
21
README.md
21
README.md
@@ -1,23 +1,2 @@
|
|||||||
# planterette
|
# planterette
|
||||||
|
|
||||||
This project uses [Gradle](https://gradle.org/).
|
|
||||||
To build and run the application, use the *Gradle* tool window by clicking the Gradle icon in the right-hand toolbar,
|
|
||||||
or run it directly from the terminal:
|
|
||||||
|
|
||||||
* Run `./gradlew run` to build and run the application.
|
|
||||||
* Run `./gradlew build` to only build the application.
|
|
||||||
* Run `./gradlew check` to run all checks, including tests.
|
|
||||||
* Run `./gradlew clean` to clean all build outputs.
|
|
||||||
|
|
||||||
Note the usage of the Gradle Wrapper (`./gradlew`).
|
|
||||||
This is the suggested way to use Gradle in production projects.
|
|
||||||
|
|
||||||
[Learn more about the Gradle Wrapper](https://docs.gradle.org/current/userguide/gradle_wrapper.html).
|
|
||||||
|
|
||||||
[Learn more about Gradle tasks](https://docs.gradle.org/current/userguide/command_line_interface.html#common_tasks).
|
|
||||||
|
|
||||||
This project follows the suggested multi-module setup and consists of the `app` and `api` subprojects.
|
|
||||||
The shared build logic was extracted to a convention plugin located in `buildSrc`.
|
|
||||||
|
|
||||||
This project uses a version catalog (see `gradle/libs.versions.toml`) to declare and version dependencies
|
|
||||||
and both a build cache and a configuration cache (see `gradle.properties`).
|
|
||||||
79
api/capability.go
Normal file
79
api/capability.go
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Capability interface {
|
||||||
|
ID() string
|
||||||
|
}
|
||||||
|
type CapabilityJSON struct {
|
||||||
|
Capability
|
||||||
|
}
|
||||||
|
type BasicCapability struct {
|
||||||
|
Id string `json:"id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c BasicCapability) ID() string {
|
||||||
|
return c.Id
|
||||||
|
}
|
||||||
|
|
||||||
|
const CapabilityBasic string = "basic"
|
||||||
|
|
||||||
|
type DBusCapability struct {
|
||||||
|
Id string `json:"id"`
|
||||||
|
Own []string `json:"own,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c DBusCapability) ID() string {
|
||||||
|
return c.Id
|
||||||
|
}
|
||||||
|
|
||||||
|
const CapabilityDBus = "dbus"
|
||||||
|
|
||||||
|
type capabilityType struct {
|
||||||
|
Type string `json:"type"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *CapabilityJSON) MarshalJSON() ([]byte, error) {
|
||||||
|
if c == nil || c.Capability == nil {
|
||||||
|
return nil, fmt.Errorf("invalid capability")
|
||||||
|
}
|
||||||
|
var v any
|
||||||
|
switch cv := c.Capability.(type) {
|
||||||
|
case *BasicCapability:
|
||||||
|
v = &struct {
|
||||||
|
capabilityType
|
||||||
|
*BasicCapability
|
||||||
|
}{capabilityType{CapabilityBasic}, cv}
|
||||||
|
|
||||||
|
case *DBusCapability:
|
||||||
|
v = &struct {
|
||||||
|
capabilityType
|
||||||
|
*DBusCapability
|
||||||
|
}{capabilityType{CapabilityDBus}, cv}
|
||||||
|
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("invalid capability")
|
||||||
|
}
|
||||||
|
return json.Marshal(v)
|
||||||
|
}
|
||||||
|
func (c *CapabilityJSON) UnmarshalJSON(data []byte) error {
|
||||||
|
t := new(capabilityType)
|
||||||
|
if err := json.Unmarshal(data, &t); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
switch t.Type {
|
||||||
|
case CapabilityBasic:
|
||||||
|
*c = CapabilityJSON{new(BasicCapability)}
|
||||||
|
|
||||||
|
case CapabilityDBus:
|
||||||
|
*c = CapabilityJSON{new(DBusCapability)}
|
||||||
|
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("invalid capability")
|
||||||
|
}
|
||||||
|
|
||||||
|
return json.Unmarshal(data, c.Capability)
|
||||||
|
}
|
||||||
25
api/manifest.go
Normal file
25
api/manifest.go
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
package api
|
||||||
|
|
||||||
|
import "hakurei.app/container/check"
|
||||||
|
|
||||||
|
type Manifest struct {
|
||||||
|
Metadata Metadata `json:"metadata"`
|
||||||
|
Executable Executable `json:"executable"`
|
||||||
|
Capabilities []CapabilityJSON `json:"capabilities"`
|
||||||
|
Permissions []CapabilityJSON `json:"permissions"`
|
||||||
|
}
|
||||||
|
type Metadata struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
Description string `json:"description"`
|
||||||
|
Author string `json:"author"`
|
||||||
|
Icon *check.Absolute `json:"icon"`
|
||||||
|
Version int `json:"version"`
|
||||||
|
VersionName string `json:"version_name"`
|
||||||
|
}
|
||||||
|
type Executable struct {
|
||||||
|
BaseImage string `json:"base_image"`
|
||||||
|
Binary *check.Absolute `json:"binary"`
|
||||||
|
Args []string `json:"args"`
|
||||||
|
Env map[string]string `json:"env"`
|
||||||
|
}
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
plugins {
|
|
||||||
`kotlin-dsl`
|
|
||||||
}
|
|
||||||
|
|
||||||
kotlin {
|
|
||||||
jvmToolchain(24)
|
|
||||||
}
|
|
||||||
|
|
||||||
dependencies {
|
|
||||||
implementation(libs.kotlinGradlePlugin)
|
|
||||||
}
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
dependencyResolutionManagement {
|
|
||||||
|
|
||||||
@Suppress("UnstableApiUsage")
|
|
||||||
repositories {
|
|
||||||
mavenCentral()
|
|
||||||
}
|
|
||||||
|
|
||||||
versionCatalogs {
|
|
||||||
create("libs") {
|
|
||||||
from(files("../gradle/libs.versions.toml"))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
rootProject.name = "buildSrc"
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
package buildsrc.convention
|
|
||||||
|
|
||||||
import org.gradle.api.tasks.testing.logging.TestLogEvent
|
|
||||||
|
|
||||||
tasks.withType<Test>().configureEach {
|
|
||||||
useJUnitPlatform()
|
|
||||||
|
|
||||||
testLogging {
|
|
||||||
events(
|
|
||||||
TestLogEvent.FAILED,
|
|
||||||
TestLogEvent.PASSED,
|
|
||||||
TestLogEvent.SKIPPED
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
5
cmd/plt-build-wrapper/main.go
Normal file
5
cmd/plt-build-wrapper/main.go
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
|
||||||
|
}
|
||||||
9
cmd/plt-build-wrapper/main_test.go
Normal file
9
cmd/plt-build-wrapper/main_test.go
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestPltBuildWrapper(t *testing.T) {
|
||||||
|
|
||||||
|
}
|
||||||
52
cmd/plt-build/build.gradle.kts
Normal file
52
cmd/plt-build/build.gradle.kts
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
plugins {
|
||||||
|
kotlin("jvm") version "2.2.10"
|
||||||
|
kotlin("plugin.serialization") version "2.2.20"
|
||||||
|
}
|
||||||
|
|
||||||
|
group = "moe.rosa"
|
||||||
|
version = "0.1.0"
|
||||||
|
|
||||||
|
repositories {
|
||||||
|
mavenCentral()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
val kotlinVersion = "2.2.10"
|
||||||
|
val kotlinCoroutinesVersion = "1.7.0-RC"
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
testImplementation(platform("org.junit:junit-bom:5.10.0"))
|
||||||
|
testImplementation("org.junit.jupiter:junit-jupiter")
|
||||||
|
testRuntimeOnly("org.junit.platform:junit-platform-launcher")
|
||||||
|
|
||||||
|
testImplementation(kotlin("test"))
|
||||||
|
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.9.0")
|
||||||
|
implementation(kotlin("reflect"))
|
||||||
|
implementation("org.jetbrains.kotlin:kotlin-scripting-common:${kotlinVersion}")
|
||||||
|
implementation("org.jetbrains.kotlin:kotlin-scripting-jvm:${kotlinVersion}")
|
||||||
|
implementation("org.jetbrains.kotlin:kotlin-scripting-dependencies:${kotlinVersion}")
|
||||||
|
implementation("org.jetbrains.kotlin:kotlin-scripting-dependencies-maven:${kotlinVersion}")
|
||||||
|
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:${kotlinCoroutinesVersion}")
|
||||||
|
}
|
||||||
|
|
||||||
|
sourceSets.main {
|
||||||
|
resources {
|
||||||
|
srcDirs("src/main/resources", "build/natives")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
kotlin {
|
||||||
|
jvmToolchain(24)
|
||||||
|
}
|
||||||
|
|
||||||
|
tasks.test {
|
||||||
|
useJUnitPlatform()
|
||||||
|
}
|
||||||
|
tasks.register<Exec>("compileNativeLib") {
|
||||||
|
val java_home = System.getProperty("java.home")
|
||||||
|
environment("CGO_CFLAGS" to "-I$java_home/include -I$java_home/include/linux")
|
||||||
|
workingDir("src/main/go")
|
||||||
|
commandLine("go", "build", "-linkshared", "-buildmode=c-shared", "-o", layout.buildDirectory.get().dir("natives").file("pltbuild.so"))
|
||||||
|
}
|
||||||
|
tasks.processResources {
|
||||||
|
dependsOn += "compileNativeLib"
|
||||||
|
}
|
||||||
1
cmd/plt-build/gradle.properties
Normal file
1
cmd/plt-build/gradle.properties
Normal file
@@ -0,0 +1 @@
|
|||||||
|
kotlin.code.style=official
|
||||||
BIN
cmd/plt-build/gradle/wrapper/gradle-wrapper.jar
vendored
Normal file
BIN
cmd/plt-build/gradle/wrapper/gradle-wrapper.jar
vendored
Normal file
Binary file not shown.
@@ -1,4 +1,4 @@
|
|||||||
#Tue Aug 26 23:15:20 CDT 2025
|
#Fri Sep 26 23:47:58 CDT 2025
|
||||||
distributionBase=GRADLE_USER_HOME
|
distributionBase=GRADLE_USER_HOME
|
||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.14-bin.zip
|
distributionUrl=https\://services.gradle.org/distributions/gradle-8.14-bin.zip
|
||||||
43
gradlew → cmd/plt-build/gradlew
vendored
43
gradlew → cmd/plt-build/gradlew
vendored
@@ -15,8 +15,6 @@
|
|||||||
# See the License for the specific language governing permissions and
|
# See the License for the specific language governing permissions and
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
#
|
#
|
||||||
# SPDX-License-Identifier: Apache-2.0
|
|
||||||
#
|
|
||||||
|
|
||||||
##############################################################################
|
##############################################################################
|
||||||
#
|
#
|
||||||
@@ -57,7 +55,7 @@
|
|||||||
# Darwin, MinGW, and NonStop.
|
# Darwin, MinGW, and NonStop.
|
||||||
#
|
#
|
||||||
# (3) This script is generated from the Groovy template
|
# (3) This script is generated from the Groovy template
|
||||||
# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
|
# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
|
||||||
# within the Gradle project.
|
# within the Gradle project.
|
||||||
#
|
#
|
||||||
# You can find Gradle at https://github.com/gradle/gradle/.
|
# You can find Gradle at https://github.com/gradle/gradle/.
|
||||||
@@ -82,11 +80,13 @@ do
|
|||||||
esac
|
esac
|
||||||
done
|
done
|
||||||
|
|
||||||
# This is normally unused
|
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
|
||||||
# shellcheck disable=SC2034
|
|
||||||
|
APP_NAME="Gradle"
|
||||||
APP_BASE_NAME=${0##*/}
|
APP_BASE_NAME=${0##*/}
|
||||||
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
|
|
||||||
APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit
|
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||||
|
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
||||||
|
|
||||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||||
MAX_FD=maximum
|
MAX_FD=maximum
|
||||||
@@ -133,29 +133,22 @@ location of your Java installation."
|
|||||||
fi
|
fi
|
||||||
else
|
else
|
||||||
JAVACMD=java
|
JAVACMD=java
|
||||||
if ! command -v java >/dev/null 2>&1
|
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||||
then
|
|
||||||
die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
|
||||||
|
|
||||||
Please set the JAVA_HOME variable in your environment to match the
|
Please set the JAVA_HOME variable in your environment to match the
|
||||||
location of your Java installation."
|
location of your Java installation."
|
||||||
fi
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Increase the maximum file descriptors if we can.
|
# Increase the maximum file descriptors if we can.
|
||||||
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
|
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
|
||||||
case $MAX_FD in #(
|
case $MAX_FD in #(
|
||||||
max*)
|
max*)
|
||||||
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
|
|
||||||
# shellcheck disable=SC2039,SC3045
|
|
||||||
MAX_FD=$( ulimit -H -n ) ||
|
MAX_FD=$( ulimit -H -n ) ||
|
||||||
warn "Could not query maximum file descriptor limit"
|
warn "Could not query maximum file descriptor limit"
|
||||||
esac
|
esac
|
||||||
case $MAX_FD in #(
|
case $MAX_FD in #(
|
||||||
'' | soft) :;; #(
|
'' | soft) :;; #(
|
||||||
*)
|
*)
|
||||||
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
|
|
||||||
# shellcheck disable=SC2039,SC3045
|
|
||||||
ulimit -n "$MAX_FD" ||
|
ulimit -n "$MAX_FD" ||
|
||||||
warn "Could not set maximum file descriptor limit to $MAX_FD"
|
warn "Could not set maximum file descriptor limit to $MAX_FD"
|
||||||
esac
|
esac
|
||||||
@@ -200,15 +193,11 @@ if "$cygwin" || "$msys" ; then
|
|||||||
done
|
done
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# Collect all arguments for the java command;
|
||||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
|
||||||
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
# shell script including quotes and variable substitutions, so put them in
|
||||||
|
# double quotes to make sure that they get re-expanded; and
|
||||||
# Collect all arguments for the java command:
|
# * put everything else in single quotes, so that it's not re-expanded.
|
||||||
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
|
|
||||||
# and any embedded shellness will be escaped.
|
|
||||||
# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
|
|
||||||
# treated as '${Hostname}' itself on the command line.
|
|
||||||
|
|
||||||
set -- \
|
set -- \
|
||||||
"-Dorg.gradle.appname=$APP_BASE_NAME" \
|
"-Dorg.gradle.appname=$APP_BASE_NAME" \
|
||||||
@@ -216,12 +205,6 @@ set -- \
|
|||||||
org.gradle.wrapper.GradleWrapperMain \
|
org.gradle.wrapper.GradleWrapperMain \
|
||||||
"$@"
|
"$@"
|
||||||
|
|
||||||
# Stop when "xargs" is not available.
|
|
||||||
if ! command -v xargs >/dev/null 2>&1
|
|
||||||
then
|
|
||||||
die "xargs is not available"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Use "xargs" to parse quoted args.
|
# Use "xargs" to parse quoted args.
|
||||||
#
|
#
|
||||||
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
|
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
|
||||||
2
cmd/plt-build/main.go
Normal file
2
cmd/plt-build/main.go
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
//go:generate ./gradlew build
|
||||||
|
package plt_build
|
||||||
19
cmd/plt-build/main_test.go
Normal file
19
cmd/plt-build/main_test.go
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
package plt_build
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestPltBuild(t *testing.T) {
|
||||||
|
cmd := exec.CommandContext(t.Context(), "./gradlew", "test")
|
||||||
|
cmd.WaitDelay = 100 * time.Millisecond
|
||||||
|
cmd.Stdin, cmd.Stdout, cmd.Stderr = os.Stdin, os.Stdout, os.Stderr
|
||||||
|
err := cmd.Run()
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
t.Error(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
1
cmd/plt-build/settings.gradle.kts
Normal file
1
cmd/plt-build/settings.gradle.kts
Normal file
@@ -0,0 +1 @@
|
|||||||
|
rootProject.name = "plt-build"
|
||||||
37
cmd/plt-build/src/main/go/main.go
Normal file
37
cmd/plt-build/src/main/go/main.go
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
//#include "pltbuild.h"
|
||||||
|
import "C"
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"os"
|
||||||
|
"strconv"
|
||||||
|
"syscall"
|
||||||
|
)
|
||||||
|
|
||||||
|
//export planterette_write
|
||||||
|
func planterette_write(
|
||||||
|
fd C.int,
|
||||||
|
str_p *C.char, str_sz C.size_t,
|
||||||
|
errno_p *C.uintptr_t,
|
||||||
|
err_str_p **C.char,
|
||||||
|
) {
|
||||||
|
f := os.NewFile(uintptr(fd), strconv.Itoa(int(fd)))
|
||||||
|
defer f.Close()
|
||||||
|
_, err := f.WriteString(C.GoStringN(str_p, C.int(str_sz)))
|
||||||
|
if err == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
var pathError *os.PathError
|
||||||
|
if errors.As(err, &pathError) {
|
||||||
|
var errno syscall.Errno
|
||||||
|
if errors.As(pathError.Err, &errno) {
|
||||||
|
*errno_p = C.uintptr_t(errno)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
*err_str_p = C.CString(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
26
cmd/plt-build/src/main/go/pltbuild.c
Normal file
26
cmd/plt-build/src/main/go/pltbuild.c
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
#include "pltbuild.h"
|
||||||
|
|
||||||
|
jint throwIOException( JNIEnv *env, char *message) {
|
||||||
|
jclass exClass;
|
||||||
|
char *className = "java/io/IOException";
|
||||||
|
|
||||||
|
exClass = (*env)->FindClass(env, className);
|
||||||
|
return (*env)->ThrowNew(env, exClass, message);
|
||||||
|
}
|
||||||
|
|
||||||
|
JNIEXPORT void JNICALL Java_moe_rosa_planterette_jni_GoFile_write__ILnet_java_String_2(JNIEnv *env, jobject obj, jint fd, jstring str) {
|
||||||
|
char *s = (*env)->GetStringUTFChars(env, str, 0);
|
||||||
|
size_t sz = (*env)->GetStringUTFLength(env, str);
|
||||||
|
|
||||||
|
uintptr_t *errno_p = NULL;
|
||||||
|
char **err_str_p = NULL;
|
||||||
|
|
||||||
|
planterette_write(fd, s, sz, errno_p, err_str_p);
|
||||||
|
|
||||||
|
if(errno_p || *err_str_p ) {
|
||||||
|
throwIOException(env, **err_str_p ? *err_str_p : strerror(*errno_p));
|
||||||
|
}
|
||||||
|
|
||||||
|
(*env)->ReleaseStringUTFChars(env, str, s);
|
||||||
|
free(err_str_p);
|
||||||
|
}
|
||||||
17
cmd/plt-build/src/main/go/pltbuild.h
Normal file
17
cmd/plt-build/src/main/go/pltbuild.h
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
#include <jni.h>
|
||||||
|
|
||||||
|
#include <string.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
|
||||||
|
#ifndef _PLTBUILD_H
|
||||||
|
#define _PLTBUILD_H
|
||||||
|
|
||||||
|
/*
|
||||||
|
* moe/rosa/planterette/jni/GoFile#write(ILnet/java/String;)V
|
||||||
|
*/
|
||||||
|
JNIEXPORT void JNICALL Java_moe_rosa_planterette_jni_GoFile_write__ILnet_java_String_2(JNIEnv *, jobject, jint, jstring);
|
||||||
|
|
||||||
|
extern void planterette_write(int fd, char *str_p, size_t str_sz, uintptr_t *errno_p, char **err_str_p);
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
package moe.rosa.planterette.api
|
||||||
|
|
||||||
|
import kotlinx.serialization.SerialName
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
sealed interface Capability {
|
||||||
|
val id: String
|
||||||
|
|
||||||
|
@SerialName("basic")
|
||||||
|
data class Basic(override val id: String) : Capability
|
||||||
|
|
||||||
|
@SerialName("dbus")
|
||||||
|
data class DBus(override val id: String, val own: List<String>) : Capability
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
package moe.rosa.planterette.api
|
||||||
|
|
||||||
|
import kotlinx.serialization.SerialName
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class Manifest(
|
||||||
|
val metadata: Metadata,
|
||||||
|
val executable: Executable,
|
||||||
|
val capabilities: List<Capability>,
|
||||||
|
val permissions: List<Capability>
|
||||||
|
) {
|
||||||
|
@Serializable
|
||||||
|
data class Metadata(
|
||||||
|
var id: String,
|
||||||
|
var name: String,
|
||||||
|
var description: String,
|
||||||
|
var author: String,
|
||||||
|
var icon: String,
|
||||||
|
var version: Int,
|
||||||
|
@SerialName("version_name") var versionName: String
|
||||||
|
) {
|
||||||
|
constructor() : this("", "", "", "", "", -1, "")
|
||||||
|
}
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class Executable(
|
||||||
|
@SerialName("base_image") var baseImage: String,
|
||||||
|
var binary: String,
|
||||||
|
val args: MutableList<String>,
|
||||||
|
val env: MutableMap<String, String>
|
||||||
|
) {
|
||||||
|
constructor() : this("", "", mutableListOf(), mutableMapOf())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
227
cmd/plt-build/src/main/kotlin/moe/rosa/planterette/dsl/DSL.kt
Normal file
227
cmd/plt-build/src/main/kotlin/moe/rosa/planterette/dsl/DSL.kt
Normal file
@@ -0,0 +1,227 @@
|
|||||||
|
@file:Suppress("unused")
|
||||||
|
package moe.rosa.planterette.dsl
|
||||||
|
|
||||||
|
import kotlinx.serialization.ExperimentalSerializationApi
|
||||||
|
import kotlinx.serialization.json.Json
|
||||||
|
import moe.rosa.planterette.api.Capability
|
||||||
|
import moe.rosa.planterette.api.Manifest
|
||||||
|
import moe.rosa.planterette.jni.GoFile
|
||||||
|
|
||||||
|
|
||||||
|
@Target(AnnotationTarget.TYPE, AnnotationTarget.CLASS, AnnotationTarget.FUNCTION)
|
||||||
|
@DslMarker
|
||||||
|
annotation class PlanteretteBlockMarker
|
||||||
|
|
||||||
|
@Target(AnnotationTarget.TYPE, AnnotationTarget.CLASS, AnnotationTarget.FUNCTION)
|
||||||
|
@PlanteretteBlockMarker
|
||||||
|
annotation class MetadataBlockMarker
|
||||||
|
|
||||||
|
@Target(AnnotationTarget.TYPE, AnnotationTarget.CLASS, AnnotationTarget.FUNCTION)
|
||||||
|
@PlanteretteBlockMarker
|
||||||
|
annotation class ExecutableBlockMarker
|
||||||
|
|
||||||
|
@Target(AnnotationTarget.TYPE, AnnotationTarget.CLASS, AnnotationTarget.FUNCTION)
|
||||||
|
@PlanteretteBlockMarker
|
||||||
|
annotation class CapabilitiesBlockMarker
|
||||||
|
|
||||||
|
@Target(AnnotationTarget.TYPE, AnnotationTarget.CLASS, AnnotationTarget.FUNCTION)
|
||||||
|
@CapabilitiesBlockMarker
|
||||||
|
annotation class DBusBlockMarker
|
||||||
|
|
||||||
|
@Target(AnnotationTarget.TYPE, AnnotationTarget.CLASS, AnnotationTarget.FUNCTION)
|
||||||
|
@PlanteretteBlockMarker
|
||||||
|
annotation class PermissionsBlockMarker
|
||||||
|
|
||||||
|
@Target(AnnotationTarget.TYPE, AnnotationTarget.CLASS, AnnotationTarget.FUNCTION)
|
||||||
|
@PermissionsBlockMarker
|
||||||
|
annotation class FilesystemBlockMarker
|
||||||
|
|
||||||
|
@PlanteretteBlockMarker
|
||||||
|
data class PlanteretteBlock(
|
||||||
|
var metadata: MetadataBlock,
|
||||||
|
var executable: ExecutableBlock,
|
||||||
|
var capabilities: CapabilitiesBlock,
|
||||||
|
var permissions: PermissionsBlock,
|
||||||
|
var buildGuard: Boolean = false
|
||||||
|
) {
|
||||||
|
// NOTE the two underscores are because this function is only intended to be added at runtime
|
||||||
|
@OptIn(ExperimentalSerializationApi::class)
|
||||||
|
@Suppress("FunctionName")
|
||||||
|
fun __build(args: Array<out String>): PlanteretteBlock {
|
||||||
|
if(!buildGuard) {
|
||||||
|
val manifest = Manifest(metadata.metadata, executable.executable, capabilities.capabilities, permissions.permissions)
|
||||||
|
GoFile.write(args[0].toInt(), Json.encodeToString(manifest))
|
||||||
|
buildGuard = true
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@MetadataBlockMarker
|
||||||
|
data class MetadataBlock(var metadata: Manifest.Metadata)
|
||||||
|
|
||||||
|
@ExecutableBlockMarker
|
||||||
|
data class ExecutableBlock(var executable: Manifest.Executable)
|
||||||
|
|
||||||
|
@CapabilitiesBlockMarker
|
||||||
|
data class CapabilitiesBlock(var capabilities: MutableList<Capability>)
|
||||||
|
|
||||||
|
// TODO add the rest of this
|
||||||
|
@DBusBlockMarker
|
||||||
|
data class DBusBlock(var own: MutableList<String>)
|
||||||
|
|
||||||
|
@PermissionsBlockMarker
|
||||||
|
data class PermissionsBlock(var permissions: MutableList<Capability>)
|
||||||
|
|
||||||
|
@FilesystemBlockMarker
|
||||||
|
class FilesystemBlock(var filesystemPermissions: MutableList<Capability>)
|
||||||
|
|
||||||
|
@PlanteretteBlockMarker
|
||||||
|
fun planterette(init: PlanteretteBlock.() -> Unit) {
|
||||||
|
PlanteretteBlock(MetadataBlock(Manifest.Metadata()), ExecutableBlock(Manifest.Executable()), CapabilitiesBlock(mutableListOf()), PermissionsBlock(mutableListOf())).apply(init)
|
||||||
|
}
|
||||||
|
|
||||||
|
@PlanteretteBlockMarker
|
||||||
|
fun PlanteretteBlock.metadata(init: @MetadataBlockMarker MetadataBlock.() -> Unit) {
|
||||||
|
this.metadata.apply(init)
|
||||||
|
}
|
||||||
|
|
||||||
|
@MetadataBlockMarker
|
||||||
|
fun MetadataBlock.id(id: String) {
|
||||||
|
this.metadata.id = id
|
||||||
|
}
|
||||||
|
|
||||||
|
@MetadataBlockMarker
|
||||||
|
fun MetadataBlock.name(name: String) {
|
||||||
|
this.metadata.name = name
|
||||||
|
}
|
||||||
|
|
||||||
|
@MetadataBlockMarker
|
||||||
|
fun MetadataBlock.description(description: String) {
|
||||||
|
this.metadata.description = description
|
||||||
|
}
|
||||||
|
|
||||||
|
@MetadataBlockMarker
|
||||||
|
fun MetadataBlock.author(author: String) {
|
||||||
|
this.metadata.author = author
|
||||||
|
}
|
||||||
|
|
||||||
|
@MetadataBlockMarker
|
||||||
|
fun MetadataBlock.icon(icon: String) {
|
||||||
|
this.metadata.icon = icon
|
||||||
|
}
|
||||||
|
|
||||||
|
@MetadataBlockMarker
|
||||||
|
fun MetadataBlock.version(number: Int, name: String) {
|
||||||
|
this.metadata.version = number
|
||||||
|
this.metadata.versionName = name
|
||||||
|
}
|
||||||
|
|
||||||
|
@PlanteretteBlockMarker
|
||||||
|
fun PlanteretteBlock.executable(init: @ExecutableBlockMarker ExecutableBlock.() -> Unit) {
|
||||||
|
this.executable.apply(init)
|
||||||
|
}
|
||||||
|
|
||||||
|
@ExecutableBlockMarker
|
||||||
|
fun ExecutableBlock.baseImage(baseImage: String) {
|
||||||
|
this.executable.baseImage = baseImage
|
||||||
|
}
|
||||||
|
|
||||||
|
@ExecutableBlockMarker
|
||||||
|
fun ExecutableBlock.binary(binary: String) {
|
||||||
|
this.executable.binary = binary
|
||||||
|
}
|
||||||
|
|
||||||
|
@ExecutableBlockMarker
|
||||||
|
fun ExecutableBlock.args(vararg args: String) {
|
||||||
|
this.executable.args.addAll(args)
|
||||||
|
}
|
||||||
|
|
||||||
|
@ExecutableBlockMarker
|
||||||
|
fun ExecutableBlock.env(vararg env: Pair<String, String>) {
|
||||||
|
this.executable.env.putAll(env)
|
||||||
|
}
|
||||||
|
|
||||||
|
@PlanteretteBlockMarker
|
||||||
|
fun PlanteretteBlock.capabilities(init: @CapabilitiesBlockMarker CapabilitiesBlock.() -> Unit) {
|
||||||
|
this.capabilities.apply(init)
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun CapabilitiesBlock.addBasic(id: String) {
|
||||||
|
this.capabilities.add(Capability.Basic(id))
|
||||||
|
}
|
||||||
|
|
||||||
|
@CapabilitiesBlockMarker
|
||||||
|
fun CapabilitiesBlock.portal() {
|
||||||
|
addBasic("moe.rosa.capabilities.Portal")
|
||||||
|
}
|
||||||
|
|
||||||
|
@CapabilitiesBlockMarker
|
||||||
|
fun CapabilitiesBlock.secrets() {
|
||||||
|
addBasic("moe.rosa.capabilities.Secrets")
|
||||||
|
}
|
||||||
|
|
||||||
|
@CapabilitiesBlockMarker
|
||||||
|
fun CapabilitiesBlock.developer() {
|
||||||
|
addBasic("moe.rosa.capabilities.Developer")
|
||||||
|
}
|
||||||
|
|
||||||
|
@CapabilitiesBlockMarker
|
||||||
|
fun CapabilitiesBlock.graphics() {
|
||||||
|
addBasic("moe.rosa.capabilities.Graphics")
|
||||||
|
}
|
||||||
|
|
||||||
|
@CapabilitiesBlockMarker
|
||||||
|
fun CapabilitiesBlock.audio() {
|
||||||
|
addBasic("moe.rosa.capabilities.Audio")
|
||||||
|
}
|
||||||
|
|
||||||
|
@CapabilitiesBlockMarker
|
||||||
|
fun CapabilitiesBlock.dbus(init: @DBusBlockMarker DBusBlock.() -> Unit) {
|
||||||
|
val dbus = DBusBlock(mutableListOf())
|
||||||
|
this.capabilities.add(Capability.DBus("moe.rosa.capabilities.DBus", dbus.own))
|
||||||
|
}
|
||||||
|
|
||||||
|
@PlanteretteBlockMarker
|
||||||
|
fun PlanteretteBlock.permissions(init: @PermissionsBlockMarker PermissionsBlock.() -> Unit) {
|
||||||
|
this.permissions.apply(init)
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun PermissionsBlock.addBasic(id: String) {
|
||||||
|
this.permissions.add(Capability.Basic(id))
|
||||||
|
}
|
||||||
|
|
||||||
|
@PermissionsBlockMarker
|
||||||
|
fun PermissionsBlock.notifications() {
|
||||||
|
addBasic("moe.rosa.permissions.Notifications")
|
||||||
|
}
|
||||||
|
|
||||||
|
@PermissionsBlockMarker
|
||||||
|
fun PermissionsBlock.filesystem(init: @FilesystemBlockMarker FilesystemBlock.() -> Unit) {
|
||||||
|
this.permissions.addAll(FilesystemBlock(mutableListOf()).apply(init).filesystemPermissions)
|
||||||
|
}
|
||||||
|
|
||||||
|
@FilesystemBlockMarker
|
||||||
|
fun FilesystemBlock.fileManager() {
|
||||||
|
this.filesystemPermissions.add(Capability.Basic("moe.rosa.permissions.filesystem.FileManager"))
|
||||||
|
}
|
||||||
|
|
||||||
|
@FilesystemBlockMarker
|
||||||
|
fun FilesystemBlock.etc() {
|
||||||
|
this.filesystemPermissions.add(Capability.Basic("moe.rosa.permissions.filesystem.Etc"))
|
||||||
|
}
|
||||||
|
|
||||||
|
@FilesystemBlockMarker
|
||||||
|
fun FilesystemBlock.tmp() {
|
||||||
|
this.filesystemPermissions.add(Capability.Basic("moe.rosa.permissions.filesystem.Tmp"))
|
||||||
|
}
|
||||||
|
|
||||||
|
@PermissionsBlockMarker
|
||||||
|
fun PermissionsBlock.network() {
|
||||||
|
addBasic("moe.rosa.permissions.Network")
|
||||||
|
}
|
||||||
|
|
||||||
|
@PermissionsBlockMarker
|
||||||
|
fun PermissionsBlock.system() {
|
||||||
|
addBasic("moe.rosa.permissions.System")
|
||||||
|
}
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
package moe.rosa.planterette.dsl
|
||||||
|
|
||||||
|
import kotlinx.coroutines.runBlocking
|
||||||
|
import moe.rosa.planterette.api.Capability
|
||||||
|
import moe.rosa.planterette.api.Manifest
|
||||||
|
import kotlin.script.experimental.annotations.KotlinScript
|
||||||
|
import kotlin.script.experimental.api.*
|
||||||
|
import kotlin.script.experimental.dependencies.*
|
||||||
|
import kotlin.script.experimental.dependencies.maven.MavenDependenciesResolver
|
||||||
|
import kotlin.script.experimental.jvm.JvmDependency
|
||||||
|
import kotlin.script.experimental.jvm.dependenciesFromCurrentContext
|
||||||
|
import kotlin.script.experimental.jvm.jvm
|
||||||
|
|
||||||
|
@KotlinScript(
|
||||||
|
fileExtension = "plt.kts",
|
||||||
|
compilationConfiguration = ScriptWithMavenDepsConfiguration::class
|
||||||
|
)
|
||||||
|
open class PltRecipe
|
||||||
|
|
||||||
|
object ScriptWithMavenDepsConfiguration : ScriptCompilationConfiguration(
|
||||||
|
{
|
||||||
|
defaultImports(DependsOn::class, Repository::class)
|
||||||
|
defaultImports.append("moe.rosa.planterette.dsl.*")
|
||||||
|
jvm {
|
||||||
|
dependenciesFromCurrentContext(
|
||||||
|
"script",
|
||||||
|
"kotlin-scripting-dependencies"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
refineConfiguration {
|
||||||
|
onAnnotations(DependsOn::class, Repository::class, handler = ::configureMavenDepsOnAnnotations)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
private val resolver = CompoundDependenciesResolver(FileSystemDependenciesResolver(), MavenDependenciesResolver())
|
||||||
|
|
||||||
|
fun configureMavenDepsOnAnnotations(context: ScriptConfigurationRefinementContext): ResultWithDiagnostics<ScriptCompilationConfiguration> {
|
||||||
|
val annotations = context.collectedData?.get(ScriptCollectedData.collectedAnnotations)?.takeIf { it.isNotEmpty() }
|
||||||
|
?: return context.compilationConfiguration.asSuccess()
|
||||||
|
return runBlocking {
|
||||||
|
resolver.resolveFromScriptSourceAnnotations(annotations)
|
||||||
|
}.onSuccess {
|
||||||
|
context.compilationConfiguration.with {
|
||||||
|
dependencies.append(JvmDependency(it))
|
||||||
|
}.asSuccess()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
package moe.rosa.planterette.jni
|
||||||
|
|
||||||
|
import java.io.File
|
||||||
|
import java.nio.file.Files
|
||||||
|
|
||||||
|
object GoFile {
|
||||||
|
external fun write(fd: Int, str: String)
|
||||||
|
init {
|
||||||
|
val libname = "pltbuild.so"
|
||||||
|
val url = GoFile::class.java.getResource("/$libname")
|
||||||
|
val tmpDir = Files.createTempDirectory("pltbuild").toFile()
|
||||||
|
tmpDir.deleteOnExit()
|
||||||
|
val nativeLibTmpFile = File(tmpDir, libname)
|
||||||
|
url?.openStream().use {
|
||||||
|
Files.copy(it!!, nativeLibTmpFile.toPath())
|
||||||
|
}
|
||||||
|
System.load(nativeLibTmpFile.absolutePath)
|
||||||
|
}
|
||||||
|
}
|
||||||
5
cmd/plt-fetch/main.go
Normal file
5
cmd/plt-fetch/main.go
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
|
||||||
|
}
|
||||||
9
cmd/plt-fetch/main_test.go
Normal file
9
cmd/plt-fetch/main_test.go
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestPltFetch(t *testing.T) {
|
||||||
|
|
||||||
|
}
|
||||||
5
cmd/plt-pkg/main.go
Normal file
5
cmd/plt-pkg/main.go
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
|
||||||
|
}
|
||||||
9
cmd/plt-pkg/main_test.go
Normal file
9
cmd/plt-pkg/main_test.go
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestPltPkg(t *testing.T) {
|
||||||
|
|
||||||
|
}
|
||||||
5
cmd/plt-server/main.go
Normal file
5
cmd/plt-server/main.go
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
|
||||||
|
}
|
||||||
9
cmd/plt-server/main_test.go
Normal file
9
cmd/plt-server/main_test.go
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestPltServer(t *testing.T) {
|
||||||
|
|
||||||
|
}
|
||||||
5
cmd/plt-updated/main.go
Normal file
5
cmd/plt-updated/main.go
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
|
||||||
|
}
|
||||||
9
cmd/plt-updated/main_test.go
Normal file
9
cmd/plt-updated/main_test.go
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestPltUpdated(t *testing.T) {
|
||||||
|
|
||||||
|
}
|
||||||
5
go.mod
Normal file
5
go.mod
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
module rosa.moe/planterette
|
||||||
|
|
||||||
|
go 1.24.9
|
||||||
|
|
||||||
|
require hakurei.app v0.3.1 // indirect
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
org.gradle.caching=true
|
|
||||||
org.gradle.configuration-cache=true
|
|
||||||
kotlin.mpp.applyDefaultHierarchyTemplate=false
|
|
||||||
@@ -1,20 +0,0 @@
|
|||||||
[versions]
|
|
||||||
kotlin = "2.2.0"
|
|
||||||
kotlinxDatetime = "0.6.1"
|
|
||||||
kotlinxSerializationJSON = "1.7.3"
|
|
||||||
kotlinxCoroutines = "1.9.0"
|
|
||||||
kotlinxIo = "0.8.0"
|
|
||||||
|
|
||||||
[libraries]
|
|
||||||
kotlinGradlePlugin = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version.ref = "kotlin" }
|
|
||||||
kotlinxDatetime = { module = "org.jetbrains.kotlinx:kotlinx-datetime", version.ref = "kotlinxDatetime" }
|
|
||||||
kotlinxSerialization = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kotlinxSerializationJSON" }
|
|
||||||
kotlinxCoroutines = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "kotlinxCoroutines" }
|
|
||||||
kotlinxIo = { module = "org.jetbrains.kotlinx:kotlinx-io-core", version.ref = "kotlinxIo" }
|
|
||||||
|
|
||||||
[bundles]
|
|
||||||
kotlinxEcosystem = ["kotlinxDatetime", "kotlinxSerialization", "kotlinxCoroutines", "kotlinxIo"]
|
|
||||||
|
|
||||||
[plugins]
|
|
||||||
kotlinPluginSerialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" }
|
|
||||||
kotlinPluginMultiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "kotlin" }
|
|
||||||
@@ -1,26 +0,0 @@
|
|||||||
plugins {
|
|
||||||
kotlin("multiplatform")
|
|
||||||
alias(libs.plugins.kotlinPluginSerialization)
|
|
||||||
}
|
|
||||||
kotlin {
|
|
||||||
jvm()
|
|
||||||
val nativeTarget = if(System.getProperty("os.arch") == "aarch64") {
|
|
||||||
linuxArm64("native")
|
|
||||||
} else {
|
|
||||||
linuxX64("native")
|
|
||||||
}
|
|
||||||
nativeTarget.binaries {
|
|
||||||
sharedLib {
|
|
||||||
baseName = "libplt"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
sourceSets {
|
|
||||||
commonMain.dependencies {
|
|
||||||
implementation(libs.bundles.kotlinxEcosystem)
|
|
||||||
}
|
|
||||||
commonTest.dependencies {
|
|
||||||
implementation(libs.bundles.kotlinxEcosystem)
|
|
||||||
implementation(kotlin("test"))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,51 +0,0 @@
|
|||||||
package app.hakurei.planterette.api
|
|
||||||
|
|
||||||
import app.hakurei.planterette.api.AbsolutePath.Companion.isAbsolute
|
|
||||||
import kotlinx.io.files.Path
|
|
||||||
import kotlinx.serialization.KSerializer
|
|
||||||
import kotlinx.serialization.Serializable
|
|
||||||
import kotlinx.serialization.Serializer
|
|
||||||
import kotlinx.serialization.Transient
|
|
||||||
import kotlinx.serialization.descriptors.PrimitiveKind
|
|
||||||
import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor
|
|
||||||
import kotlinx.serialization.descriptors.SerialDescriptor
|
|
||||||
import kotlinx.serialization.encoding.Decoder
|
|
||||||
import kotlinx.serialization.encoding.Encoder
|
|
||||||
|
|
||||||
/**
|
|
||||||
* AbsolutePath holds a pathname checked to be absolute.
|
|
||||||
* @constructor checks pathname and returns a new AbsolutePath if pathname is absolute.
|
|
||||||
*/
|
|
||||||
@Serializable(with = AbsolutePathSerializer::class)
|
|
||||||
data class AbsolutePath(val pathname: String, @Transient val path: Path = Path(pathname)) {
|
|
||||||
init {
|
|
||||||
if(!isAbsolute(pathname)) {
|
|
||||||
throw AbsolutePathException(pathname)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
operator fun plus(other: String): AbsolutePath {
|
|
||||||
return AbsolutePath(pathname + other)
|
|
||||||
}
|
|
||||||
companion object {
|
|
||||||
fun isAbsolute(pathname: String): Boolean {
|
|
||||||
return Path(pathname).isAbsolute
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
object AbsolutePathSerializer : KSerializer<AbsolutePath> {
|
|
||||||
override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor(this::class.qualifiedName!!, PrimitiveKind.STRING)
|
|
||||||
override fun serialize(encoder: Encoder, value: AbsolutePath) {
|
|
||||||
encoder.encodeString(value.pathname)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun deserialize(decoder: Decoder): AbsolutePath {
|
|
||||||
val path = decoder.decodeString()
|
|
||||||
return AbsolutePath(path)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* AbsolutePathException is returned by @see AbsolutePath() and holds the invalid pathname.
|
|
||||||
*/
|
|
||||||
data class AbsolutePathException(val pathname: String) : IllegalArgumentException("Path $pathname is not absolute")
|
|
||||||
@@ -1,209 +0,0 @@
|
|||||||
package app.hakurei.planterette.api
|
|
||||||
|
|
||||||
import kotlinx.serialization.SerialName
|
|
||||||
import kotlinx.serialization.Serializable
|
|
||||||
|
|
||||||
/**
|
|
||||||
* HakureiConfig is used to seal an app implementation.
|
|
||||||
* @param id reverse-DNS style arbitrary identifier string from config; Passed to wayland security-context-v1 as application ID and used as part of defaults in dbus session proxy
|
|
||||||
*
|
|
||||||
* @param path absolute path to executable file
|
|
||||||
* @param args final args passed to container init
|
|
||||||
*
|
|
||||||
* @param enablements system services to make available in the container
|
|
||||||
*
|
|
||||||
* @param sessionBus session D-Bus proxy configuration; null makes session bus proxy assume built-in defaults
|
|
||||||
* @param systemBus system D-Bus proxy configuration; null disables system bus proxy
|
|
||||||
* @param directWayland direct access to the wayland socket; when this gets set no attempt is made to attach security-context-v1 and the bare socket is mounted to the sandbox
|
|
||||||
*
|
|
||||||
* @param username passwd username in container, defaults to passwd name of target uid or chronos
|
|
||||||
* @param shell absolute path to shell
|
|
||||||
* @param data absolute path to home directory in the init mount namespace
|
|
||||||
* @param dir directory to ender and use as home in the container mount namespace, null for Data
|
|
||||||
* @param extraPerms extra ACL ops, dispatches before container init
|
|
||||||
*
|
|
||||||
* @param identity numerical application id, used for init user namespace credentials
|
|
||||||
* @param groups list of supplementary groups inherited by container processes
|
|
||||||
*
|
|
||||||
* @param container abstract container configuration baseline
|
|
||||||
*/
|
|
||||||
@Serializable
|
|
||||||
data class HakureiConfig(
|
|
||||||
val id: String,
|
|
||||||
|
|
||||||
val path: AbsolutePath? = null,
|
|
||||||
val args: List<String>,
|
|
||||||
|
|
||||||
val enablements: Byte,
|
|
||||||
|
|
||||||
@SerialName("session_bus") val sessionBus: DbusConfig? = null,
|
|
||||||
@SerialName("system_bus") val systemBus: DbusConfig? = null,
|
|
||||||
@SerialName("direct_wayland") val directWayland: Boolean? = null,
|
|
||||||
|
|
||||||
val username: String? = null,
|
|
||||||
val shell: AbsolutePath,
|
|
||||||
val data: AbsolutePath,
|
|
||||||
val dir: AbsolutePath,
|
|
||||||
@SerialName("extra_perms") val extraPerms: List<ExtraPermConfig>? = null,
|
|
||||||
|
|
||||||
val identity: Int,
|
|
||||||
val groups: List<String>,
|
|
||||||
|
|
||||||
val container: ContainerConfig
|
|
||||||
) {
|
|
||||||
@Serializable
|
|
||||||
data class DbusConfig(
|
|
||||||
val see: List<String>? = null,
|
|
||||||
val talk: List<String>? = null,
|
|
||||||
val own: List<String>? = null,
|
|
||||||
val call: Map<String, String>? = null,
|
|
||||||
val broadcast: Map<String, String>? = null,
|
|
||||||
|
|
||||||
val log: Boolean? = null,
|
|
||||||
val filter: Boolean
|
|
||||||
)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* ExtraPermConfig describes an ACL update op.
|
|
||||||
*/
|
|
||||||
@Serializable
|
|
||||||
data class ExtraPermConfig(
|
|
||||||
val ensure: Boolean? = null,
|
|
||||||
val path: AbsolutePath,
|
|
||||||
@SerialName("r") val read: Boolean? = null,
|
|
||||||
@SerialName("w") val write: Boolean? = null,
|
|
||||||
@SerialName("x") val execute: Boolean? = null
|
|
||||||
)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* ContainerConfig describes the container configuration baseline to which the app implementation adds upon.
|
|
||||||
* @param hostname container hostname
|
|
||||||
* @param waitDelay duration to wait for after interrupting a container's initial process in nanoseconds; a negative value causes the container to be terminated immediately on cancellation
|
|
||||||
* @param seccompFlags extra seccomp flags
|
|
||||||
* @param seccompPresets extra seccomp presets
|
|
||||||
* @param seccompCompat disable project-specific filter extensions
|
|
||||||
* @param devel allow ptrace and friends
|
|
||||||
* @param userns allow userns creation in container
|
|
||||||
* @param net share host net namespace
|
|
||||||
* @param tty allow dangerous terminal I/O
|
|
||||||
* @param multiarch allow multiarch
|
|
||||||
*
|
|
||||||
* @param env initial process environment variables
|
|
||||||
* @param mapRealUid map target user uid to privileged user uid in the user namespace
|
|
||||||
*
|
|
||||||
* @param device pass through all devices
|
|
||||||
* @param filesystem container host filesystem bind mounts
|
|
||||||
* @param link create symlinks inside container filesystem
|
|
||||||
*
|
|
||||||
* @param autoRoot automatically bind mount top-level directories to container root; the zero value disables this behaviour
|
|
||||||
* @param rootFlags extra flags for AutoRoot
|
|
||||||
*
|
|
||||||
* @param etc read-only /etc directory
|
|
||||||
* @param autoEtc automatically set up /etc symlinks
|
|
||||||
*/
|
|
||||||
@Serializable
|
|
||||||
data class ContainerConfig(
|
|
||||||
val hostname: String?,
|
|
||||||
@SerialName("wait_delay") val waitDelay: Int? = null,
|
|
||||||
@SerialName("seccomp_flags") val seccompFlags: Int,
|
|
||||||
@SerialName("seccomp_presets") val seccompPresets: Int,
|
|
||||||
@SerialName("seccomp_compat") val seccompCompat: Boolean? = null,
|
|
||||||
|
|
||||||
val devel: Boolean? = null,
|
|
||||||
val userns: Boolean? = null,
|
|
||||||
val net: Boolean? = null,
|
|
||||||
val tty: Boolean? = null,
|
|
||||||
val multiarch: Boolean? = null,
|
|
||||||
|
|
||||||
val env: Map<String, String>,
|
|
||||||
@SerialName("map_real_uid") val mapRealUid: Boolean,
|
|
||||||
|
|
||||||
val device: Boolean?,
|
|
||||||
val filesystem: List<FilesystemConfig>,
|
|
||||||
@SerialName("symlink") val link: List<LinkConfig>,
|
|
||||||
|
|
||||||
@SerialName("auto_root") val autoRoot: AbsolutePath,
|
|
||||||
@SerialName("root_flags") val rootFlags: Int,
|
|
||||||
|
|
||||||
val etc: AbsolutePath?,
|
|
||||||
|
|
||||||
@SerialName("auto_etc") val autoEtc: Boolean,
|
|
||||||
)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* FilesystemConfig is an abstract representation of a bind mount.
|
|
||||||
* @param dst mount point in container, same as src if empty
|
|
||||||
* @param src host filesystem path to make available to the container
|
|
||||||
* @param write do not mount filesystem read-only
|
|
||||||
* @param device do not disable device files
|
|
||||||
* @param must fail if the bind mount cannot be established for any reason
|
|
||||||
*/
|
|
||||||
@Serializable
|
|
||||||
data class FilesystemConfig(
|
|
||||||
val dst: AbsolutePath? = null,
|
|
||||||
val src: AbsolutePath,
|
|
||||||
val write: Boolean? = null,
|
|
||||||
@SerialName("dev") val device: Boolean? = null,
|
|
||||||
@SerialName("require") val must: Boolean? = null
|
|
||||||
)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param target symlink target in container
|
|
||||||
* @param linkname linkname the symlink points to; prepend '*' to dereference an absolute pathname on host
|
|
||||||
*/
|
|
||||||
@Serializable
|
|
||||||
data class LinkConfig(
|
|
||||||
val target: AbsolutePath,
|
|
||||||
val linkname: String
|
|
||||||
)
|
|
||||||
enum class Enablement(val value: Int) {
|
|
||||||
Wayland(1 shl 0),
|
|
||||||
X11(1 shl 1),
|
|
||||||
DBus(1 shl 2),
|
|
||||||
Pulse(1 shl 3);
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
fun enablements(vararg enablements: Enablement): Byte {
|
|
||||||
return enablements.orOf(Enablement::value).toByte()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
enum class SeccompFilterPreset(val value: Int) {
|
|
||||||
Ext(1 shl 0),
|
|
||||||
DenyNS(1 shl 1),
|
|
||||||
DenyTTY(1 shl 2),
|
|
||||||
DenyDevel(1 shl 3),
|
|
||||||
Linux32(1 shl 4);
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
fun filterPresets(vararg filterPresets: SeccompFilterPreset): Int {
|
|
||||||
return filterPresets.orOf(SeccompFilterPreset::value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
enum class HakureiExportFlag(val value: Int) {
|
|
||||||
Multiarch(1 shl 0),
|
|
||||||
CAN(1 shl 1),
|
|
||||||
Bluetooth(1 shl 2);
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
fun exportFlags(vararg exportFlags: HakureiExportFlag): Int {
|
|
||||||
return exportFlags.orOf(HakureiExportFlag::value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
enum class RootFlag(val value: Int) {
|
|
||||||
Optional(1 shl 0),
|
|
||||||
Writable(1 shl 1),
|
|
||||||
Device(1 shl 2);
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
fun rootFlags(vararg rootFlags: RootFlag): Int {
|
|
||||||
return rootFlags.orOf(RootFlag::value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,35 +0,0 @@
|
|||||||
package app.hakurei.planterette.api
|
|
||||||
|
|
||||||
import kotlinx.serialization.Serializable
|
|
||||||
|
|
||||||
@Serializable
|
|
||||||
data class PackageManifest(
|
|
||||||
var hakureiConfig: HakureiConfig,
|
|
||||||
var id: String,
|
|
||||||
var version: Version,
|
|
||||||
var name: String,
|
|
||||||
var description: String,
|
|
||||||
var architecture: MutableList<Architecture>,
|
|
||||||
var baseImage: BaseImage
|
|
||||||
) {
|
|
||||||
@Serializable
|
|
||||||
data class Version(val version: String, val canonicalVersion: UInt)
|
|
||||||
|
|
||||||
@Serializable
|
|
||||||
enum class Architecture {
|
|
||||||
X86_64,
|
|
||||||
ARM64,
|
|
||||||
}
|
|
||||||
@Serializable
|
|
||||||
data class BaseImage(val type: Type) {
|
|
||||||
@Serializable
|
|
||||||
enum class Type {
|
|
||||||
DEBIAN,
|
|
||||||
CHIMERA,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
companion object {
|
|
||||||
val fileList = listOf("planterette.json", "icon.png", "image.tar")
|
|
||||||
val signatureFile = "package.sig"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,48 +0,0 @@
|
|||||||
package app.hakurei.planterette.api
|
|
||||||
|
|
||||||
@Suppress("unused")
|
|
||||||
object Paths {
|
|
||||||
const val FHS_ROOT = "/"
|
|
||||||
const val FHS_ETC = "/etc/"
|
|
||||||
const val FHS_TMP = "/tmp/"
|
|
||||||
|
|
||||||
const val FHS_RUN = "/run/"
|
|
||||||
const val FHS_RUN_USER = FHS_RUN + "user/"
|
|
||||||
|
|
||||||
const val FHS_USR = "/usr/"
|
|
||||||
const val FHS_USR_BIN = FHS_USR + "bin/"
|
|
||||||
|
|
||||||
const val FHS_VAR = "/var/"
|
|
||||||
const val FHS_VAR_LIB = FHS_VAR + "lib/"
|
|
||||||
const val FHS_VAR_EMPTY = FHS_VAR + "empty/"
|
|
||||||
|
|
||||||
const val FHS_DEV = "/dev/"
|
|
||||||
const val FHS_PROC = "/proc/"
|
|
||||||
const val FHS_PROC_SYS = FHS_PROC + "sys/"
|
|
||||||
const val FHS_SYS = "/sys/"
|
|
||||||
|
|
||||||
const val NONEXISTENT = FHS_PROC + "nonexistent"
|
|
||||||
const val SYSROOT_DIR = "sysroot"
|
|
||||||
const val HOST_DIR = "host"
|
|
||||||
const val HOST_PATH = FHS_ROOT + HOST_DIR
|
|
||||||
const val SYSROOT_PATH = FHS_ROOT + SYSROOT_DIR
|
|
||||||
|
|
||||||
val AbsFHSRoot = AbsolutePath(FHS_ROOT)
|
|
||||||
val AbsFHSEtc = AbsolutePath(FHS_ETC)
|
|
||||||
val AbsFHSTmp = AbsolutePath(FHS_TMP)
|
|
||||||
|
|
||||||
val AbsFHSRun = AbsolutePath(FHS_RUN)
|
|
||||||
val AbsFHSRunUser = AbsolutePath(FHS_RUN_USER)
|
|
||||||
|
|
||||||
val AbsFHSUsrBin = AbsolutePath(FHS_USR_BIN)
|
|
||||||
|
|
||||||
val AbsFHSVar = AbsolutePath(FHS_VAR)
|
|
||||||
val AbsFHSVarLib = AbsolutePath(FHS_VAR_LIB)
|
|
||||||
|
|
||||||
val AbsFHSDev = AbsolutePath(FHS_DEV)
|
|
||||||
val AbsFHSProc = AbsolutePath(FHS_PROC)
|
|
||||||
val AbsFHSSys = AbsolutePath(FHS_SYS)
|
|
||||||
|
|
||||||
val AbsNonexistent = AbsolutePath(NONEXISTENT)
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
package app.hakurei.planterette.api
|
|
||||||
|
|
||||||
inline fun <T> Array<out T>.orOf(selector: (T) -> Int): Int {
|
|
||||||
var sum = 0
|
|
||||||
for (element in this) {
|
|
||||||
sum = sum or selector(element)
|
|
||||||
}
|
|
||||||
return sum
|
|
||||||
}
|
|
||||||
@@ -1,26 +0,0 @@
|
|||||||
package app.hakurei.planterette.api
|
|
||||||
|
|
||||||
import kotlin.test.Test
|
|
||||||
import kotlin.test.assertEquals
|
|
||||||
import kotlin.test.assertFailsWith
|
|
||||||
import kotlin.test.assertIs
|
|
||||||
|
|
||||||
class AbsolutePathTest {
|
|
||||||
@Test
|
|
||||||
fun testAbsolutePath() {
|
|
||||||
val path = AbsolutePath("/dev/null")
|
|
||||||
assertIs<AbsolutePath>(path)
|
|
||||||
}
|
|
||||||
@Test
|
|
||||||
fun testRelativePath() {
|
|
||||||
assertFailsWith(AbsolutePathException::class) {
|
|
||||||
AbsolutePath("./test")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@Test
|
|
||||||
fun testPathJoin() {
|
|
||||||
val want = AbsolutePath("/dev/null")
|
|
||||||
val base = AbsolutePath("/dev/")
|
|
||||||
assertEquals(want, base + "null")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,333 +0,0 @@
|
|||||||
package app.hakurei.planterette.api
|
|
||||||
|
|
||||||
import app.hakurei.planterette.api.HakureiConfig.Enablement.*
|
|
||||||
import app.hakurei.planterette.api.HakureiConfig.SeccompFilterPreset.*
|
|
||||||
import app.hakurei.planterette.api.HakureiConfig.HakureiExportFlag.*
|
|
||||||
import app.hakurei.planterette.api.HakureiConfig.RootFlag.*
|
|
||||||
|
|
||||||
import kotlinx.serialization.ExperimentalSerializationApi
|
|
||||||
import kotlinx.serialization.encodeToString
|
|
||||||
import kotlinx.serialization.json.Json
|
|
||||||
import kotlin.test.Test
|
|
||||||
import kotlin.test.assertEquals
|
|
||||||
|
|
||||||
class HakureiConfigTest {
|
|
||||||
@OptIn(ExperimentalSerializationApi::class)
|
|
||||||
val format = Json {
|
|
||||||
explicitNulls = false
|
|
||||||
prettyPrint = true
|
|
||||||
}
|
|
||||||
val want: String = """
|
|
||||||
{
|
|
||||||
"id": "org.chromium.Chromium",
|
|
||||||
"path": "/run/current-system/sw/bin/chromium",
|
|
||||||
"args": [
|
|
||||||
"chromium",
|
|
||||||
"--ignore-gpu-blocklist",
|
|
||||||
"--disable-smooth-scrolling",
|
|
||||||
"--enable-features=UseOzonePlatform",
|
|
||||||
"--ozone-platform=wayland"
|
|
||||||
],
|
|
||||||
"enablements": 13,
|
|
||||||
"session_bus": {
|
|
||||||
"see": null,
|
|
||||||
"talk": [
|
|
||||||
"org.freedesktop.Notifications",
|
|
||||||
"org.freedesktop.FileManager1",
|
|
||||||
"org.freedesktop.ScreenSaver",
|
|
||||||
"org.freedesktop.secrets",
|
|
||||||
"org.kde.kwalletd5",
|
|
||||||
"org.kde.kwalletd6",
|
|
||||||
"org.gnome.SessionManager"
|
|
||||||
],
|
|
||||||
"own": [
|
|
||||||
"org.chromium.Chromium.*",
|
|
||||||
"org.mpris.MediaPlayer2.org.chromium.Chromium.*",
|
|
||||||
"org.mpris.MediaPlayer2.chromium.*"
|
|
||||||
],
|
|
||||||
"call": {
|
|
||||||
"org.freedesktop.portal.*": "*"
|
|
||||||
},
|
|
||||||
"broadcast": {
|
|
||||||
"org.freedesktop.portal.*": "@/org/freedesktop/portal/*"
|
|
||||||
},
|
|
||||||
"filter": true
|
|
||||||
},
|
|
||||||
"system_bus": {
|
|
||||||
"see": null,
|
|
||||||
"talk": [
|
|
||||||
"org.bluez",
|
|
||||||
"org.freedesktop.Avahi",
|
|
||||||
"org.freedesktop.UPower"
|
|
||||||
],
|
|
||||||
"own": null,
|
|
||||||
"call": null,
|
|
||||||
"broadcast": null,
|
|
||||||
"filter": true
|
|
||||||
},
|
|
||||||
"username": "chronos",
|
|
||||||
"shell": "/run/current-system/sw/bin/zsh",
|
|
||||||
"data": "/var/lib/hakurei/u0/org.chromium.Chromium",
|
|
||||||
"dir": "/data/data/org.chromium.Chromium",
|
|
||||||
"extra_perms": [
|
|
||||||
{
|
|
||||||
"ensure": true,
|
|
||||||
"path": "/var/lib/hakurei/u0",
|
|
||||||
"x": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"path": "/var/lib/hakurei/u0/org.chromium.Chromium",
|
|
||||||
"r": true,
|
|
||||||
"w": true,
|
|
||||||
"x": true
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"identity": 9,
|
|
||||||
"groups": [
|
|
||||||
"video",
|
|
||||||
"dialout",
|
|
||||||
"plugdev"
|
|
||||||
],
|
|
||||||
"container": {
|
|
||||||
"hostname": "localhost",
|
|
||||||
"wait_delay": -1,
|
|
||||||
"seccomp_flags": 1,
|
|
||||||
"seccomp_presets": 1,
|
|
||||||
"seccomp_compat": true,
|
|
||||||
"devel": true,
|
|
||||||
"userns": true,
|
|
||||||
"net": true,
|
|
||||||
"tty": true,
|
|
||||||
"multiarch": true,
|
|
||||||
"env": {
|
|
||||||
"GOOGLE_API_KEY": "AIzaSyBHDrl33hwRp4rMQY0ziRbj8K9LPA6vUCY",
|
|
||||||
"GOOGLE_DEFAULT_CLIENT_ID": "77185425430.apps.googleusercontent.com",
|
|
||||||
"GOOGLE_DEFAULT_CLIENT_SECRET": "OTJgUOQcT7lO7GsGZq2G4IlT"
|
|
||||||
},
|
|
||||||
"map_real_uid": true,
|
|
||||||
"device": true,
|
|
||||||
"filesystem": [
|
|
||||||
{
|
|
||||||
"dst": "/tmp/",
|
|
||||||
"src": "/proc/nonexistent",
|
|
||||||
"write": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"src": "/nix/store"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"src": "/run/current-system"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"src": "/run/opengl-driver"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"src": "/var/db/nix-channels"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"dst": "/data/data/org.chromium.Chromium",
|
|
||||||
"src": "/var/lib/hakurei/u0/org.chromium.Chromium",
|
|
||||||
"write": true,
|
|
||||||
"require": true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"src": "/dev/dri",
|
|
||||||
"dev": true
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"symlink": [
|
|
||||||
{
|
|
||||||
"target": "/run/user/65534",
|
|
||||||
"linkname": "/run/user/150"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"auto_root": "/var/lib/hakurei/base/org.debian",
|
|
||||||
"root_flags": 2,
|
|
||||||
"etc": "/etc/",
|
|
||||||
"auto_etc": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
""".trimIndent()
|
|
||||||
val testConfig = HakureiConfig(
|
|
||||||
id = "org.chromium.Chromium",
|
|
||||||
path = Paths.AbsFHSRun + "current-system/sw/bin/chromium",
|
|
||||||
args = listOf(
|
|
||||||
"chromium",
|
|
||||||
"--ignore-gpu-blocklist",
|
|
||||||
"--disable-smooth-scrolling",
|
|
||||||
"--enable-features=UseOzonePlatform",
|
|
||||||
"--ozone-platform=wayland",
|
|
||||||
),
|
|
||||||
enablements = HakureiConfig.Enablement.enablements(Wayland, DBus, Pulse),
|
|
||||||
sessionBus = HakureiConfig.DbusConfig(
|
|
||||||
talk = listOf(
|
|
||||||
"org.freedesktop.Notifications",
|
|
||||||
"org.freedesktop.FileManager1",
|
|
||||||
"org.freedesktop.ScreenSaver",
|
|
||||||
"org.freedesktop.secrets",
|
|
||||||
"org.kde.kwalletd5",
|
|
||||||
"org.kde.kwalletd6",
|
|
||||||
"org.gnome.SessionManager"
|
|
||||||
),
|
|
||||||
own = listOf(
|
|
||||||
"org.chromium.Chromium.*",
|
|
||||||
"org.mpris.MediaPlayer2.org.chromium.Chromium.*",
|
|
||||||
"org.mpris.MediaPlayer2.chromium.*"
|
|
||||||
),
|
|
||||||
call = mapOf("org.freedesktop.portal.*" to "*"),
|
|
||||||
broadcast = mapOf("org.freedesktop.portal.*" to "@/org/freedesktop/portal/*"),
|
|
||||||
log = false,
|
|
||||||
filter = true,
|
|
||||||
),
|
|
||||||
systemBus = HakureiConfig.DbusConfig(
|
|
||||||
talk = listOf("org.bluez", "org.freedesktop.Avahi", "org.freedesktop.UPower"),
|
|
||||||
log = false,
|
|
||||||
filter = true,
|
|
||||||
),
|
|
||||||
directWayland = false,
|
|
||||||
|
|
||||||
username = "chronos",
|
|
||||||
shell = Paths.AbsFHSRun + "current-system/sw/bin/zsh",
|
|
||||||
data = Paths.AbsFHSVarLib + "hakurei/u0/org.chromium.Chromium",
|
|
||||||
dir = AbsolutePath("/data/data/org.chromium.Chromium"),
|
|
||||||
extraPerms = listOf(
|
|
||||||
HakureiConfig.ExtraPermConfig(path = Paths.AbsFHSVarLib + "hakurei/u0", ensure = true, execute = true),
|
|
||||||
HakureiConfig.ExtraPermConfig(path = Paths.AbsFHSVarLib + "hakurei/u0/org.chromium.Chromium", read = true, write = true, execute = true)
|
|
||||||
),
|
|
||||||
identity = 9,
|
|
||||||
groups = listOf("video", "dialout", "plugdev"),
|
|
||||||
container = HakureiConfig.ContainerConfig(
|
|
||||||
hostname = "localhost",
|
|
||||||
devel = true,
|
|
||||||
userns = true,
|
|
||||||
net = true,
|
|
||||||
device = true,
|
|
||||||
waitDelay = -1,
|
|
||||||
seccompFlags = HakureiConfig.HakureiExportFlag.exportFlags(Multiarch),
|
|
||||||
seccompPresets = HakureiConfig.SeccompFilterPreset.filterPresets(Ext),
|
|
||||||
seccompCompat = true,
|
|
||||||
tty = true,
|
|
||||||
multiarch = true,
|
|
||||||
mapRealUid = true,
|
|
||||||
env = mapOf(
|
|
||||||
"GOOGLE_API_KEY" to "AIzaSyBHDrl33hwRp4rMQY0ziRbj8K9LPA6vUCY",
|
|
||||||
"GOOGLE_DEFAULT_CLIENT_ID" to "77185425430.apps.googleusercontent.com",
|
|
||||||
"GOOGLE_DEFAULT_CLIENT_SECRET" to "OTJgUOQcT7lO7GsGZq2G4IlT"
|
|
||||||
),
|
|
||||||
filesystem = listOf(
|
|
||||||
HakureiConfig.FilesystemConfig(dst = Paths.AbsFHSTmp, src = Paths.AbsNonexistent, write = true),
|
|
||||||
HakureiConfig.FilesystemConfig(src = AbsolutePath("/nix/store")),
|
|
||||||
HakureiConfig.FilesystemConfig(src = Paths.AbsFHSRun + "current-system"),
|
|
||||||
HakureiConfig.FilesystemConfig(src = Paths.AbsFHSRun + "opengl-driver"),
|
|
||||||
HakureiConfig.FilesystemConfig(src = Paths.AbsFHSVar + "db/nix-channels"),
|
|
||||||
HakureiConfig.FilesystemConfig(src = Paths.AbsFHSVarLib + "hakurei/u0/org.chromium.Chromium", dst = AbsolutePath("/data/data/org.chromium.Chromium"), write = true, must = true),
|
|
||||||
HakureiConfig.FilesystemConfig(src = Paths.AbsFHSDev + "dri", device = true)
|
|
||||||
),
|
|
||||||
link = listOf(HakureiConfig.LinkConfig(Paths.AbsFHSRunUser + "65534", Paths.FHS_RUN_USER + "150")),
|
|
||||||
autoRoot = Paths.AbsFHSVarLib + "hakurei/base/org.debian",
|
|
||||||
rootFlags = HakureiConfig.RootFlag.rootFlags(Writable),
|
|
||||||
etc = Paths.AbsFHSEtc,
|
|
||||||
autoEtc = true
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
internal fun compare(want: HakureiConfig, test: HakureiConfig) {
|
|
||||||
assertEquals(want.id, test.id, "id")
|
|
||||||
|
|
||||||
assertEquals(want.path, test.path, "path")
|
|
||||||
assertEquals(want.args, test.args, "args")
|
|
||||||
|
|
||||||
assertEquals(want.enablements, test.enablements, "enablements")
|
|
||||||
|
|
||||||
assertEquals(want.sessionBus?.see, test.sessionBus?.see, "sessionBus.see")
|
|
||||||
assertEquals(want.sessionBus?.talk, test.sessionBus?.talk, "sessionBus.talk")
|
|
||||||
assertEquals(want.sessionBus?.own, test.sessionBus?.own, "sessionBus.own")
|
|
||||||
assertEquals(want.sessionBus?.broadcast, test.sessionBus?.broadcast, "sessionBus.broadcast")
|
|
||||||
assertEquals(want.sessionBus?.log ?: false, test.sessionBus?.log, "sessionBus.log")
|
|
||||||
assertEquals(want.sessionBus?.filter, test.sessionBus?.filter, "sessionBus.filter")
|
|
||||||
|
|
||||||
assertEquals(want.systemBus?.see, test.systemBus?.see, "systemBus.see")
|
|
||||||
assertEquals(want.systemBus?.talk, test.systemBus?.talk, "systemBus.talk")
|
|
||||||
assertEquals(want.systemBus?.own, test.systemBus?.own, "systemBus.own")
|
|
||||||
assertEquals(want.systemBus?.broadcast, test.systemBus?.broadcast, "systemBus.broadcast")
|
|
||||||
assertEquals(want.systemBus?.log ?: false, test.systemBus?.log, "systemBus.log")
|
|
||||||
assertEquals(want.systemBus?.filter, test.systemBus?.filter, "systemBus.filter")
|
|
||||||
|
|
||||||
assertEquals(want.directWayland ?: false, test.directWayland, "directWayland")
|
|
||||||
assertEquals(want.username, test.username, "username")
|
|
||||||
assertEquals(want.shell, test.shell, "shell")
|
|
||||||
assertEquals(want.data, test.data, "data")
|
|
||||||
assertEquals(want.dir, test.dir, "dir")
|
|
||||||
assertEquals(want.extraPerms, test.extraPerms, "extraPerms")
|
|
||||||
|
|
||||||
assertEquals(want.identity, test.identity, "identity")
|
|
||||||
assertEquals(want.groups, test.groups, "groups")
|
|
||||||
|
|
||||||
assertEquals(want.container.waitDelay, test.container.waitDelay, "waitDelay")
|
|
||||||
assertEquals(want.container.seccompFlags, test.container.seccompFlags, "seccompFlags")
|
|
||||||
assertEquals(want.container.seccompPresets, test.container.seccompPresets, "seccompPresets")
|
|
||||||
assertEquals(want.container.seccompCompat, test.container.seccompCompat, "seccompCompat")
|
|
||||||
|
|
||||||
assertEquals(want.container.devel, test.container.devel, "devel")
|
|
||||||
assertEquals(want.container.userns, test.container.userns, "userns")
|
|
||||||
assertEquals(want.container.net, test.container.net, "net")
|
|
||||||
assertEquals(want.container.tty, test.container.tty, "tty")
|
|
||||||
assertEquals(want.container.multiarch, test.container.multiarch, "multiarch")
|
|
||||||
|
|
||||||
assertEquals(want.container.env, test.container.env, "env")
|
|
||||||
assertEquals(want.container.mapRealUid, test.container.mapRealUid, "mapRealUid")
|
|
||||||
|
|
||||||
assertEquals(want.container.device, test.container.device, "device")
|
|
||||||
assertEquals(want.container.filesystem.size, test.container.filesystem.size, "filesystem.size")
|
|
||||||
for(i in 0 until want.container.filesystem.size) {
|
|
||||||
val w = want.container.filesystem[i]
|
|
||||||
val t = test.container.filesystem[i]
|
|
||||||
|
|
||||||
assertEquals(w.src, t.src, "filesystem[$i].src")
|
|
||||||
assertEquals(w.dst, t.dst, "filesystem[$i].dst")
|
|
||||||
assertEquals(w.write, t.write, "filesystem[$i].write")
|
|
||||||
assertEquals(w.device, t.device, "filesystem[$i].device")
|
|
||||||
assertEquals(w.must, t.must, "filesystem[$i].must")
|
|
||||||
}
|
|
||||||
assertEquals(want.container.link, test.container.link, "link")
|
|
||||||
assertEquals(want.container.autoRoot, test.container.autoRoot, "autoroot")
|
|
||||||
|
|
||||||
assertEquals(want.container.etc, test.container.etc, "etc")
|
|
||||||
|
|
||||||
assertEquals(want.container.autoEtc, test.container.autoEtc, "autoEtc")
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun testHakureiConfigSerialization() {
|
|
||||||
val json = format.encodeToString(testConfig)
|
|
||||||
val test = format.decodeFromString<HakureiConfig>(json)
|
|
||||||
assertEquals(testConfig, test)
|
|
||||||
val want = format.decodeFromString<HakureiConfig>(want)
|
|
||||||
|
|
||||||
compare(want, test)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun testEnablements() {
|
|
||||||
val enablement = HakureiConfig.Enablement.enablements(Wayland, X11, DBus, Pulse)
|
|
||||||
assertEquals(enablement, 0b1111)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun testSeccompFilterPresets() {
|
|
||||||
val presets = HakureiConfig.SeccompFilterPreset.filterPresets(Ext, DenyNS, DenyTTY, DenyDevel, Linux32)
|
|
||||||
assertEquals(presets, 0b11111)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun testExportFlags() {
|
|
||||||
val flags = HakureiConfig.HakureiExportFlag.exportFlags(Multiarch, CAN, Bluetooth)
|
|
||||||
assertEquals(flags, 0b111)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun testRootFlags() {
|
|
||||||
val flags = HakureiConfig.RootFlag.rootFlags(Optional, Writable, Device)
|
|
||||||
assertEquals(flags, 0b111)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
plugins {
|
|
||||||
kotlin("multiplatform")
|
|
||||||
}
|
|
||||||
kotlin {
|
|
||||||
jvm()
|
|
||||||
val nativeTarget = if(System.getProperty("os.arch") == "aarch64") {
|
|
||||||
linuxArm64("native")
|
|
||||||
} else {
|
|
||||||
linuxX64("native")
|
|
||||||
}
|
|
||||||
nativeTarget.binaries {
|
|
||||||
executable()
|
|
||||||
}
|
|
||||||
sourceSets {
|
|
||||||
nativeMain.dependencies {
|
|
||||||
implementation(project(":libplt"))
|
|
||||||
}
|
|
||||||
nativeTest.dependencies {
|
|
||||||
implementation(project(":libplt"))
|
|
||||||
implementation(kotlin("test"))
|
|
||||||
}
|
|
||||||
jvmMain.dependencies {
|
|
||||||
implementation(project(":libplt"))
|
|
||||||
}
|
|
||||||
jvmTest.dependencies {
|
|
||||||
implementation(project(":libplt"))
|
|
||||||
implementation(kotlin("test"))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
package app.hakurei.planterette.api
|
|
||||||
|
|
||||||
interface Task {
|
|
||||||
val execute: String
|
|
||||||
}
|
|
||||||
class AptInstallTask(val packages: List<String>) : Task {
|
|
||||||
private fun getPackageList(): String {
|
|
||||||
var string = ""
|
|
||||||
packages.forEach { p ->
|
|
||||||
string += "$p "
|
|
||||||
}
|
|
||||||
return string
|
|
||||||
}
|
|
||||||
override val execute: String
|
|
||||||
get() = "sudo apt install ${getPackageList()}"
|
|
||||||
}
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
plugins {
|
|
||||||
kotlin("multiplatform")
|
|
||||||
}
|
|
||||||
kotlin {
|
|
||||||
val nativeTarget = if(System.getProperty("os.arch") == "aarch64") {
|
|
||||||
linuxArm64("native")
|
|
||||||
} else {
|
|
||||||
linuxX64("native")
|
|
||||||
}
|
|
||||||
nativeTarget.binaries {
|
|
||||||
executable()
|
|
||||||
}
|
|
||||||
sourceSets {
|
|
||||||
nativeMain.dependencies {
|
|
||||||
implementation(project(":libplt"))
|
|
||||||
}
|
|
||||||
nativeTest.dependencies {
|
|
||||||
implementation(project(":libplt"))
|
|
||||||
implementation(kotlin("test"))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
plugins {
|
|
||||||
kotlin("multiplatform")
|
|
||||||
}
|
|
||||||
kotlin {
|
|
||||||
val nativeTarget = if(System.getProperty("os.arch") == "aarch64") {
|
|
||||||
linuxArm64("native")
|
|
||||||
} else {
|
|
||||||
linuxX64("native")
|
|
||||||
}
|
|
||||||
nativeTarget.binaries {
|
|
||||||
executable()
|
|
||||||
}
|
|
||||||
sourceSets {
|
|
||||||
nativeMain.dependencies {
|
|
||||||
implementation(project(":libplt"))
|
|
||||||
}
|
|
||||||
nativeTest.dependencies {
|
|
||||||
implementation(project(":libplt"))
|
|
||||||
implementation(kotlin("test"))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
plugins {
|
|
||||||
kotlin("multiplatform")
|
|
||||||
}
|
|
||||||
kotlin {
|
|
||||||
val nativeTarget = if(System.getProperty("os.arch") == "aarch64") {
|
|
||||||
linuxArm64("native")
|
|
||||||
} else {
|
|
||||||
linuxX64("native")
|
|
||||||
}
|
|
||||||
nativeTarget.binaries {
|
|
||||||
executable()
|
|
||||||
}
|
|
||||||
sourceSets {
|
|
||||||
nativeMain.dependencies {
|
|
||||||
implementation(project(":libplt"))
|
|
||||||
}
|
|
||||||
nativeTest.dependencies {
|
|
||||||
implementation(project(":libplt"))
|
|
||||||
implementation(kotlin("test"))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
dependencyResolutionManagement {
|
|
||||||
@Suppress("UnstableApiUsage")
|
|
||||||
repositories {
|
|
||||||
mavenCentral()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
plugins {
|
|
||||||
id("org.gradle.toolchains.foojay-resolver-convention") version "0.8.0"
|
|
||||||
}
|
|
||||||
|
|
||||||
rootProject.name = "planterette"
|
|
||||||
|
|
||||||
include("plt")
|
|
||||||
include("libplt")
|
|
||||||
include("plt-build")
|
|
||||||
include("plt-fetch")
|
|
||||||
include("plt-pkg")
|
|
||||||
Reference in New Issue
Block a user