All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.conqat.lib.commons.io.DotNetUtils Maven / Gradle / Ivy

The newest version!
/*
 * Copyright (c) CQSE GmbH
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.conqat.lib.commons.io;

import java.io.File;
import java.io.IOException;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.conqat.lib.commons.error.FormatException;
import org.conqat.lib.commons.string.StringUtils;
import org.conqat.lib.commons.system.SystemUtils;
import org.conqat.lib.commons.version.Version;

/**
 * Utility class for .NET related stuff. Executes a .NET process and checks for a valid .NET runtime
 * installation.
 */
public class DotNetUtils {

	private static final Logger LOGGER = LogManager.getLogger(DotNetUtils.class);

	/**
	 * Executable name of mono, used to execute .NET programs on non-windows machines.
	 */
	public static final String MONO_COMMAND = "mono";

	/**
	 * Executable name of dotnet, used to execute .NET (>4, core) DLLs.
	 * 

* See dotnet command online * reference for details. */ private static final String DOTNET_COMMAND = "dotnet"; /** Regex for extracting the version of installed .NET runtimes. **/ private static final Pattern DOTNET_RUNTIME_VERSION_PATTERN = Pattern .compile("Microsoft\\.NETCore\\.App\\s+?([0-9.]+)\\s+?\\[.+\\]"); /** The minimum .NET version that we require. **/ private static final Version DOTNET_MIN_VERSION = new Version(6, 0); /** * Creates a {@link ProcessBuilder} for executing a .net assembly. If the current OS is not Windows * mono will be used for executing the assembly. */ public static ProcessBuilder createDotNetProcessBuilder(String... arguments) { ProcessBuilder builder = new ProcessBuilder(); if (!SystemUtils.isWindows()) { builder.command().add(MONO_COMMAND); } builder.command().addAll(Arrays.asList(arguments)); return builder; } /** * Tries to execute the {@code dotnet} tool to determine if a suitable .NET runtime is installed. We * assume that the newest available runtime version is the one used per default on the current * system. If this runtime version is equal or newer to the minimal required version * ({@link #DOTNET_MIN_VERSION}), this check succeeds. *

* Error messages are generated and returned for the following cases: *

    *
  • The {@code dotnet} tool returned an error, indicating that something is wrong with the * installation.
  • *
  • No .NET runtime could be detected.
  • *
  • The newest detected .NET runtime is too old.
  • *
  • The {@code dotnet} tool could not be executed. This can happen due a missing or invalid .NET * installation or due to OS issues.
  • *
* * @return {@code null} if the check succeeds without errors, else an error message string. */ public static @Nullable String checkDotnetIsInstalled() { try { ProcessUtils.ExecutionResult result = ProcessUtils .execute(new String[] { DotNetUtils.DOTNET_COMMAND, "--list-runtimes" }); String errorMessage = performDotnetCheck(result.getReturnCode(), result.getStdout(), result.getStderr()); if (errorMessage != null) { logDotNetInstallationDetails(result); } return errorMessage; } catch (IOException e) { logDotNetInstallationDetails(null); return String.format( "Error during the detection of the .NET runtime. `dotnet` could not be executed. " + "The .NET runtime version %s or higher has to be installed.\nFurther details: %s", DOTNET_MIN_VERSION, e.getMessage()); } } private static void logDotNetInstallationDetails(ProcessUtils.ExecutionResult executionResult) { LOGGER.error(".NET installation could not be detected or did not fulfill requirements. " + "This log will contain additional warning messages to simplify debugging."); String path = System.getenv("PATH"); LOGGER.warn("Value of $PATH: " + path); Optional fullPath = findUsedDotNetInstallation(path); if (!fullPath.isPresent()) { LOGGER.warn("Could not find " + DOTNET_COMMAND + " executable on the path!"); } else { LOGGER.warn("First installation found on PATH: " + fullPath.get().getAbsolutePath()); LOGGER.warn("Can execute: " + fullPath.get().canExecute()); } if (executionResult != null) { LOGGER.warn("Execution returned error code " + executionResult.getReturnCode()); LOGGER.warn("Std out: " + executionResult.getStdout()); LOGGER.warn("Std err: " + executionResult.getStderr()); } } private static Optional findUsedDotNetInstallation(String path) { for (String directory : path.split(File.pathSeparator)) { File file = new File(directory, DOTNET_COMMAND); if (file.isFile()) { return Optional.of(file); } file = new File(directory, DOTNET_COMMAND + ".exe"); if (file.isFile()) { return Optional.of(file); } } return Optional.empty(); } /** * Performs the .NET version check given the return code, stdout, and stderr of a * {@code dotnet --list-runtimes} invocation. *

* Error messages are generated and returned for the following cases: *

    *
  • The {@code dotnet} tool returned an error, indicating that something is wrong with the * installation.
  • *
  • No .NET runtime could be detected.
  • *
  • The newest detected .NET runtime is too old.
  • *
* * @return {@code null} if everything is fine, else an error message string. */ /* package */ static @Nullable String performDotnetCheck(int returnCode, @NonNull String stdout, @NonNull String stderr) { if (returnCode != 0) { return String.format("Error executing `dotnet` (%d). %s", returnCode, generateGenericInstallErrorMessage(stdout, stderr)); } List installedVersions = extractRuntimeVersions(stdout); Optional highestVersion = installedVersions.stream().max(Comparator.naturalOrder()); if (!highestVersion.isPresent()) { return String.format("No .NET runtime detected. %s", generateGenericInstallErrorMessage(stdout, stderr)); } if (!highestVersion.get().isGreaterOrEqual(DOTNET_MIN_VERSION)) { return String.format("Unsupported .NET runtime detected (latest: %s). %s", highestVersion.get(), generateGenericInstallErrorMessage(stdout, stderr)); } return null; } /** * Generates the generic trailing part for the error messages of the .NET runtime version detection. */ private static @NonNull String generateGenericInstallErrorMessage(String stdout, String stderr) { return String.format( "Please make sure that version %s or higher of the .NET runtime is installed.\n" + "std out: %s\nstd error: %s", DOTNET_MIN_VERSION, StringUtils.isEmpty(stdout) ? "-empty-" : stdout, StringUtils.isEmpty(stderr) ? "-empty-" : stderr); } /** * Executes a .NET program in a thread-safe way. */ public static ProcessUtils.ExecutionResult executeDotNet(String[] completeArguments) throws IOException { return ProcessUtils.execute(createDotNetProcessBuilder(completeArguments)); } /** * Extracts all .NET runtime versions from the given raw output of a {@code dotnet --list-runtimes} * invocation. */ /* package */static @NonNull List extractRuntimeVersions(@NonNull String rawVersionsList) { return StringUtils.splitLinesAsList(rawVersionsList).stream().map(DotNetUtils::extractRuntimeVersionFromLine) .filter(Objects::nonNull).collect(Collectors.toList()); } /** * Extracts the .NET runtime version from the given single raw output line of a * {@code dotnet --list-runtimes} invocation using {@link #DOTNET_RUNTIME_VERSION_PATTERN}. */ /* package */ static @Nullable Version extractRuntimeVersionFromLine(@NonNull String line) { Matcher matcher = DOTNET_RUNTIME_VERSION_PATTERN.matcher(line); if (!matcher.find() || matcher.groupCount() != 1) { return null; } try { return Version.parseVersion(matcher.group(1)); } catch (FormatException e) { return null; } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy