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

toolkit.cli.37.0.0.source-code.OrtMain.kt Maven / Gradle / Ivy

Go to download

Part of the OSS Review Toolkit (ORT), a suite to automate software compliance checks.

There is a newer version: 42.1.0
Show newest version
/*
 * Copyright (C) 2017 The ORT Project Authors (see )
 *
 * 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
 *
 *     https://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.
 *
 * SPDX-License-Identifier: Apache-2.0
 * License-Filename: LICENSE
 */

package org.ossreviewtoolkit.cli

import ch.qos.logback.classic.Level
import ch.qos.logback.classic.Logger

import com.github.ajalt.clikt.completion.completionOption
import com.github.ajalt.clikt.core.CliktCommand
import com.github.ajalt.clikt.core.Context
import com.github.ajalt.clikt.core.context
import com.github.ajalt.clikt.core.main
import com.github.ajalt.clikt.core.subcommands
import com.github.ajalt.clikt.output.MordantHelpFormatter
import com.github.ajalt.clikt.parameters.options.associate
import com.github.ajalt.clikt.parameters.options.convert
import com.github.ajalt.clikt.parameters.options.default
import com.github.ajalt.clikt.parameters.options.flag
import com.github.ajalt.clikt.parameters.options.option
import com.github.ajalt.clikt.parameters.options.switch
import com.github.ajalt.clikt.parameters.options.versionOption
import com.github.ajalt.clikt.parameters.types.file
import com.github.ajalt.mordant.rendering.TextColors
import com.github.ajalt.mordant.rendering.Theme
import com.github.ajalt.mordant.rendering.VerticalAlign
import com.github.ajalt.mordant.rendering.Widget
import com.github.ajalt.mordant.table.ColumnWidth
import com.github.ajalt.mordant.table.grid

import kotlin.system.exitProcess

import org.ossreviewtoolkit.model.config.LicenseFilePatterns
import org.ossreviewtoolkit.model.config.OrtConfiguration
import org.ossreviewtoolkit.plugins.commands.api.OrtCommand
import org.ossreviewtoolkit.utils.common.EnvironmentVariableFilter
import org.ossreviewtoolkit.utils.common.MaskedString
import org.ossreviewtoolkit.utils.common.Os
import org.ossreviewtoolkit.utils.common.expandTilde
import org.ossreviewtoolkit.utils.common.mebibytes
import org.ossreviewtoolkit.utils.common.replaceCredentialsInUri
import org.ossreviewtoolkit.utils.ort.Environment
import org.ossreviewtoolkit.utils.ort.ORT_CONFIG_DIR_ENV_NAME
import org.ossreviewtoolkit.utils.ort.ORT_CONFIG_FILENAME
import org.ossreviewtoolkit.utils.ort.ORT_DATA_DIR_ENV_NAME
import org.ossreviewtoolkit.utils.ort.ORT_NAME
import org.ossreviewtoolkit.utils.ort.ortConfigDirectory
import org.ossreviewtoolkit.utils.ort.ortDataDirectory
import org.ossreviewtoolkit.utils.ort.printStackTrace

import org.slf4j.LoggerFactory

private const val REQUIRED_OPTION_MARKER = "*"

// A priority value that is higher than any Clikt built-in value for allocating width.
private const val HIGHEST_PRIORITY_FOR_WIDTH = 100

private val ORT_LOGO = """
     ______________________________
    /        \_______   \__    ___/
    |    |   | |       _/ |    |
    |    |   | |    |   \ |    |
    \________/ |____|___/ |____|
""".trimIndent()

/**
 * The entry point for the application with [args] being the list of arguments.
 */
fun main(args: Array) {
    Os.fixupUserHomeProperty()
    OrtMain().main(args)
    exitProcess(0)
}

class OrtMain : CliktCommand(ORT_NAME) {
    private val configFile by option("--config", "-c", help = "The path to a configuration file.")
        .convert { it.expandTilde() }
        .file(mustExist = true, canBeFile = true, canBeDir = false, mustBeWritable = false, mustBeReadable = true)
        .default(ortConfigDirectory.resolve(ORT_CONFIG_FILENAME))

    private val logLevel by option(help = "Set the verbosity level of log output.").switch(
        "--error" to Level.ERROR,
        "--warn" to Level.WARN,
        "--info" to Level.INFO,
        "--debug" to Level.DEBUG
    ).default(Level.WARN)

    private val stacktrace by option(help = "Print out the stacktrace for all exceptions.").flag()

    private val configArguments by option(
        "-P",
        help = "Override a key-value pair in the configuration file. For example: " +
            "-P ort.scanner.storages.postgres.connection.schema=testSchema"
    ).associate()

    private val helpAll by option(
        "--help-all",
        help = "Display help for all subcommands."
    ).flag()

    private val env = Environment()

    init {
        completionOption()

        context {
            helpFormatter = { MordantHelpFormatter(context = it, REQUIRED_OPTION_MARKER, showDefaultValues = true) }
        }

        subcommands(OrtCommand.ALL.values)

        versionOption(
            version = env.ortVersion,
            names = setOf("--version", "-v"),
            help = "Show the version and exit.",
            message = { it }
        )
    }

    override val invokeWithoutSubcommand = true

    override fun helpEpilog(context: Context) = "$REQUIRED_OPTION_MARKER denotes required options."

    override fun run() {
        // This is somewhat dirty: For logging, ORT uses the Log4j API (because of its nice Kotlin API), but Logback as
        // the implementation (for its robustness). The former API does not provide a way to set the root log level,
        // only the Log4j implementation does (via org.apache.logging.log4j.core.config.Configurator). However, the
        // SLF4J API does provide a way to get the root logger and set its level. That is why ORT's CLI additionally
        // depends on the SLF4J API, just to be able to set the root log level below.
        val rootLogger = LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME) as Logger
        rootLogger.level = logLevel

        // Make the parameter globally available.
        printStackTrace = stacktrace

        // Make options available to subcommands and apply static configuration.
        val ortConfig = OrtConfiguration.load(args = configArguments, file = configFile)
        currentContext.findOrSetObject { ortConfig }
        LicenseFilePatterns.configure(ortConfig.licenseFilePatterns)

        EnvironmentVariableFilter.reset(
            ortConfig.deniedProcessEnvironmentVariablesSubstrings,
            ortConfig.allowedProcessEnvironmentVariableNames
        )

        if (helpAll) {
            registeredSubcommands().forEach {
                echo(it.getFormattedHelp())
                echo()
            }
        } else {
            echo(getOrtHeader(env.ortVersion))
            echo("Looking for ORT configuration in the following file:")
            echo("\t" + configFile.absolutePath + " (does not exist)".takeIf { !configFile.exists() }.orEmpty())
            echo()
        }
    }

    private fun getOrtHeader(version: String): Widget =
        grid {
            column(0) {
                width = ColumnWidth(width = null, expandWeight = null, priority = HIGHEST_PRIORITY_FOR_WIDTH)
            }

            column(1) { verticalAlign = VerticalAlign.BOTTOM }
            padding { bottom = 1 }

            row {
                cell(ORT_LOGO) { style = TextColors.cyan }

                val commandName = currentContext.invokedSubcommand?.commandName
                val command = commandName?.let { " '${TextColors.cyan(commandName)}'" }.orEmpty()

                val userName = System.getProperty("user.name").takeUnless { it == "?" } ?: Os.userHomeDirectory.name
                val user = userName?.let { " as '${Theme.Default.warning(userName)}'" }.orEmpty()

                val maxMemInMib = env.maxMemory / 1.mebibytes

                cell(
                    """
                        The OSS Review Toolkit, version ${Theme.Default.info(version)},
                        built with JDK ${env.buildJdk}, running under Java ${env.javaVersion}.
                        Executing$command$user on ${env.os}
                        with ${env.processors} CPUs and a maximum of $maxMemInMib MiB of memory.
                    """.trimIndent()
                )
            }

            row {
                val content = mutableListOf("Environment variables:")

                listOf(
                    ORT_CONFIG_DIR_ENV_NAME to ortConfigDirectory.path,
                    ORT_DATA_DIR_ENV_NAME to ortDataDirectory.path,
                    *env.variables.toList().toTypedArray()
                ).mapTo(content) { (key, value) ->
                    val safeValue = value.replaceCredentialsInUri(MaskedString.DEFAULT_MASK)
                    "${Theme.Default.info(key)} = ${Theme.Default.warning(safeValue)}"
                }

                cell(content.joinToString("\n")) { columnSpan = 2 }
            }
        }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy