246909a2创建于 5月26日历史提交
/*
 * Copyright (c) 2024 Huawei Device Co., Ltd.
 * 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.
 */

#include "util/options.h"

#include <cstdint>
#include <cstdio>
#include <cstring>
#include <dirent.h>
#include <getopt.h>
#include <sys/stat.h>
#include <unistd.h>

#include <map>

#include "util/common.h"
#include "util/file.h"
#include "util/logger.h"
#include "util/string_helper.h"
#include "util/string_builder.h"

namespace OHOS {
namespace Idl {
const char *Options::optSupportArgs = "hvcs:m:p:d:r:o:D:t:";
static struct option g_longOpts[] = {
    {"help",                  no_argument,       nullptr, 'h'},
    {"version",               no_argument,       nullptr, 'v'},
    {"client-enable",         no_argument,       nullptr, 'C'},
    {"system",                required_argument, nullptr, 'S'},
    {"mode",                  required_argument, nullptr, 'm'},
    {"gen-c",                 no_argument,       nullptr, '1'},
    {"gen-cpp",               no_argument,       nullptr, '2'},
    {"gen-java",              no_argument,       nullptr, '3'},
    {"gen-rust",              no_argument,       nullptr, '4'},
    {"gen-ts",                no_argument,       nullptr, '5'},
    {"package",               required_argument, nullptr, 'p'},
    {"dump-ast",              no_argument,       nullptr, 'a'},
    {"dump-metadata",         no_argument,       nullptr, 'e'},
    {"hash",                  no_argument,       nullptr, 'H'},
    {"log-domainid",          required_argument, nullptr, 'i'},
    {"log-tag",               required_argument, nullptr, 'g'},
    {"intf-type",             required_argument, nullptr, 'T'},
    {"generate-with-version", no_argument,       nullptr, 'V'},
    {nullptr,                 0,                 nullptr,  0 }
};

Options &Options::GetInstance()
{
    static Options option;
    return option;
}

bool Options::Parse(int argc, char *argv[])
{
    int ret = true;
    program = argv[0];
    opterr = 0;
    int op = 0;
    int optIndex = 0;

    if (argc < 0 || argc > UINT16_MAX) {
        Logger::E(TAG, "argc out of range");
        return false;
    }

    while ((op = getopt_long_only(argc, argv, optSupportArgs, g_longOpts, &optIndex)) != -1) {
        if (optind > 0 && optind <= argc) {
            ret = ParseSingle(op, std::string(argv[optind - 1]));
        }
    }

    if (doCompile) {
        int i = 0;
        while (++i < argc) {
            if (argv[i][0] != '-' && StringHelper::EndWith(argv[i], ".idl")) {
                AddSources(argv[i]);
            }
        }
    }

    return ret ? CheckOptions() : ret;
}

bool Options::ParseSingle(int option, std::string optVal)
{
    bool ret = true;
    switch (option) {
        case 'h':
            doShowUsage = true;
            break;
        case 'v':
            doShowVersion = true;
            break;
        case 'a':
            doDumpAST = true;
            break;
        case 'e':
            doDumpMetadata = true;
            break;
        case 'H':
            doHashKey = true;
            break;
        case 'c':
            doCompile = true;
            break;
        case 'C':
            doClient = true;
            break;
        case '1':
            SetLanguage("c");
            break;
        case '2':
            SetLanguage("cpp");
            break;
        case '3':
            SetLanguage("java");
            break;
        case '4':
            SetLanguage("rust");
            break;
        case '5':
            SetLanguage("ts");
            break;
        case 'V':
            generateWithVersion = true;
            break;
        default:
            ret = ParseOptionWithValue(option, optVal);
            break;
    }
    return ret;
}

bool Options::ParseOptionWithValue(int option, std::string optVal)
{
    bool ret = true;
    StringBuilder errors;

    switch (option) {
        case 'o':
            outPutFile = optarg;
            break;
        case 'S':
            ret = SetSystemLevel(optarg);
            break;
        case 'm':
            ret = SetGenerateMode(optarg);
            break;
        case 's':
            SetMetadataFile(optarg);
            break;
        case 'p':
            SetPackage(optarg);
            break;
        case 'D':
            AddSourcesByDir(optarg);
            break;
        case 'd':
            SetOutDir(optarg);
            break;
        case 'r':
            ret = AddPackagePath(optarg);
            break;
        case 't':
            ret = SetHiTrace(optarg);
            break;
        case 'i':
            ret = SetLogDomainId(optarg);
            break;
        case 'g':
            ret = SetLogTag(optarg);
            break;
        case 'T':
            ret = SetInterfaceType(optarg);
            break;
        default:
            errors.Append(optVal.c_str());
            break;
    }
    illegalOptions = errors.ToString();
    return ret;
}

bool Options::SetSystemLevel(const std::string &system)
{
    static std::map<std::string, SystemLevel> systemLevelMap = {
        {"mini", SystemLevel::MINI},
        {"lite", SystemLevel::LITE},
        {"full", SystemLevel::FULL},
    };

    auto levelIter = systemLevelMap.find(system);
    if (levelIter == systemLevelMap.end()) {
        Logger::E(TAG, "invalid system level set: '%s', please input mini/lite/full", system.c_str());
        return false;
    }
    systemLevel = levelIter->second;
    return true;
}

bool Options::SetGenerateMode(const std::string &mode)
{
    static std::map<std::string, GenMode> codeGenMap = {
        {"low", GenMode::LOW},
        {"passthrough", GenMode::PASSTHROUGH},
        {"ipc", GenMode::IPC},
        {"kernel", GenMode::KERNEL},
    };

    auto codeGenIter = codeGenMap.find(mode);
    if (codeGenIter == codeGenMap.end()) {
        Logger::E(TAG, "invalid generate mode set: '%s', please input low/passthrough/ipc/kernel/sa.", mode.c_str());
        return false;
    }
    genMode = codeGenIter->second;
    return true;
}

bool Options::SetLanguage(const std::string &language)
{
    static const std::map<std::string, Language> languageMap = {
        {"c", Language::C},
        {"cpp", Language::CPP},
        {"java", Language::JAVA},
        {"rust", Language::RUST},
        {"ts", Language::TS},
    };

    const auto kindIter = languageMap.find(language);
    if (kindIter == languageMap.end()) {
        Logger::E(TAG, "invalid language '%s', please input c, cpp, java, rust or ts", language.c_str());
        return false;
    }

    doGenerateCode = true;
    genLanguage = kindIter->second;
    return true;
}

bool Options::SetMetadataFile(const std::string &file)
{
    doSaveMetadata = true;
    metadataFile = file;
    return true;
}

void Options::SetPackage(const std::string &infPackage)
{
    idlPackage = infPackage;
}

void Options::AddSources(const std::string &sourceFile)
{
    std::string realPath = File::AdapterRealPath(sourceFile);
    if (realPath.empty()) {
        Logger::E(TAG, "invalid idl file path:%s", sourceFile.c_str());
        return;
    }

    if (sourceFiles.insert(realPath).second == false) {
        Logger::E(TAG, "this idl file has been add:%s", sourceFile.c_str());
        return;
    }
}

void Options::AddSourcesByDir(const std::string &dir)
{
    sourceDir = dir;
    std::set<std::string> files = File::FindFiles(sourceDir);
    if (!files.empty()) {
        doCompile = true;
        sourceFiles.insert(files.begin(), files.end());
    }
}

bool Options::AddPackagePath(const std::string &packagePath)
{
    size_t index = packagePath.find(":");
    if (index == std::string::npos || index == packagePath.size() - 1) {
        Logger::E(TAG, "invalid option parameters '%s'.", packagePath.c_str());
        return false;
    }

    std::string package = packagePath.substr(0, index);
    std::string path = File::AdapterRealPath(packagePath.substr(index + 1));
    if (path.empty()) {
        Logger::E(TAG, "invalid path '%s'.", packagePath.substr(index + 1).c_str());
        return false;
    }

    auto it = packagePathMap.find(package);
    if (it != packagePathMap.end()) {
        Logger::E(TAG, "The '%s:%s' has been set.", package.c_str(), path.c_str());
        return false;
    }

    packagePathMap[package] = path;
    return true;
}

void Options::SetOutDir(const std::string &dir)
{
    doOutDir = true;
    genDir = dir;
}

bool Options::CheckOptions()
{
    if (doShowUsage || doShowVersion) {
        return true;
    }
    if (HasErrors()) {
        ShowErrors();
        return false;
    }
    if (interfaceType == InterfaceType::SA) {
        return CheckSaOptions();
    } else if (interfaceType == InterfaceType::HDI) {
        return CheckHdiOptions();
    } else if (interfaceType == InterfaceType::SM ||
                interfaceType == InterfaceType::SAM ||
                interfaceType == InterfaceType::SAM_SM ||
                interfaceType == InterfaceType::SAM_UDS ||
                interfaceType == InterfaceType::SM_UDS) {
        return CheckSmOptions();
    } else {
        Logger::E(TAG, "Interface type 'intf-type' value '%d' invalid, please input 'hdi' or 'sa'.", interfaceType);
        return false;
    }
}

bool Options::CheckSaOptions()
{
    if (!DoSupportSaType()) {
        return false;
    }

    if (sourceFiles.empty() && !DoSaveMetadata()) {
        Logger::E(TAG, "Option 'intf-type sa' must set idl file.");
        return false;
    }

    if (!DoLegalLog()) {
        Logger::E(TAG, "Option 'log-domainid' and 'log-tag' must be used together.");
        return false;
    }

    attribute.doHitrace = doHitrace;
    attribute.hitraceTag = hitraceTag;
    attribute.logTag = logTag;
    attribute.domainId = domainId;
    attribute.doLog = DoLogOn();

    return true;
}

bool Options::DoSupportSaType()
{
    bool ret = true;
    if (genLanguage != Language::CPP && genLanguage != Language::RUST && genLanguage != Language::TS) {
        Logger::E(TAG, "Option 'intf-type sa' only support language option 'gen-cpp', 'gen-rust' or 'gen-ts'.");
        ret = false;
    }

    if (systemLevel != SystemLevel::INIT) {
        Logger::E(TAG, "Option 'intf-type sa' not support option 'system'.");
        ret = false;
    }

    if (genMode != GenMode::INIT) {
        Logger::E(TAG, "Option 'intf-type sa' not support option 'm' or 'mode'.");
        ret = false;
    }

    if (!idlPackage.empty()) {
        Logger::E(TAG, "Option 'intf-type sa' not support option 'p' or 'package'.");
        ret = false;
    }

    if (doHashKey) {
        Logger::E(TAG, "Option 'intf-type sa' not support option 'hash'.");
        ret = false;
    }

    if (!packagePathMap.empty()) {
        Logger::E(TAG, "Option 'intf-type sa' not support option 'r'.");
        ret = false;
    }

    if (!sourceDir.empty()) {
        Logger::E(TAG, "Option 'intf-type sa' not support option 'D'.");
        ret = false;
    }

    if (!outPutFile.empty()) {
        Logger::E(TAG, "Option 'intf-type sa' not support option 'o'.");
        ret = false;
    }

    if (!ret) {
        printf("Use \"-h, --help\" to show usage.\n");
    }
    return ret;
}

bool Options::CheckHdiOptions()
{
    SetHdiDefaultOption();
    if (!DoSupportHdiType()) {
        return false;
    }

    if (doCompile) {
        if (!DoGetHashKey() && !doDumpAST && !doGenerateCode && !doOutDir) {
            Logger::E(TAG, "nothing to do.");
            return false;
        }

        if (!doGenerateCode && doOutDir) {
            Logger::E(TAG, "no target language.");
            return false;
        }

        if (doGenerateCode && !doOutDir) {
            Logger::E(TAG, "no out directory.");
            return false;
        }
    } else {
        if (DoGetHashKey() || doDumpAST || doGenerateCode || doOutDir) {
            Logger::E(TAG, "no idl files.");
            return false;
        }
    }

    return true;
}

void Options::SetHdiDefaultOption()
{
    if (systemLevel == SystemLevel::INIT) {
        systemLevel = SystemLevel::FULL;
    }
    if (genMode == GenMode::INIT) {
        genMode = GenMode::IPC;
    }
    return;
}

bool Options::DoSupportHdiType()
{
    bool ret = true;

    if (genLanguage != Language::C && genLanguage != Language::CPP && genLanguage != Language::JAVA) {
        Logger::E(TAG, "Option 'intf-type hdi' only support language option 'gen-c', 'gen-cpp' or 'gen-java'.");
        ret = false;
    }

    if (doDumpMetadata) {
        Logger::E(TAG, "Option 'intf-type hdi' not support option 'dump-metadata'.");
        ret = false;
    }

    if (doKeywords) {
        Logger::E(TAG, "Option 'intf-type hdi' not support option '-t', '-log-domainid' or '-log-tag'.");
        ret = false;
    }

    if (doSaveMetadata) {
        Logger::E(TAG, "Option 'intf-type hdi' not support option '-s'.");
        ret = false;
    }

    if (!ret) {
        printf("Use \"-h, --help\" to show usage.\n");
    }
    return ret;
}

bool Options::DoSupportSmType()
{
    bool ret = true;

    if (genLanguage != Language::CPP && genLanguage != Language::JAVA) {
        Logger::E(TAG, "Option 'intf-type sm' only support language option 'gen-cpp' or 'gen-java'.");
        ret = false;
    }

    if (doDumpMetadata) {
        Logger::E(TAG, "Option 'intf-type sm' not support option 'dump-metadata'.");
        ret = false;
    }

    if (doKeywords) {
        Logger::E(TAG, "Option 'intf-type sm' not support option '-t', '-log-domainid' or '-log-tag'.");
        ret = false;
    }

    if (doSaveMetadata) {
        Logger::E(TAG, "Option 'intf-type sm' not support option '-s'.");
        ret = false;
    }

    if (!ret) {
        printf("Use \"-h, --help\" to show usage.\n");
    }
    return ret;
}

void Options::SetSmDefaultOption()
{
    systemLevel = SystemLevel::INIT;
    genMode = GenMode::INIT;
    return;
}

bool Options::CheckSmOptions()
{
    SetSmDefaultOption();
    if (!DoSupportSmType()) {
        return false;
    }

    if (doCompile) {
        if (!DoGetHashKey() && !doDumpAST && !doGenerateCode && !doOutDir) {
            Logger::E(TAG, "nothing to do.");
            return false;
        }

        if (!doGenerateCode && doOutDir) {
            Logger::E(TAG, "no target language.");
            return false;
        }

        if (doGenerateCode && !doOutDir) {
            Logger::E(TAG, "no out directory.");
            return false;
        }
    } else {
        if (DoGetHashKey() || doDumpAST || doGenerateCode || doOutDir) {
            Logger::E(TAG, "no idl files.");
            return false;
        }
    }

    return true;
}

bool Options::SetHiTrace(const std::string &tag)
{
    doKeywords = true;
    hitraceTag = tag;
    doHitrace = true;
    return true;
}

bool Options::SetLogDomainId(const std::string &id)
{
    doKeywords = true;
    domainId = id;
    return true;
}

bool Options::SetLogTag(const std::string &tag)
{
    doKeywords = true;
    logTag = tag;
    return true;
}

bool Options::SetInterfaceType(const std::string &type)
{
    static std::map<std::string, InterfaceType> Type = {
        {"hdi", InterfaceType::HDI},
        {"sa", InterfaceType::SA},
        {"sm", InterfaceType::SM},
        {"sam", InterfaceType::SAM},
        {"sam_sm", InterfaceType::SAM_SM},
        {"sam_uds", InterfaceType::SAM_UDS},
        {"sm_uds", InterfaceType::SM_UDS},
    };

    auto codeGenIter = Type.find(type);
    if (codeGenIter == Type.end()) {
        Logger::E(TAG, "invalid interface type set: '%s', please input hdi/sa.", type.c_str());
        return false;
    }
    interfaceType = codeGenIter->second;
    return true;
}

bool Options::DoLogOn() const
{
    if (!domainId.empty() && !logTag.empty()) {
        return true;
    }
    return false;
}

bool Options::DoLegalLog() const
{
    if (genLanguage == Language::CPP) {
        if (!domainId.empty() && !logTag.empty()) {
            return true;
        } else if (domainId.empty() && logTag.empty()) {
            return true;
        } else {
            return false;
        }
    }
    return true;
}

bool Options::HasErrors() const
{
    return !illegalOptions.empty();
}

void Options::ShowErrors() const
{
    std::vector<std::string> illegalOptionsVec = StringHelper::Split(illegalOptions, " ");
    for (size_t i = 0; i < illegalOptionsVec.size(); i++) {
        Logger::E(TAG, "The Option \"%s\" is illegal.", illegalOptionsVec[i].c_str());
    }
    printf("Use \"-h, --help\" to show usage.\n");
    return;
}

bool Options::HasWarning() const
{
    if (interfaceType == InterfaceType::SA) {
        if (genLanguage == Language::RUST || genLanguage == Language::TS) {
            if (DoSearchKeywords()) {
                return true;
            }
        }
    }
    return false;
}

void Options::ShowWarning() const
{
    printf("Warning : Executed successfully, but contains invalid commands !\n");
}

void Options::ShowVersion() const
{
    printf("idl %d.%d\n"
          "Copyright (c) Huawei Technologies Co., Ltd. 2024. All rights reserved.\n\n",
           VERSION_MAJOR, VERSION_MINOR);
}

void Options::ShowUsage() const
{
    printf("Compile a .idl file and generate C/C++/Ts/Rust and Java codes.\n"
           "Usage: idl [options] file\n"
           "Options:\n"
           "  -h, --help                      Display command line options\n"
           "  -v, --version                   Display toolchain version information\n"
           "      --system <value>            Set system level 'mini','lite' or 'full', the default value is 'full', "
           "only support 'intf-type hdi'\n"
           "  -m, --mode <value>              Set generate code mode 'low', 'passthrough', 'ipc' or 'kernel',"
           " the default value is 'ipc', only support 'intf-type hdi'\n"
           "  -p, --package <package name>    Set package of idl files, only support 'intf-type hdi'\n"
           "      --dump-ast                  Display the AST of the compiled file\n"
           "      --dump-metadata             Display the metadata generated from the compiled file, "
           "only support 'intf-type sa'\n"
           "      --hash                      Generate hash info of idl files, only support 'intf-type hdi'\n"
           "  -r <rootPackage>:<rootPath>     Set root path of root package, only support 'intf-type hdi'\n"
           "  -c                              Compile the .idl file\n"
           "  -D <directory>                  Directory of the idl file, only support 'intf-type hdi'\n"
           "  -d <directory>                  Place generated codes into <directory>\n"
           "  -o <file>                       Place the output into <file>, only support 'intf-type hdi'\n"
           "  -s <file>                       Place the metadata into <file>, only support 'intf-type sa'\n"
           "      --gen-c                     Generate C codes, only support 'intf-type hdi'\n"
           "      --gen-cpp                   Generate C++ codes\n"
           "      --gen-java                  Generate Java codes, only support 'intf-type hdi'\n"
           "      --gen-rust                  Generate Rust codes, only support 'intf-type sa'\n"
           "      --gen-ts                    Generate Ts codes, only support 'intf-type sa'\n"
           "      --log-domainid <domainid>   Place the service domain in <domainid>, Enable log(Pair with -log-tag), "
           "only support 'intf-type sa'\n"
           "      --client-enable             Generate client codes, only support 'intf-type sa'\n"
           "      --log-tag <tag>             Place the subsystem name in <tag>, Enable log(Pair with -log-domainid), "
           "only support 'intf-type sa'\n"
           "  -t <hitrace tag>                Place the constant name from hitrace_meter.h file in <hitrace tag>, "
           "only support 'intf-type sa'\n"
           "      --intf-type <tag>           Set type of generated codes 'sa' or 'hdi', default type is 'sa'\n");
}

/*
 * -r option: -r ohos.hdi:./drivers/interface
 * package:ohos.hdi.foo.v1_0
 * rootPackage:ohos.hdi
 */
std::string Options::GetRootPackage(const std::string &package) const
{
    const auto &packagePaths = GetPackagePathMap();
    for (const auto &packageRoot : packagePaths) {
        if (StringHelper::StartWith(package, packageRoot.first)) {
            return packageRoot.first;
        }
    }

    return "";
}

/*
 * -r option: -r ohos.hdi:./drivers/interface
 * package:ohos.hdi.foo.v1_0
 * rootPath:./drivers/interface
 */
std::string Options::GetRootPath(const std::string &package) const
{
    const auto &packagePaths = GetPackagePathMap();
    for (const auto &packageRoot : packagePaths) {
        if (StringHelper::StartWith(package, packageRoot.first)) {
            return packageRoot.second;
        }
    }

    return "";
}

/*
 * -r option: -r ohos.hdi:./drivers/interface
 * package:ohos.hdi.foo.v1_0
 * subPackage:foo.v1_0
 */
std::string Options::GetSubPackage(const std::string &package) const
{
    if (interfaceType == InterfaceType::SM ||
        interfaceType == InterfaceType::SM_UDS) {
        return package;
    }
    std::string rootPackage = GetRootPackage(package);
    if (rootPackage.empty()) {
        return package;
    }

    return package.substr(rootPackage.size() + 1);
}

/*
 * -r option: -r ohos.hdi:./drivers/interface
 * package:ohos.hdi.foo.v1_0
 * packagePath:./drivers/interface/foo/v1_0
 */
std::string Options::GetPackagePath(const std::string &package) const
{
    std::string rootPackage = "";
    std::string rootPath = "";
    const auto &packagePaths = GetPackagePathMap();
    for (const auto &packageRoot : packagePaths) {
        if (StringHelper::StartWith(package, packageRoot.first)) {
            rootPackage = packageRoot.first;
            rootPath = packageRoot.second;
        }
    }

    if (rootPackage.empty()) {
        // The current path is the root path
        std::string curPath = File::AdapterPath(StringHelper::Replace(package, '.', SEPARATOR));
        return File::AdapterRealPath(curPath);
    }

    if (StringHelper::EndWith(rootPath, SEPARATOR)) {
        rootPath.pop_back();
    }

    std::string subPath = StringHelper::Replace(package.substr(rootPackage.size() + 1), '.', SEPARATOR);
    return File::AdapterPath(rootPath + "/" + subPath);
}

/*
 * -r option: -r ohos.hdi:./drivers/interface
 * import: ohos.hdi.foo.v1_0.MyTypes
 * packagePath:./drivers/interface/foo/v1_0/MyTypes.idl
 */
std::string Options::GetImportFilePath(const std::string &import, const std::string &curPath) const
{
    if (interfaceType == InterfaceType::SA && genLanguage == Language::CPP) {
        size_t index = curPath.rfind(SEPARATOR);
        if (index == std::string::npos || index == curPath.size() - 1) {
            Logger::E(TAG, "currrent path:%s, is errno", curPath.c_str());
            return "";
        }
        std::string retPath = curPath.substr(0, index + 1);
        retPath += import + ".idl";
        return File::AdapterRealPath(retPath);
    }

    size_t index = import.rfind('.');
    if (index == std::string::npos) {
        return import;
    }

    std::string dir = GetPackagePath(StringHelper::SubStr(import, 0, index));
    std::string className = import.substr(index + 1);
    return StringHelper::Format("%s%c%s.idl", dir.c_str(), SEPARATOR, className.c_str());
}
} // namespace Idl
} // namespace OHOS