diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 61d44aef2be3abfb942a97dd22fc599574ff7a31..78a2b161680ddaf01c1bb349c658b7afcd6e2b9f 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -52,6 +52,24 @@ sphinx-documentation: # Run sphinx-build (treating warnings as errors) sphinx-build -W docs/source output +conda-test: + stage: test + script: + - | + # Install system dependencies + apt-get install --yes --no-install-recommends git bzip2 + + # Install micromamba + curl -Ls https://micromamba.snakepit.net/api/micromamba/linux-64/1.4.2 | tar xvj bin/micromamba + chmod +x ./bin/micromamba + export MAMBA_ROOT_PREFIX=/opt/micromamba + ./bin/micromamba shell --yes init ${MAMBA_ROOT_PREFIX} + source /root/.bashrc + + # Test creation conda environment + micromamba env create --dry-run --name pantools --yes --strict-channel-priority \ + --channel conda-forge --channel bioconda --file conda.yaml + unit-tests: stage: test script: diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 6bf3f50232dedfee51dca4cc5c4855fdc39df909..922a84ed94a2980f4c2d49272866ed82d2b50f58 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -3,12 +3,19 @@ repos: hooks: - id: pre-commit-validate-config name: Validate pre-commit config - description: 'Validates the .pre-commit-config.yaml file' + description: 'Validates the .pre-commit-config.yaml file.' files: '.pre-commit-config.yaml' entry: pre-commit-validate-config args: - .pre-commit-config.yaml language: system + - id: conda-yaml-validate + name: Validate conda YAML + description: 'Validates conda.yaml for creation of conda environment.' + files: 'conda.yaml' + entry: mamba + args: [env, create, -d, -n=test_pantools, -f=conda.yaml] + language: system - id: maven-compile name: Compile with Maven description: 'Compile all code with mvn compile.' diff --git a/CHANGELOG.md b/CHANGELOG.md index d9f3ff4eebe8bb2b32c732fa4f7f8ea0bbb7a223..75f1ca8e51baee1103e93b9a44159e5393991ac1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,8 @@ All notable changes to Pantools will be documented in this file. ### Changed - `pantools busco_protein`, `pantools gene_classification`, `pantools kmer_classification`, `pantools group_info`, `pantools rename_phylogeny`, `pantools create_tree_template`, `pantools core_phylogeny`, `pantools rename_matrix` to be compatible with possible added phasing information in a pangenome database (!148). - `busco_protein` is no longer functional with BUSCO v3 and odb9 datasets (!146). +- `conda_linux.yaml` and `conda_macos.yaml` now unified in one `conda.yaml` file (!203). +- Updated KMC version to >=3.1.0 (!203). ### Fixed - `pantools core_phylogeny` and `pantools consensus_tree` now ignore MSAs that could not be trimmed (!197). diff --git a/README.md b/README.md index 787780a78aa19ca38271e3b5315e26b5372d295c..d77b489a53e7d7ed48fb8100ccd4ea77873021f7 100755 --- a/README.md +++ b/README.md @@ -50,8 +50,7 @@ git checkout v4.0.0 We provide conda environments for installing all dependencies of PanTools, which can be installed using conda/mamba (we recommend using mamba): ```bash -mamba env create -n pantools -f conda_linux.yaml # for linux -mamba env create -n pantools -f conda_macos.yaml # for macOS (does not include busco) +mamba env create -n pantools -f conda.yaml ``` ## Building a runnable jar diff --git a/conda_linux.yaml b/conda.yaml similarity index 88% rename from conda_linux.yaml rename to conda.yaml index 8da2a955552c26419019b4dea77f16eddea8c13f..4e05083448468104a08392a1a8a40b2a48d689b7 100644 --- a/conda_linux.yaml +++ b/conda.yaml @@ -4,8 +4,7 @@ channels: dependencies: - openjdk=8.0 - maven - - python=3.7 - - kmc=3.0.1 + - kmc>=3.1.0 - mcl - mafft - iqtree @@ -13,7 +12,7 @@ dependencies: - mash=2.3 - fastani - busco=5 - - r-base + - r-base>=3.5 - r-ggplot2 - r-ape - graphviz diff --git a/conda_macos.yaml b/conda_macos.yaml deleted file mode 100644 index 6225cc6c281867e19859e2abb192ca23a03039bf..0000000000000000000000000000000000000000 --- a/conda_macos.yaml +++ /dev/null @@ -1,26 +0,0 @@ -channels: - - conda-forge - - bioconda -dependencies: - - openjdk=8.0 - - maven - - python=3.7 - - kmc=3.0.1 - - mcl - - mafft - - iqtree - - fasttree - - mash=2.3 - - fastani - #- busco=5 #causes conflicts - - r-base - - r-ggplot2 - - r-ape - - graphviz - - aster=1.3 - #- bcftools>=1.12 #can only be installed from this YAML without strict channel priority - - tabix - - blast - - pal2nal>=14.1 - - paml>=4.10.6 - #- r-cowplot>=1.1.1 #causes conflicts diff --git a/docs/source/developer_guide/install.rst b/docs/source/developer_guide/install.rst index 2025d2a1b9994cf8fce8fb478c968b81857e120f..dc22dbef581c614105281141a1a475474bf331f8 100644 --- a/docs/source/developer_guide/install.rst +++ b/docs/source/developer_guide/install.rst @@ -27,19 +27,16 @@ below. Install dependencies using Conda ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -To install all dependencies into a separate environment, run the -following commands. Please choose the conda_linux.yaml or -conda_macos.yaml file depending on your operating system. These files -be found in the `release <https://git.wur.nl/bioinformatics/pantools/-/ -releases>`_ or in the PanTools home when working with the git itself. There is a -difference between the two files because not all tools are available for macOS. -For smooth dependency resolving with conda, it is recommended to use strict -channel priority and to only use the bioconda and conda-forge channels. +To install all dependencies into a separate environment, run the following +commands. Please use the conda.yaml file which can be found in the +`release <https://git.wur.nl/bioinformatics/pantools/-/releases>`_ or in the +PanTools home when working with the git itself. For smooth dependency resolving +with conda, it is recommended to use strict channel priority and to only use the +bioconda and conda-forge channels. .. code:: bash - $ mamba env create -n pantools -f conda_linux.yaml #for Linux - $ mamba env create -n pantools -f conda_macos.yaml #for macOS + $ mamba env create -n pantools -f conda.yaml -------------- diff --git a/docs/source/developer_guide/release.rst b/docs/source/developer_guide/release.rst index f5875ce5a8053b9469bc2c5097ed32d904cfbd31..dccaf5d4cc02ea71f92d5f0eab5a5b52a262fcdc 100644 --- a/docs/source/developer_guide/release.rst +++ b/docs/source/developer_guide/release.rst @@ -16,6 +16,6 @@ The following steps are required to create a new release of PanTools: and delete the **new release branch**. 9. Put a tag on the merge commit in the **current stable release branch**. 10. Locally compile PanTools and put the compiled JAR file on the server. -11. Create a release from the tag and attach 1) the compiled JAR file, 2) the - ``conda_linux.yaml`` file, and 3) the ``conda_macos.yaml`` file. +11. Create a release from the tag and attach 1) the compiled JAR file, and 2) + the ``conda.yaml`` file. 12. Update the bioconda recipe for the new release. diff --git a/docs/source/user_guide/install.rst b/docs/source/user_guide/install.rst index c88de86ec8128077646e5230dbdce22741ed2a78..0a7f37345b44057884f6af8284c83b6ed6e40b59 100644 --- a/docs/source/user_guide/install.rst +++ b/docs/source/user_guide/install.rst @@ -20,8 +20,8 @@ For users that don't have conda installed, we recommend to install conda first: .. substitution-code-block:: bash - $ wget https://repo.anaconda.com/miniconda/Miniconda3-latest-Linux-x86_64.sh - $ bash Miniconda3-latest-Linux-x86_64.sh + $ curl -L -O "https://github.com/conda-forge/miniforge/releases/latest/download/Miniforge3-$(uname)-$(uname -m).sh" + $ bash Miniforge3-$(uname)-$(uname -m).sh # Follow the instructions on the screen # Restart your terminal @@ -34,23 +34,13 @@ For users that don't have conda installed, we recommend to install conda first: # Restart your terminal -Next, we recommend to install mamba into the conda base environment to enable -much faster dependency solving: - -.. substitution-code-block:: bash - - $ conda install -n base mamba - -Finally, we can install PanTools. We recommend to install PanTools into a +Now, we can install PanTools. We recommend to install PanTools into a separate environment. Please make sure you install the most recent versions of the tools. -NB: not all dependencies are available for macOS, therefore please use the -correct line from this code block depending on your operating system: .. substitution-code-block:: bash - $ mamba create -n pantools pantools bcftools busco # Linux - $ mamba create -n pantools pantools # macOS + $ mamba create -n pantools pantools Please make sure to activate the environment before using PanTools: @@ -77,19 +67,15 @@ environment that has all dependencies. Install dependencies using conda ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Please choose the conda_linux.yaml or conda_macos.yaml file depending on your -operating system. These files be found on the -`release page <https://git.wur.nl/bioinformatics/pantools/-/releases>`_. The -difference between the two files comes from some dependencies conflicting on -macOS but not on Linux. Only on Linux a full installation of PanTools is -possible. On macOS, some functionalities will not be available. +All dependencies are listed in conda.yaml which can be found on the +`release page <https://git.wur.nl/bioinformatics/pantools/-/releases>`_. For users that don't have conda installed, we recommend to install conda first: .. substitution-code-block:: bash - $ wget https://repo.anaconda.com/miniconda/Miniconda3-latest-Linux-x86_64.sh - $ bash Miniconda3-latest-Linux-x86_64.sh + $ curl -L -O "https://github.com/conda-forge/miniforge/releases/latest/download/Miniforge3-$(uname)-$(uname -m).sh" + $ bash Miniforge3-$(uname)-$(uname -m).sh # Follow the instructions on the screen # Restart your terminal @@ -102,19 +88,11 @@ For users that don't have conda installed, we recommend to install conda first: # Restart your terminal -Next, we recommend to install mamba into the conda base environment to enable -much faster dependency solving: - -.. substitution-code-block:: bash - - $ conda install mamba -n base -c conda-forge - -Finally, we can install all dependencies into a separate environment: +Now, we can install all dependencies into a separate environment: .. substitution-code-block:: bash - $ mamba env create -n pantools -f conda_linux.yaml #for Linux - $ mamba env create -n pantools -f conda_macos.yaml #for macOS + $ mamba env create -n pantools -f conda.yaml Please make sure to activate the environment before using PanTools: diff --git a/src/main/java/nl/wur/bif/pantools/construction/index/IndexDatabase.java b/src/main/java/nl/wur/bif/pantools/construction/index/IndexDatabase.java index b92287659e08f111b4d766a026a7d28d30e26338..4043a4fad2969e06818ec0f3222976762da8a177 100755 --- a/src/main/java/nl/wur/bif/pantools/construction/index/IndexDatabase.java +++ b/src/main/java/nl/wur/bif/pantools/construction/index/IndexDatabase.java @@ -16,7 +16,7 @@ import java.util.logging.Level; import java.util.logging.Logger; import static nl.wur.bif.pantools.utils.Globals.*; -import static nl.wur.bif.pantools.utils.Utils.executeCommand; +import static nl.wur.bif.pantools.utils.Utils.runCommandWithOutput; /** * Implements all the functionality to build a KMC-based kmer index. @@ -179,7 +179,7 @@ public final class IndexDatabase { public IndexDatabase(String index_path, String genomes_path_file, SequenceDatabase genomeDb, int k) { int i, j, number_of_pages, record_size, page_size, full_page_size; long number_of_prefixes, longest_scaffold = 0; - Pantools.logger.info("Creating index in {}", index_path); + Pantools.logger.debug("Creating index in {}", index_path); try { Files.createDirectory(Paths.get(index_path)); Run_KMC(index_path, genomes_path_file, genomeDb, k); @@ -212,7 +212,7 @@ public final class IndexDatabase { offset_len = (int)Math.round(Math.ceil( Math.log(longest_scaffold) / Math.log(2) / 8)); poniter_length = 2 * id_len + offset_len + 1; write_info(index_path, "sorted"); - Pantools.logger.info("Indexing kmers..."); + Pantools.logger.debug("Indexing kmers..."); // load the prefix file into the memory Pantools.logger.trace("Loading prefixes in memory..."); number_of_prefixes = 1 << (2 * pre_len); @@ -283,21 +283,31 @@ public final class IndexDatabase { // move current index files to directory old_darabase try{ - // make new index for new genomes + // make new index for new genomes Pantools.logger.info("Running KMC with K = {} ...", old_database.K); - executeCommand(KMC + " -cs127 -r -k" + old_database.K + " -t" + THREADS + " -ci1 -fm " + (genomeDb.num_genomes - previous_num_genomes > 1 ? "@" + + final String index_command = KMC + " -cs127 -r -k" + old_database.K + " -t" + THREADS + " -ci1 -fm " + (genomeDb.num_genomes - previous_num_genomes > 1 ? "@" + genomes_path_file.trim() : genomeDb.genome_names[previous_num_genomes + 1]) + - " " + index_path + "new " + index_path); - - - - // merge two indeces - executeCommand(KMC + "_tools union " + index_path + "sorted " + index_path + "new " + index_path + "merged"); + " " + index_path + "new " + index_path; + Pantools.logger.debug("KMC command: {}", index_command); + + final String index_output = runCommandWithOutput(index_command); + Pantools.logger.trace("KMC output: {}", index_output); + + // merge two indices + Pantools.logger.debug("Merging two indices."); + + final String merge_command = KMC + "_tools simple union " + index_path + "sorted " + index_path + "new " + index_path + "merged"; + Pantools.logger.debug("KMC command: {}", merge_command); + + final String merge_output = runCommandWithOutput(merge_command); + Pantools.logger.trace("KMC output: {}", merge_output); + pre_file = new RandomAccessFile(index_path + "merged" + PREFIX_FILE_EXTENTION, "r"); pre_file.seek(pre_file.length() - 8); header_pos = read_int(pre_file); pre_file.seek(pre_file.length() - 8 - header_pos); - // read the merged index properties + + // read the merged index properties K = read_int(pre_file); mode = read_int(pre_file); ctr_size = read_int(pre_file); @@ -314,10 +324,11 @@ public final class IndexDatabase { offset_len = (int)Math.round(Math.ceil( Math.log(longest_scaffold) / Math.log(2) / 8)); poniter_length = 2 * id_len + offset_len + 1; write_info(index_path, "merged"); - Pantools.logger.info("number of available kmers:\t{}", old_database.kmers_num); - Pantools.logger.info("number of new kmers:\t{}", (kmers_num - old_database.kmers_num)); + Pantools.logger.info("Number of available kmers:\t{}", old_database.kmers_num); + Pantools.logger.info("Number of new kmers:\t{}", (kmers_num - old_database.kmers_num)); Pantools.logger.info("Total number of kmers:\t{}", kmers_num); - // load the prefix file into the memory + + // load the prefix file into the memory Pantools.logger.trace("loading new prefixes in memory..."); number_of_prefixes = 1 << (2 * pre_len); prefix_ptr = new long[(int)number_of_prefixes]; @@ -335,7 +346,8 @@ public final class IndexDatabase { } pre_buff = null; pre_file.close(); - // mapping suffix file into the memory + + // mapping suffix file into the memory Pantools.logger.trace("Mapping new suffix file to memory..."); record_size = ctr_size + suf_len / 4; full_page_size = MAX_BYTE_COUNT / record_size * record_size; // in bytes @@ -422,60 +434,46 @@ public final class IndexDatabase { */ public void Run_KMC(String index_path, String genomes_path_file, SequenceDatabase genomeDb, int k) { double p = 0; - String output; try{ Runtime.getRuntime().exec(KMC); // to check if kmc is reachable if (k > -1) { // K is not given by the user, then calculate the optimal K + Pantools.logger.info("K is given by the user: {}", k); + K = k; if (K % 2 == 0) // Even values are problamatic to localization process K += 1; - - String kmc_command = KMC + " -cs127 -k" + K + " -t" + THREADS + " -ci1 -fm " + + + final String kmc_command = KMC + " -cs127 -k" + K + " -t" + THREADS + " -ci1 -fm " + (genomeDb.num_genomes > 1 ? "@" + genomes_path_file.trim() : genomeDb.genome_names[1]) + " " + index_path + "/kmers " + index_path; Pantools.logger.debug("KMC command: {}", kmc_command); - executeCommand(kmc_command); - if (new File(index_path + "/kmers.kmc_pre").exists() && new File(index_path + "/kmers.kmc_suf").exists()) { - final String kmcSortCommand = String.format( - "kmc_tools -t%1$d sort %2$s/kmers %2$s/sorted", - THREADS, - index_path); - output = executeCommand(kmcSortCommand); - // Small databases are usually sorted already - if (output.startsWith("This database contains sorted k-mers already!")) { - new File(index_path + "/kmers.kmc_pre").renameTo(new File(index_path + "/sorted.kmc_pre")); - new File(index_path + "/kmers.kmc_suf").renameTo(new File(index_path + "/sorted.kmc_suf")); - } else { - new File(index_path + "/kmers.kmc_pre").delete(); - new File(index_path + "/kmers.kmc_suf").delete(); - } + final String output = runCommandWithOutput(kmc_command); + Pantools.logger.trace("KMC output: {}", output); + + if (checkIfKmcIndexExists(index_path)) { + sortKmcIndex(index_path); } else { Pantools.logger.error("No kmc index found in {}", index_path); - System.exit(1); + throw new RuntimeException("Missing kmc index"); } } else { + Pantools.logger.info("K is not given by the user. Calculating the optimal K."); + K = Math.round((float)(Math.log(genomeDb.number_of_bytes * 200)/Math.log(4))); K += (K % 2 == 0 ? 1 : 0); do { K -= 2; - Pantools.logger.info("K = {}", K); - String kmc_command = KMC + " -cs127 -k" + K + " -t" + THREADS + " -ci1 -fm " + + Pantools.logger.debug("K = {}", K); + + final String kmc_command = KMC + " -cs127 -k" + K + " -t" + THREADS + " -ci1 -fm " + (genomeDb.num_genomes > 1 ? "@" + genomes_path_file.trim() : genomeDb.genome_names[1]) + " " + index_path + "/kmers " + index_path; Pantools.logger.debug("KMC command: {}", kmc_command); - executeCommand(kmc_command); - if (new File(index_path + "/kmers.kmc_pre").exists() && new File(index_path + "/kmers.kmc_suf").exists()) { - final String kmcSortCommand = String.format( - "kmc_tools -t%1$d sort %2$s/kmers %2$s/sorted", - THREADS, - index_path); - output = executeCommand(kmcSortCommand); - // Small databases are usually sorted already - if (output.startsWith("This database contains sorted k-mers already!")) { - new File(index_path + "/kmers.kmc_pre").renameTo(new File(index_path + "/sorted.kmc_pre")); - new File(index_path + "/kmers.kmc_suf").renameTo(new File(index_path + "/sorted.kmc_suf")); - } else { - new File(index_path + "/kmers.kmc_pre").delete(); - new File(index_path + "/kmers.kmc_suf").delete(); - } + + final String kmc_output = runCommandWithOutput(kmc_command); + Pantools.logger.trace("KMC output: {}", kmc_output); + + if (checkIfKmcIndexExists(index_path)) { + sortKmcIndex(index_path); + pre_file = new RandomAccessFile(index_path + "/sorted.kmc_pre", "r"); pre_file.seek(pre_file.length() - 8); header_pos = read_int(pre_file); @@ -499,37 +497,66 @@ public final class IndexDatabase { pre_file.close(); } else { Pantools.logger.error("No kmc index found in {}", index_path); - System.exit(1); + throw new RuntimeException("Missing kmc index"); } } while (p < 0.01); K += 2; - executeCommand(KMC + " -cs127 -k" + K + " -t" + THREADS + " -ci1 -fm " + - (genomeDb.num_genomes > 1 ? "@" + genomes_path_file.trim() : genomeDb.genome_names[1]) + " " + index_path + "/kmers " + index_path); - if (new File(index_path + "/kmers.kmc_pre").exists() && new File(index_path + "/kmers.kmc_suf").exists()) { - final String kmcSortCommand = String.format( - "kmc_tools -t%1$d sort %2$s/kmers %2$s/sorted", - THREADS, - index_path); - output = executeCommand(kmcSortCommand); - // Small databases are usually sorted already - if (output.startsWith("This database contains sorted k-mers already!")) { - new File(index_path + "/kmers.kmc_pre").renameTo(new File(index_path + "/sorted.kmc_pre")); - new File(index_path + "/kmers.kmc_suf").renameTo(new File(index_path + "/sorted.kmc_suf")); - } else { - new File(index_path + "/kmers.kmc_pre").delete(); - new File(index_path + "/kmers.kmc_suf").delete(); - } + + final String kmc_command = KMC + " -cs127 -k" + K + " -t" + THREADS + " -ci1 -fm " + + (genomeDb.num_genomes > 1 ? "@" + genomes_path_file.trim() : genomeDb.genome_names[1]) + " " + index_path + "/kmers " + index_path; + Pantools.logger.debug("KMC command: {}", kmc_command); + + final String kmc_output = runCommandWithOutput(kmc_command); + Pantools.logger.trace("KMC output: {}", kmc_output); + + if (checkIfKmcIndexExists(index_path)) { + sortKmcIndex(index_path); } else { Pantools.logger.error("No kmc index found in {}", index_path); - System.exit(1); + throw new RuntimeException("Missing kmc index"); } } - Pantools.logger.info("K = {}", K); + Pantools.logger.debug("K = {}", K); } catch (IOException ex) { Pantools.logger.warn("Failed to find K!"); } } + /** + * Checks if the KMC index exists in the given path + * @param index_path Path to the index database + * @return True if the KMC index exists, false otherwise + */ + private boolean checkIfKmcIndexExists(String index_path) { + Pantools.logger.debug("Checking if kmc index exists in {}", index_path); + boolean indexExists = new File(index_path + "/kmers.kmc_pre").exists() && new File(index_path + "/kmers.kmc_suf").exists(); + Pantools.logger.trace("KMC index exists: {}", indexExists); + return indexExists; + } + + /** + * Sorts the KMC index in the given path + * @param index_path Path to the index database + */ + private void sortKmcIndex(String index_path) { + Pantools.logger.debug("Sorting kmc index in {}", index_path); + + final String command = KMC + "_tools transform " + index_path + "/kmers sort " + index_path + "/sorted"; + Pantools.logger.debug("KMC command: {}", command); + + final String output = runCommandWithOutput(command); + Pantools.logger.trace("KMC output: {}", output); + + // Small databases are usually sorted already + if (output.startsWith("Warning: input database is already sorted.")) { + new File(index_path + "/kmers.kmc_pre").renameTo(new File(index_path + "/sorted.kmc_pre")); + new File(index_path + "/kmers.kmc_suf").renameTo(new File(index_path + "/sorted.kmc_suf")); + } else { + new File(index_path + "/kmers.kmc_pre").delete(); + new File(index_path + "/kmers.kmc_suf").delete(); + } + } + /** * Writes the index database specification in the info file of the index database * diff --git a/src/main/java/nl/wur/bif/pantools/utils/Utils.java b/src/main/java/nl/wur/bif/pantools/utils/Utils.java index 502569e8b70dbe621c98a9d39ed7be1937297844..aab4ca5681677b19f743799893b628a3d530a56e 100644 --- a/src/main/java/nl/wur/bif/pantools/utils/Utils.java +++ b/src/main/java/nl/wur/bif/pantools/utils/Utils.java @@ -540,25 +540,29 @@ public final class Utils { /** * Executes a shell command. - * - * @param command The command - * @return The output of the bash command - */ - public static String executeCommand(String command) { - StringBuilder exe_output = new StringBuilder(); - String line = ""; - Process p; + * @param command the shell command + * @return the output (both stdout and stderr) of the command + */ + public static String runCommandWithOutput(String command) { + List<String> cmd = new ArrayList<>(); + cmd.add("sh"); + cmd.add("-c"); + cmd.add(command); + ProcessBuilder pb = new ProcessBuilder(cmd); try { - p = Runtime.getRuntime().exec(command); + Process p = pb.start(); p.waitFor(); BufferedReader reader = new BufferedReader(new InputStreamReader(p.getInputStream())); + StringBuilder output = new StringBuilder(); + String line; while ((line = reader.readLine()) != null) { - exe_output.append(line).append("\n"); + output.append(line).append("\n"); } - } catch (Exception e) { + return output.toString(); + } catch (IOException | InterruptedException e) { e.printStackTrace(); + throw new RuntimeException(e.getMessage()); } - return exe_output.toString(); } /**