diff --git a/ballerina/Ballerina.toml b/ballerina/Ballerina.toml index a5be3ce6..56117b51 100644 --- a/ballerina/Ballerina.toml +++ b/ballerina/Ballerina.toml @@ -1,7 +1,7 @@ [package] org = "ballerina" name = "os" -version = "1.3.0" +version = "1.4.0" authors = ["Ballerina"] keywords = ["environment"] repository = "https://github.com/ballerina-platform/module-ballerina-os" @@ -10,10 +10,10 @@ license = ["Apache-2.0"] distribution = "2201.0.4" [[platform.java11.dependency]] -path = "../native/build/libs/os-native-1.3.0.jar" +path = "../native/build/libs/os-native-1.4.0-SNAPSHOT.jar" [[platform.java11.dependency]] path = "./lib/io-native-1.2.2.jar" [[platform.java11.dependency]] -path = "../test-utils/build/libs/os-test-utils-1.3.0.jar" +path = "../test-utils/build/libs/os-test-utils-1.4.0-SNAPSHOT.jar" diff --git a/ballerina/Dependencies.toml b/ballerina/Dependencies.toml index dd39f182..ceae845b 100644 --- a/ballerina/Dependencies.toml +++ b/ballerina/Dependencies.toml @@ -6,6 +6,18 @@ [ballerina] dependencies-toml-version = "2" +[[package]] +org = "ballerina" +name = "io" +version = "1.2.2" +dependencies = [ + {org = "ballerina", name = "jballerina.java"}, + {org = "ballerina", name = "lang.value"} +] +modules = [ + {org = "ballerina", packageName = "io", moduleName = "io"} +] + [[package]] org = "ballerina" name = "jballerina.java" @@ -14,11 +26,20 @@ modules = [ {org = "ballerina", packageName = "jballerina.java", moduleName = "jballerina.java"} ] +[[package]] +org = "ballerina" +name = "lang.value" +version = "0.0.0" +dependencies = [ + {org = "ballerina", name = "jballerina.java"} +] + [[package]] org = "ballerina" name = "os" -version = "1.3.0" +version = "1.4.0" dependencies = [ + {org = "ballerina", name = "io"}, {org = "ballerina", name = "jballerina.java"}, {org = "ballerina", name = "test"} ] diff --git a/ballerina/build.gradle b/ballerina/build.gradle index 490a9d48..cac130c7 100644 --- a/ballerina/build.gradle +++ b/ballerina/build.gradle @@ -38,6 +38,7 @@ def packageOrg = "ballerina" def tomlVersion = stripBallerinaExtensionVersion("${project.version}") def ballerinaTomlFilePlaceHolder = new File("${project.rootDir}/build-config/resources/Ballerina.toml") def ballerinaTomlFile = new File("$project.projectDir/Ballerina.toml") +def configTOMLFile = new File("$project.projectDir/tests/Config.toml") def stripBallerinaExtensionVersion(String extVersion) { if (extVersion.matches(project.ext.timestampedVersionRegex)) { @@ -75,12 +76,25 @@ task updateTomlFiles { doLast { def stdlibNativeIoVersion = project.stdlibIoVersion def newConfig = ballerinaTomlFilePlaceHolder.text.replace("@project.version@", project.version) + def distributionBinPath = "$project.rootDir/target/ballerina-runtime/bin" newConfig = newConfig.replace("@toml.version@", tomlVersion) newConfig = newConfig.replace("@io.native.version@", stdlibNativeIoVersion) ballerinaTomlFile.text = newConfig + setExecPath(configTOMLFile,distributionBinPath) } } +def setExecPath(configTOMLFile, distributionBinPath) { + def distributionPath + if (Os.isFamily(Os.FAMILY_WINDOWS)) { + distributionPath = (distributionBinPath + "/bal.bat").replace("/", "\\") + distributionPath = distributionPath.replace("\\", "\\\\") + } else { + distributionPath = distributionBinPath + "/bal" + } + configTOMLFile.text = configTOMLFile.text.replace("@exec.path@", distributionPath) +} + task commitTomlFiles { doLast { project.exec { diff --git a/ballerina/os.bal b/ballerina/os.bal index 31eba86b..3fa601df 100644 --- a/ballerina/os.bal +++ b/ballerina/os.bal @@ -16,6 +16,16 @@ import ballerina/jballerina.java; +public type Command record {| + string value; + string[] arguments = []; +|}; + +public type EnvProperties record {| + never command?; + anydata...; +|}; + # Returns the environment variable value associated with the provided name. # ```ballerina # string port = os:getEnv("HTTP_PORT"); @@ -71,7 +81,7 @@ public isolated function getUserHome() returns string = @java:Method { public isolated function setEnv(string key, string value) returns Error? { if key == "" { return error Error("The parameter key cannot be an empty string"); - } else if key == "==" { + } else if key == "==" { return error Error("The parameter key cannot be == sign"); } else { return setEnvExtern(key, value); @@ -113,3 +123,16 @@ isolated function listEnvExtern() returns map = @java:Method { name: "listEnv", 'class: "io.ballerina.stdlib.os.nativeimpl.ListEnv" } external; + +# Executes an operating system command as a subprocess of the current process. +# ```ballerina +# os:Process|os:Error result = os:exec({value: "bal", arguments: ["run", filepath]}, BAL_CONFIG_FILE = "/abc/Config.toml"); +# ``` +# +# + command - The command to be executed +# + envProperties - The environment properties +# + return - Process object in success, or an Error if a failure occurs +public isolated function exec(Command command, *EnvProperties envProperties) returns Process|Error = @java:Method { + name: "exec", + 'class: "io.ballerina.stdlib.os.nativeimpl.Exec" +} external; diff --git a/ballerina/os_errors.bal b/ballerina/os_errors.bal index a19cc3dc..36bba9fd 100644 --- a/ballerina/os_errors.bal +++ b/ballerina/os_errors.bal @@ -16,3 +16,6 @@ # Represents OS module related errors. public type Error distinct error; + +# Process Execution error that returns when the os:Exec function fails. +public type ProcessExecError distinct Error; diff --git a/ballerina/process.bal b/ballerina/process.bal new file mode 100644 index 00000000..9568df8e --- /dev/null +++ b/ballerina/process.bal @@ -0,0 +1,70 @@ +// Copyright (c) 2022 WSO2 Inc. (http://www.wso2.org) All Rights Reserved. +// +// WSO2 Inc. licenses this file to you 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. + +import ballerina/io; +import ballerina/jballerina.java; + +# This object contains information on a process being created from Ballerina. +# This is returned from the `exec` function in the `os` module. +public class Process { + + # Waits for the process to finish its work and exit. + # This will return 0 if successful, or a different value during failure depending on the operating system. + # ```ballerina + # int|os:Error exitCode = process.waitForExit(); + # ``` + # + # + return - Returns the exit code for the process, or else an `Error` if a failure occurs + public isolated function waitForExit() returns int|Error { + return nativeWaitForExit(self); + } + + # Returns the standard output as default. Option provided to return standard error by providing file descriptor. + # If the process was not finished and exited explicitly by running process.waitForExit(), then process.output() will finish the work and exit and return the output. + # ```ballerina + # byte[]|os:Error err = process.output(io:stderr); + # ``` + # + # + fileOutputStream - The output stream (`io:stdout` or `io:stderr`) content needs to be returned + # + return - The `byte[]`, which represents the process's 'standard error', or the 'standard out', or an Error + public isolated function output(io:FileOutputStream fileOutputStream = io:stdout) returns byte[]|Error { + return nativeOutput(self, fileOutputStream); + } + + # Terminates the process. + # ```ballerina + # process.exit(); + # ``` + # + public isolated function exit() { + return nativeExit(self); + } +} + +isolated function nativeWaitForExit(Process process) returns int|Error = @java:Method { + name: "waitForExit", + 'class: "io.ballerina.stdlib.os.nativeimpl.WaitForExit" +} external; + +isolated function nativeExit(Process process) = @java:Method { + name: "exit", + 'class: "io.ballerina.stdlib.os.nativeimpl.Exit" +} external; + +isolated function nativeOutput(Process process, int fileOutputStream) returns byte[]|Error = @java:Method { + name: "output", + 'class: "io.ballerina.stdlib.os.nativeimpl.Output" +} external; diff --git a/ballerina/tests/Config.toml b/ballerina/tests/Config.toml new file mode 100644 index 00000000..d7fd8ea3 --- /dev/null +++ b/ballerina/tests/Config.toml @@ -0,0 +1,2 @@ +[os] +bal_exec_path = "@exec.path@" diff --git a/ballerina/tests/os-test.bal b/ballerina/tests/os_test.bal similarity index 50% rename from ballerina/tests/os-test.bal rename to ballerina/tests/os_test.bal index f1e11ce3..68843dd5 100644 --- a/ballerina/tests/os-test.bal +++ b/ballerina/tests/os_test.bal @@ -16,6 +16,9 @@ import ballerina/jballerina.java; import ballerina/test; +import ballerina/io; + +configurable string bal_exec_path = ?; @test:Config {} function testGetEnv() { @@ -154,3 +157,117 @@ function getSystemProperty(string key) returns string = @java:Method { name: "getSystemProperty", 'class: "io.ballerina.stdlib.os.utils.OSUtils" } external; + +@test:Config {} +function testExec() returns error? { + Process process = check exec({value: "echo", arguments: ["hello world"]}); + int exitCode = check process.waitForExit(); + test:assertEquals(exitCode, 0); + + byte[] outputBytes = check process.output(); + string outputString = check string:fromBytes(outputBytes); + test:assertEquals(outputString.trim(), "hello world"); +} + +@test:Config {} +function testExecOutputWithoutWaitForExit() returns error? { + Process process = check exec({value: "echo", arguments: ["hello world"]}); + + byte[] outputBytes = check process.output(); + string outputString = check string:fromBytes(outputBytes); + test:assertEquals(outputString.trim(), "hello world"); +} + +@test:Config {} +function testExecWithOutputStdOut() returns error? { + Process process = check exec({value: bal_exec_path, arguments: ["run", "tests/resources/hello1.bal"]}); + int exitCode = check process.waitForExit(); + test:assertEquals(exitCode, 0); + + byte[] stdOutBytes = check process.output(io:stdout); + string stdOutString = check string:fromBytes(stdOutBytes); + test:assertTrue(stdOutString.includes("hello world")); + + byte[] stdErrBytes = check process.output(io:stderr); + string stdErrString = check string:fromBytes(stdErrBytes); + test:assertFalse(stdErrString.includes("hello world")); +} + +@test:Config {} +function testExecWithOutputStdErr() returns error? { + Process process = check exec({value: bal_exec_path, arguments: ["run", "tests/resources/hello2.bal"]}); + int exitCode = check process.waitForExit(); + test:assertEquals(exitCode, 0); + + byte[] stdOutBytes = check process.output(io:stdout); + string stdOutString = check string:fromBytes(stdOutBytes); + test:assertFalse(stdOutString.includes("hello world")); + + byte[] stdErrBytes = check process.output(io:stderr); + string stdErrString = check string:fromBytes(stdErrBytes); + test:assertTrue(stdErrString.includes("hello world")); +} + +@test:Config {} +function testExecWithEnvironmentVariable() returns error? { + Process process = check exec({value: bal_exec_path, arguments: ["run", "tests/resources/hello3.bal"]}, BAL_CONFIG_FILES = "tests/resources/config/Config.toml"); + int exitCode = check process.waitForExit(); + test:assertEquals(exitCode, 0); + + byte[] outputBytes = check process.output(io:stderr); + string outputString = check string:fromBytes(outputBytes); + test:assertTrue(outputString.includes("{\"time\":\"")); + test:assertTrue(outputString.includes("\", \"level\":\"DEBUG\", \"module\":\"\", \"message\":\"debug message\"}")); +} + +@test:Config {} +function testExecExit() returns error? { + Process process = check exec({value: "echo", arguments: ["hello world"]}); + process.exit(); + + int _ = check process.waitForExit(); + + byte[]|Error outputBytes = process.output(); + if isWindowsEnvironment() { + if outputBytes is error { + test:assertFail("Expected output does not match"); + } else { + test:assertEquals(string:fromBytes(outputBytes), ""); + } + } else { + if outputBytes is error { + test:assertEquals(outputBytes.message(), "Failed to read the output of the process: Stream closed"); + } else { + test:assertFail("Expected error message does not match"); + } + } +} + +@test:Config {} +function testExecNegative() returns error? { + Process|Error process = exec({value: "foo"}); + if process is Error { + if isWindowsEnvironment() { + test:assertEquals(process.message(), "Failed to retrieve the process object: Cannot run program \"foo\": CreateProcess error=2, " + + "The system cannot find the file specified"); + } else { + test:assertEquals(process.message(), "Failed to retrieve the process object: Cannot run program \"foo\": error=2, No such file or directory"); + } + } else { + test:assertFail("Expected error message does not match"); + } +} + +isolated function isWindowsEnvironment() returns boolean { + var osType = java:toString(nativeGetSystemPropery(java:fromString("os.name"))); + if osType is string { + return osType.toLowerAscii().includes("win"); + } + return false; +} + +isolated function nativeGetSystemPropery(handle key) returns handle = @java:Method { + name: "getProperty", + 'class: "java.lang.System", + paramTypes: ["java.lang.String"] +} external; diff --git a/ballerina/tests/resources/config/Config.toml b/ballerina/tests/resources/config/Config.toml new file mode 100644 index 00000000..e24dfd83 --- /dev/null +++ b/ballerina/tests/resources/config/Config.toml @@ -0,0 +1,3 @@ +[ballerina.log] +level = "DEBUG" +format = "json" diff --git a/ballerina/tests/resources/hello1.bal b/ballerina/tests/resources/hello1.bal new file mode 100644 index 00000000..f2ac38cb --- /dev/null +++ b/ballerina/tests/resources/hello1.bal @@ -0,0 +1,21 @@ +// Copyright (c) 2022 WSO2 Inc. (http://www.wso2.org) All Rights Reserved. +// +// WSO2 Inc. licenses this file to you 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. + +import ballerina/io; + +public function main() { + io:fprintln(io:stdout, "hello world"); +} diff --git a/ballerina/tests/resources/hello2.bal b/ballerina/tests/resources/hello2.bal new file mode 100644 index 00000000..b3dab273 --- /dev/null +++ b/ballerina/tests/resources/hello2.bal @@ -0,0 +1,21 @@ +// Copyright (c) 2022 WSO2 Inc. (http://www.wso2.org) All Rights Reserved. +// +// WSO2 Inc. licenses this file to you 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. + +import ballerina/io; + +public function main() { + io:fprintln(io:stderr, "hello world"); +} diff --git a/ballerina/tests/resources/hello3.bal b/ballerina/tests/resources/hello3.bal new file mode 100644 index 00000000..01fe2529 --- /dev/null +++ b/ballerina/tests/resources/hello3.bal @@ -0,0 +1,21 @@ +// Copyright (c) 2022 WSO2 Inc. (http://www.wso2.org) All Rights Reserved. +// +// WSO2 Inc. licenses this file to you 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. + +import ballerina/log; + +public function main() { + log:printDebug("debug message"); +} diff --git a/changelog.md b/changelog.md index 8141ada1..35c7599e 100644 --- a/changelog.md +++ b/changelog.md @@ -3,6 +3,11 @@ This file contains all the notable changes done to the Ballerina OS package thro ## [Unreleased] +### Added +- [Introduce OS command execution support in Ballerina](https://github.com/ballerina-platform/ballerina-standard-library/issues/2852) + +## [1.3.0] - [2022-05-30] + ### Added - [Add functions to set and unset environment variables.](https://github.com/ballerina-platform/ballerina-standard-library/issues/2764) - [Add functions to list environment variables as a map.](https://github.com/ballerina-platform/ballerina-standard-library/issues/2764) diff --git a/docs/proposals/command-execution.md b/docs/proposals/command-execution.md new file mode 100644 index 00000000..8bad6370 --- /dev/null +++ b/docs/proposals/command-execution.md @@ -0,0 +1,87 @@ +# Proposal: OS Command Execution + +_Owners_: @daneshk @MadhukaHarith92 +_Reviewers_: @daneshk +_Created_: 2022/07/07 +_Updated_: 2022/07/07 +_Issues_: [#2963](https://github.com/ballerina-platform/ballerina-standard-library/issues/2963) + +## Summary +At the moment, the Ballerina OS module does not have an API to execute OS commands. In this proposal, we describe how this requirement can be facilitated. + +## Goals +To support OS command execution. + +## Motivation +To allow users to execute operating system commands programmatically. + +## Description + +```ballerina +# Represents a command. +# +# + value - The value of the command +# + arguments - The arguments of the command +public type Command record { + string value; + string[] arguments; +}; + +# Executes an operating system command as a subprocess of the current process. +# ```ballerina +# os:Process|os:Error result = os:exec({value: "bal", arguments: ["run", filepath]}, BAL_CONFIG_FILE = "/abc/Config.toml"); +# ``` +# +# + command - The command to be executed +# + envProperties - The environment properties +# +# + return - Process object in success, or an Error if a failure occurs +public isolated function exec(Command command, *EnvProperties envProperties) returns Process | Error; +``` +- The `exec` function can be used to execute OS commands programmatically. Users can pass the command as a raw template. Additionally, users can pass any number of environment properties as key-value pairs that need to be set when executing the command. + +```ballerina +# Environment properties that need to be set when executing the command. +# +# + command - command which cannot be a key +public type EnvProperties record {| + never command?; + anydata...; +|}; +``` + +- Represents an environment property as an included record type. + +```ballerina +# This object contains information on a process being created from Ballerina. +# This is returned from the `exec` function. +public class Process { + + # Waits for the process to finish its work and exit. + # ```ballerina + # int|os:Error exitCode = process.waitForExit(); + # ``` + # + # + return - Returns the exit code for the process, or else an `Error` if a failure occurs + public isolated function waitForExit() returns int|Error; + + # Provides a stream (to read from), which is made available as the 'standard error', or the 'standard out' of the process. + # ```ballerina + # byte[]|os:Error err = process.output(io:stderr); + # ``` + # + # + return - The `byte[]`, which represents the process's 'standard error', or the 'standard out', or an Error + public isolated function output(io:FileOutputStream fileOutputStream) returns byte[]|Error; + + # Terminates the process. + # ```ballerina + # process.exit(); + # ``` + # + public isolated function exit(); +} +``` + +## Testing +- Execute an OS command using the `exec` function and validate whether it was successful. +- Pass an environment property to the `exec` function and validate if it was set correctly. diff --git a/docs/proposals/environment-variables.md b/docs/proposals/environment-variables.md index ed373266..a2b7c126 100644 --- a/docs/proposals/environment-variables.md +++ b/docs/proposals/environment-variables.md @@ -2,8 +2,8 @@ _Owners_: @daneshk @MadhukaHarith92 _Reviewers_: @daneshk -_Created_: 2022/12/18 -_Updated_: 2022/12/18 +_Created_: 2022/02/18 +_Updated_: 2022/02/18 _Issues_: [#2666](https://github.com/ballerina-platform/ballerina-standard-library/issues/2666) [#2630](https://github.com/ballerina-platform/ballerina-standard-library/issues/2630) ## Summary diff --git a/docs/spec/spec.md b/docs/spec/spec.md index 58681628..94359e83 100644 --- a/docs/spec/spec.md +++ b/docs/spec/spec.md @@ -20,6 +20,7 @@ The conforming implementation of the specification is released and included in t 1. [Overview](#1-overview) 2. [Environment Variable Values](#2-environment-variable-values) 3. [Operating System Users Information](#3-operating-system-users-information) +4. [Operating System Command execution](#4-operating-system-command-execution) ## 1. Overview This specification elaborates on the operating-system-related functions available in the OS library. @@ -55,3 +56,81 @@ The current user's home directory path can be retrieved using the `os:getUserHom ```ballerina string userHome = os:getUserHome(); ``` + +## 4. Operating System Command execution +The users can execute OS commands using the `os:exec()` function by passing an `os:Command` record. +```ballerina +os:Process|os:Error result = os:exec({value: "bal", arguments: ["run", filepath]}, BAL_CONFIG_FILE = "/abc/Config.toml"); +``` + +The following is the record type definitions of `os:Command`. +```ballerina +public type Command record {| + string value; + string[] arguments = []; +|}; +``` + +Additionally, users can pass any number of environment properties as key-value pairs. +```ballerina +public type EnvProperties record {| + never command?; + anydata...; +|}; +``` + +This will return an `os:Process` object. To wait for the process to finish its work and exit, `process.waitForExit()` function can be used. +```ballerina +int|os:Error exitCode = process.waitForExit(); +``` + +To retrieve the output of the process, `process.output()` function can be used. This will return the standard output as default. +There is an option provided to return standard error by providing file descriptor. +```ballerina +byte[]|os:Error err = process.output(io:stderr); +``` + +To terminate a process, `process.exit()` function can be used. +```ballerina +process.exit(); +``` + +The following is the definition of the `os:Process` object. +```ballerina +# This object contains information on a process being created from Ballerina. +# This is returned from the `exec` function in the `os` module. +public class Process { + + # Waits for the process to finish its work and exit. + # This will return 0 if successful, or a different value during failure depending on the operating system. + # ```ballerina + # int|os:Error exitCode = process.waitForExit(); + # ``` + # + # + return - Returns the exit code for the process, or else an `Error` if a failure occurs + public isolated function waitForExit() returns int|Error { + return nativeWaitForExit(self); + } + + # Returns the standard output as default. Option provided to return standard error by providing file descriptor. + # If the process was not finished and exited explicitly by running process.waitForExit(), then process.output() will finish the work and exit and return the output. + # ```ballerina + # byte[]|os:Error err = process.output(io:stderr); + # ``` + # + # + fileOutputStream - The output stream (`io:stdout` or `io:stderr`) content needs to be returned + # + return - The `byte[]`, which represents the process's 'standard error', or the 'standard out', or an Error + public isolated function output(io:FileOutputStream fileOutputStream = io:stdout) returns byte[]|Error { + return nativeOutput(self, fileOutputStream); + } + + # Terminates the process. + # ```ballerina + # process.exit(); + # ``` + # + public isolated function exit() { + return nativeExit(self); + } +} +``` diff --git a/gradle.properties b/gradle.properties index 8abac839..d3a3e108 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,6 +1,6 @@ org.gradle.caching=true group=io.ballerina.stdlib -version=1.3.1-SNAPSHOT +version=1.4.0-SNAPSHOT ballerinaGradlePluginVersion=0.14.1 diff --git a/native/src/main/java/io/ballerina/stdlib/os/nativeimpl/Errors.java b/native/src/main/java/io/ballerina/stdlib/os/nativeimpl/Errors.java new file mode 100644 index 00000000..9606b47c --- /dev/null +++ b/native/src/main/java/io/ballerina/stdlib/os/nativeimpl/Errors.java @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2022, WSO2 Inc. (http://www.wso2.org) All Rights Reserved. + * + * WSO2 Inc. licenses this file to you 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 io.ballerina.stdlib.os.nativeimpl; + +/** + * Java enumeration of the errors of the os library. + * + * @since 1.4.0 + */ +public enum Errors { + ProcessExecError +} diff --git a/native/src/main/java/io/ballerina/stdlib/os/nativeimpl/Exec.java b/native/src/main/java/io/ballerina/stdlib/os/nativeimpl/Exec.java new file mode 100644 index 00000000..505314e7 --- /dev/null +++ b/native/src/main/java/io/ballerina/stdlib/os/nativeimpl/Exec.java @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2022, WSO2 Inc. (http://www.wso2.org) All Rights Reserved. + * + * WSO2 Inc. licenses this file to you 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 io.ballerina.stdlib.os.nativeimpl; + +import io.ballerina.runtime.api.creators.ErrorCreator; +import io.ballerina.runtime.api.utils.StringUtils; +import io.ballerina.runtime.api.values.BMap; +import io.ballerina.runtime.api.values.BString; +import io.ballerina.stdlib.os.utils.OSUtils; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; + +import static io.ballerina.stdlib.os.nativeimpl.Errors.ProcessExecError; + +/** + * Extern function os:exec. + * + * @since 1.4.0 + */ +public class Exec { + + static final String VALUE = "value"; + static final String ARGUMENTS = "arguments"; + + private Exec() { + + } + + public static Object exec(BMap command, BMap env) { + List commandList = new ArrayList<>(); + commandList.add(command.getStringValue(StringUtils.fromString(VALUE)).getValue()); + String[] arguments = command.getArrayValue(StringUtils.fromString(ARGUMENTS)).getStringArray(); + + Collections.addAll(commandList, arguments); + ProcessBuilder pb = new ProcessBuilder(commandList); + if (env != null) { + Map pbEnv = pb.environment(); + env.entrySet().forEach(entry -> pbEnv.put(entry.getKey().getValue(), entry.getValue().toString())); + } + try { + return OSUtils.getProcessObject(pb.start()); + } catch (IOException e) { + return ErrorCreator.createError(ModuleUtils.getModule(), String.valueOf(ProcessExecError), + StringUtils.fromString("Failed to retrieve the process object" + ": " + e.getMessage()), + null, null); + } + } +} diff --git a/native/src/main/java/io/ballerina/stdlib/os/nativeimpl/Exit.java b/native/src/main/java/io/ballerina/stdlib/os/nativeimpl/Exit.java new file mode 100644 index 00000000..d726521b --- /dev/null +++ b/native/src/main/java/io/ballerina/stdlib/os/nativeimpl/Exit.java @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2022, WSO2 Inc. (http://www.wso2.org) All Rights Reserved. + * + * WSO2 Inc. licenses this file to you 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 io.ballerina.stdlib.os.nativeimpl; + +import io.ballerina.runtime.api.values.BObject; +import io.ballerina.stdlib.os.utils.OSUtils; + +/** + * External function for ballerina.os:Process.exit. + * + * @since 1.4.0 + */ +public class Exit { + + private Exit() { + + } + + public static void exit(BObject objVal) { + Process process = OSUtils.processFromObject(objVal); + process.destroy(); + } +} diff --git a/native/src/main/java/io/ballerina/stdlib/os/nativeimpl/Output.java b/native/src/main/java/io/ballerina/stdlib/os/nativeimpl/Output.java new file mode 100644 index 00000000..bde2e4f7 --- /dev/null +++ b/native/src/main/java/io/ballerina/stdlib/os/nativeimpl/Output.java @@ -0,0 +1,79 @@ +/* + * Copyright (c) 2022, WSO2 Inc. (http://www.wso2.org) All Rights Reserved. + * + * WSO2 Inc. licenses this file to you 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 io.ballerina.stdlib.os.nativeimpl; + +import io.ballerina.runtime.api.PredefinedTypes; +import io.ballerina.runtime.api.creators.ErrorCreator; +import io.ballerina.runtime.api.creators.TypeCreator; +import io.ballerina.runtime.api.creators.ValueCreator; +import io.ballerina.runtime.api.types.ArrayType; +import io.ballerina.runtime.api.utils.StringUtils; +import io.ballerina.runtime.api.values.BArray; +import io.ballerina.runtime.api.values.BObject; +import io.ballerina.stdlib.os.utils.OSUtils; + +import java.io.IOException; +import java.io.InputStream; + +import static io.ballerina.stdlib.os.nativeimpl.Errors.ProcessExecError; + +/** + * External function for ballerina.os:Process.output. + * + * @since 1.4.0 + */ +public class Output { + + private Output() { + + } + + private static final ArrayType BYTE_ARRAY_TYPE = TypeCreator.createArrayType(PredefinedTypes.TYPE_BYTE); + + public static Object output(BObject objVal, long fileOutputStream) { + BArray byteDataArray = ValueCreator.createArrayValue(BYTE_ARRAY_TYPE); + byte[] result; + Process process = OSUtils.processFromObject(objVal); + InputStream in; + if (fileOutputStream == 1) { + in = process.getInputStream(); + } else { + in = process.getErrorStream(); + } + try { + result = in.readAllBytes(); + } catch (IOException e) { + return ErrorCreator.createError(ModuleUtils.getModule(), String.valueOf(ProcessExecError), + StringUtils.fromString("Failed to read the output of the process" + ": " + e.getMessage()), + null, null); + } finally { + try { + in.close(); + } catch (IOException e) { + return ErrorCreator.createError(ModuleUtils.getModule(), String.valueOf(ProcessExecError), + StringUtils.fromString("Failed to close the output stream of the process" + ": " + + e.getMessage()), null, null); + } + } + for (int i = 0; i < result.length; i++) { + byteDataArray.add(i, result[i]); + } + return byteDataArray; + } +} diff --git a/native/src/main/java/io/ballerina/stdlib/os/nativeimpl/WaitForExit.java b/native/src/main/java/io/ballerina/stdlib/os/nativeimpl/WaitForExit.java new file mode 100644 index 00000000..65a5c01b --- /dev/null +++ b/native/src/main/java/io/ballerina/stdlib/os/nativeimpl/WaitForExit.java @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2022, WSO2 Inc. (http://www.wso2.org) All Rights Reserved. + * + * WSO2 Inc. licenses this file to you 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 io.ballerina.stdlib.os.nativeimpl; + +import io.ballerina.runtime.api.creators.ErrorCreator; +import io.ballerina.runtime.api.utils.StringUtils; +import io.ballerina.runtime.api.values.BObject; +import io.ballerina.stdlib.os.utils.OSUtils; + +import static io.ballerina.stdlib.os.nativeimpl.Errors.ProcessExecError; + +/** + * External function for ballerina.os:Process.waitForExit. + * + * @since 1.4.0 + */ +public class WaitForExit { + + private WaitForExit() { + + } + + public static Object waitForExit(BObject objVal) { + Process process = OSUtils.processFromObject(objVal); + try { + return process.waitFor(); + } catch (InterruptedException e) { + return ErrorCreator.createError(ModuleUtils.getModule(), String.valueOf(ProcessExecError), + StringUtils.fromString("Failed to wait for process to exit" + ": " + e.getMessage()), null, null); + } + } +} diff --git a/native/src/main/java/io/ballerina/stdlib/os/utils/OSConstants.java b/native/src/main/java/io/ballerina/stdlib/os/utils/OSConstants.java new file mode 100644 index 00000000..38b74ec7 --- /dev/null +++ b/native/src/main/java/io/ballerina/stdlib/os/utils/OSConstants.java @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2022, WSO2 Inc. (http://www.wso2.org) All Rights Reserved. + * + * WSO2 Inc. licenses this file to you 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 io.ballerina.stdlib.os.utils; + +/** + * Constants for os package functions. + * + * @since 1.4.0 + */ +public class OSConstants { + + static final String PROCESS_TYPE = "Process"; + + static final String PROCESS_FIELD = "ProcessField"; + + private OSConstants() { + } +} diff --git a/native/src/main/java/io/ballerina/stdlib/os/utils/OSUtils.java b/native/src/main/java/io/ballerina/stdlib/os/utils/OSUtils.java index b67a1b82..17c9dc10 100644 --- a/native/src/main/java/io/ballerina/stdlib/os/utils/OSUtils.java +++ b/native/src/main/java/io/ballerina/stdlib/os/utils/OSUtils.java @@ -17,15 +17,30 @@ */ package io.ballerina.stdlib.os.utils; +import io.ballerina.runtime.api.creators.ValueCreator; import io.ballerina.runtime.api.utils.StringUtils; +import io.ballerina.runtime.api.values.BObject; import io.ballerina.runtime.api.values.BString; +import java.io.IOException; + +import static io.ballerina.stdlib.os.nativeimpl.ModuleUtils.getModule; +import static io.ballerina.stdlib.os.utils.OSConstants.PROCESS_FIELD; +import static io.ballerina.stdlib.os.utils.OSConstants.PROCESS_TYPE; + /** * @since 0.8.0 */ public class OSUtils { - private OSUtils() { + public static BObject getProcessObject(Process process) throws IOException { + BObject obj = ValueCreator.createObjectValue(getModule(), PROCESS_TYPE); + obj.addNativeData(PROCESS_FIELD, process); + return obj; + } + + public static Process processFromObject(BObject objVal) { + return (Process) objVal.getNativeData(PROCESS_FIELD); } /**