/****************************************************************************
**
** Copyright (C) 2015 The Qt Company Ltd.
** Contact: http://www.qt.io/licensing
**
** This file is part of Qbs.
**
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms and
** conditions see http://www.qt.io/terms-conditions. For further information
** use the contact form at http://www.qt.io/contact-us.
**
** GNU Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 2.1 or version 3 as published by the Free
** Software Foundation and appearing in the file LICENSE.LGPLv21 and
** LICENSE.LGPLv3 included in the packaging of this file.  Please review the
** following information to ensure the GNU Lesser General Public License
** requirements will be met: https://www.gnu.org/licenses/lgpl.html and
** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
**
** In addition, as a special exception, The Qt Company gives you certain additional
** rights.  These rights are described in The Qt Company LGPL Exception
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
****************************************************************************/

import qbs.File
import qbs.FileInfo
import qbs.Host
import qbs.ModUtils
import qbs.PathTools
import qbs.Probes
import qbs.Process
import qbs.TextFile
import qbs.Utilities
import qbs.UnixUtils
import qbs.WindowsUtils
import 'cpp.js' as Cpp
import 'gcc.js' as Gcc

CppModule {
    condition: qbs.toolchain && qbs.toolchain.includes("gcc")
    priority: -100

    Depends { name: "codesign" }

    Probes.GccBinaryProbe {
        id: compilerPathProbe
        condition: !toolchainInstallPath && !_skipAllChecks
        _compilerName: compilerName
        _toolchainPrefix: toolchainPrefix
    }

    // Find the version as early as possible in case other things depend on it,
    // for example the question of whether certain flags are supported and which need to be used
    // in the GccProbe itself.
    Probes.GccVersionProbe {
        id: gccVersionProbe
        compilerFilePath: compilerPath
        environment: probeEnv
    }

    Probes.GccProbe {
        id: gccProbe
        condition: !_skipAllChecks
        compilerFilePathByLanguage: compilerPathByLanguage
        enableDefinesByLanguage: enableCompilerDefinesByLanguage
        environment: probeEnv
        flags: targetDriverFlags.concat(sysrootFlags)
        _sysroot: sysroot
    }
    property var probeEnv

    Probes.BinaryProbe {
        id: binutilsProbe
        condition: !File.exists(archiverPath) && !_skipAllChecks
        names: Gcc.toolNames([archiverName, assemblerName, linkerName, nmName,
                              objcopyName, stripName], toolchainPrefix)
    }

    targetLinkerFlags: Gcc.targetFlags("linker", false,
                                       target, targetArch, machineType, qbs.targetOS)
    targetAssemblerFlags: Gcc.targetFlags("assembler", assemblerHasTargetOption,
                                          target, targetArch, machineType, qbs.targetOS)
    targetDriverFlags: Gcc.targetFlags("compiler", compilerHasTargetOption,
                                       target, targetArch, machineType, qbs.targetOS)

    Probe {
        id: nmProbe
        condition: !_skipAllChecks
        property string theNmPath: nmPath
        property bool hasDynamicOption
        configure: {
            var proc = new Process();
            try {
                hasDynamicOption = proc.exec(theNmPath, ["-D", theNmPath], false) == 0;
                console.debug("nm has -D: " + hasDynamicOption);
            } finally {
                proc.close();
            }
        }
    }

    qbs.architecture: gccProbe.found ? gccProbe.architecture : original
    endianness: gccProbe.endianness

    compilerDefinesByLanguage: gccProbe.compilerDefinesByLanguage

    compilerVersionMajor: gccVersionProbe.versionMajor
    compilerVersionMinor: gccVersionProbe.versionMinor
    compilerVersionPatch: gccVersionProbe.versionPatch

    compilerIncludePaths: gccProbe.includePaths
    compilerFrameworkPaths: gccProbe.frameworkPaths
    compilerLibraryPaths: gccProbe.libraryPaths

    staticLibraryPrefix: "lib"
    staticLibrarySuffix: ".a"

    precompiledHeaderSuffix: ".gch"

    property bool compilerHasTargetOption: qbs.toolchain.includes("clang")
                                           && Utilities.versionCompare(compilerVersion, "3.1") >= 0
    property bool assemblerHasTargetOption: qbs.toolchain.includes("xcode")
                                            && Utilities.versionCompare(compilerVersion, "7") >= 0
    property string target: targetArch
                            ? [targetArch, targetVendor, targetSystem, targetAbi].join("-")
                            : undefined
    property string targetArch: Utilities.canonicalTargetArchitecture(
                                    qbs.architecture, endianness,
                                    targetVendor, targetSystem, targetAbi)
    property string targetVendor: "unknown"
    property string targetSystem: "unknown"
    property string targetAbi: "unknown"

    property string toolchainPrefix: compilerPathProbe.found
                                     ? compilerPathProbe.tcPrefix
                                     : undefined
    toolchainInstallPath: compilerPathProbe.found ? compilerPathProbe.path : undefined
    property string binutilsPath: binutilsProbe.found ? binutilsProbe.path : toolchainInstallPath

    assemblerName: 'as' + compilerExtension
    compilerName: cxxCompilerName
    linkerName: 'ld' + compilerExtension
    property string archiverName: 'ar' + compilerExtension
    property string nmName: 'nm' + compilerExtension
    property string objcopyName: "objcopy" + compilerExtension
    property string stripName: "strip" + compilerExtension
    property string dsymutilName: "dsymutil" + compilerExtension
    property string lipoName
    property string sysroot: qbs.sysroot
    property string syslibroot: sysroot
    property stringList sysrootFlags: sysroot ? ["--sysroot=" + sysroot] : []

    property string exportedSymbolsCheckMode: "ignore-undefined"
    PropertyOptions {
        name: "exportedSymbolsCheckMode"
        allowedValues: ["strict", "ignore-undefined"]
        description: "Controls when we consider an updated dynamic library as changed with "
            + "regards to other binaries depending on it. The default is \"ignore-undefined\", "
            + "which means we do not care about undefined symbols being added or removed. "
            + "If you do care about that, e.g. because you link dependent products with an option "
            + "such as \"--no-undefined\", then you should set this property to \"strict\"."
    }

    property string linkerVariant
    PropertyOptions {
        name: "linkerVariant"
        allowedValues: ["bfd", "gold", "lld"]
        description: "Allows to specify the linker variant. Maps to gcc's and clang's -fuse-ld "
                     + "option."
    }
    Properties {
        condition: linkerVariant
        driverLinkerFlags: "-fuse-ld=" + linkerVariant
    }

    property string toolchainPathPrefix: Gcc.pathPrefix(toolchainInstallPath, toolchainPrefix)
    property string binutilsPathPrefix: Gcc.pathPrefix(binutilsPath, toolchainPrefix)

    property string cCompilerName: (qbs.toolchain.includes("clang") ? "clang" : "gcc")
                                   + compilerExtension
    property string cxxCompilerName: (qbs.toolchain.includes("clang") ? "clang++" : "g++")
                                     + compilerExtension

    compilerPathByLanguage: ({
        "c": toolchainPathPrefix + cCompilerName,
        "cpp": toolchainPathPrefix + cxxCompilerName,
        "objc": toolchainPathPrefix + cCompilerName,
        "objcpp": toolchainPathPrefix + cxxCompilerName,
        "asm_cpp": toolchainPathPrefix + cCompilerName
    })

    assemblerPath: binutilsPathPrefix + assemblerName
    compilerPath: toolchainPathPrefix + compilerName
    linkerPath: binutilsPathPrefix + linkerName
    property string archiverPath: binutilsPathPrefix + archiverName
    property string nmPath: binutilsPathPrefix + nmName
    property bool _nmHasDynamicOption: nmProbe.hasDynamicOption
    property string objcopyPath: binutilsPathPrefix + objcopyName
    property string stripPath: binutilsPathPrefix + stripName
    property string dsymutilPath: toolchainPathPrefix + dsymutilName
    property string lipoPath
    property stringList dsymutilFlags

    property bool alwaysUseLipo: false
    defineFlag: "-D"
    includeFlag: "-I"
    systemIncludeFlag: "-isystem"
    preincludeFlag: "-include"
    libraryPathFlag: "-L"
    linkerScriptFlag: "-T"

    readonly property bool shouldCreateSymlinks: {
        return createSymlinks && internalVersion && ["macho", "elf"].includes(cpp.imageFormat);
    }

    readonly property bool shouldSignArtifacts: codesign._canSignArtifacts
                                                && codesign.enableCodeSigning
                                                // codesigning is done during the lipo step
                                                && !product.multiplexed

    property string internalVersion: {
        if (product.version === undefined)
            return undefined;

        var coreVersion = product.version.match("^([0-9]+\.){0,3}[0-9]+");
        if (!coreVersion)
            return undefined;

        var maxVersionParts = 3;
        var versionParts = coreVersion[0].split('.').slice(0, maxVersionParts);

        // pad if necessary
        for (var i = versionParts.length; i < maxVersionParts; ++i)
            versionParts.push("0");

        return versionParts.join('.');
    }

    property string soVersion: {
        if (!internalVersion)
            return "";

        return internalVersion.split('.')[0];
    }

    property var buildEnv: {
        var env = {};
        if (qbs.toolchain.includes("mingw"))
            env.PATH = [toolchainInstallPath]; // For libwinpthread etc
        return env;
    }

    exceptionHandlingModel: {
        if (qbs.toolchain.includes("mingw")) {
            // https://gcc.gnu.org/onlinedocs/cpp/Common-Predefined-Macros.html claims
            // __USING_SJLJ_EXCEPTIONS__ is defined as 1 when using SJLJ exceptions, but there don't
            // seem to be defines for the other models, so use the presence of the DLLs for now.
            var prefix = toolchainInstallPath;
            if (!Host.os().includes("windows"))
                prefix = FileInfo.joinPaths(toolchainInstallPath, "..", "lib", "gcc",
                                            toolchainPrefix,
                                            [compilerVersionMajor, compilerVersionMinor].join("."));
            var models = ["seh", "sjlj", "dw2"];
            for (var i = 0; i < models.length; ++i) {
                if (File.exists(FileInfo.joinPaths(prefix, "libgcc_s_" + models[i] + "-1.dll"))) {
                    return models[i];
                }
            }
        }
        return base;
    }

    validate: {
        if (_skipAllChecks)
            return;
        if (!File.exists(compilerPath)) {
            var pathMessage = FileInfo.isAbsolutePath(compilerPath)
                    ? "at '" + compilerPath + "'"
                    : "'" + compilerPath + "' in PATH";
            throw ModUtils.ModuleError("Could not find selected C++ compiler " + pathMessage + ". "
                                       + "Ensure that the compiler is properly "
                                       + "installed, or set cpp.toolchainInstallPath to a valid "
                                       + "toolchain path, or consider whether you meant to set "
                                       + "cpp.compilerName instead.");
        }

        var isWrongTriple = false;

        if (gccProbe.architecture) {
            if (Utilities.canonicalArchitecture(architecture)
                    !== Utilities.canonicalArchitecture(gccProbe.architecture))
                isWrongTriple = true;
        } else if (architecture) {
            // This is a warning and not an error on the rare chance some new architecture comes
            // about which qbs does not know about the macros of. But it *might* still work.
            console.warn("Unknown architecture '" + architecture + "' " +
                         "may not be supported by this compiler.");
        }

        if (gccProbe.endianness) {
            if (endianness !== gccProbe.endianness)
                isWrongTriple = true;
        } else if (endianness) {
            console.warn("Could not detect endianness ('"
                         + endianness + "' given)");
        }

        if (gccProbe.targetPlatform) {
            // Can't differentiate Darwin OSes at the compiler level alone
            if (gccProbe.targetPlatform === "darwin"
                    ? !qbs.targetOS.includes("darwin")
                    : qbs.targetPlatform !== gccProbe.targetPlatform)
                isWrongTriple = true;
        } else if (qbs.targetPlatform) {
            console.warn("Could not detect target platform ('"
                         + qbs.targetPlatform + "' given)");
        }

        if (isWrongTriple) {
            var realTriple = [
                Utilities.canonicalArchitecture(gccProbe.architecture),
                gccProbe.targetPlatform,
                gccProbe.endianness ? gccProbe.endianness + "-endian" : undefined
            ].join("-");
            var givenTriple = [
                Utilities.canonicalArchitecture(architecture),
                qbs.targetPlatform,
                endianness ? endianness + "-endian" : undefined
            ].join("-");
            var msg = "The selected compiler '" + compilerPath + "' produces code for '" +
                    realTriple + "', but '" + givenTriple + "' was given, which is incompatible.";
            if (validateTargetTriple) {
                msg +=  " If you are absolutely certain that your configuration is correct " +
                        "(check the values of the qbs.architecture, qbs.targetPlatform, " +
                        "and qbs.endianness properties) and that this message has been " +
                        "emitted in error, set the cpp.validateTargetTriple property to " +
                        "false. However, you should consider submitting a bug report in any " +
                        "case.";
                throw ModUtils.ModuleError(msg);
            } else {
                console.warn(msg);
            }
        }

        var validateFlagsFunction = function (value) {
            if (value) {
                for (var i = 0; i < value.length; ++i) {
                    if (["-target", "-triple", "-arch"].includes(value[i]))
                        return false;
                }
            }
            return true;
        }

        var validator = new ModUtils.PropertyValidator("cpp");
        var msg = "'-target', '-triple' and '-arch' cannot appear in flags; set qbs.architecture instead";
        validator.addCustomValidator("assemblerFlags", assemblerFlags, validateFlagsFunction, msg);
        validator.addCustomValidator("cppFlags", cppFlags, validateFlagsFunction, msg);
        validator.addCustomValidator("cFlags", cFlags, validateFlagsFunction, msg);
        validator.addCustomValidator("cxxFlags", cxxFlags, validateFlagsFunction, msg);
        validator.addCustomValidator("objcFlags", objcFlags, validateFlagsFunction, msg);
        validator.addCustomValidator("objcxxFlags", objcxxFlags, validateFlagsFunction, msg);
        validator.addCustomValidator("commonCompilerFlags", commonCompilerFlags, validateFlagsFunction, msg);
        validator.addCustomValidator("platformAssemblerFlags", platformAssemblerFlags, validateFlagsFunction, msg);
        //validator.addCustomValidator("platformCppFlags", platformCppFlags, validateFlagsFunction, msg);
        validator.addCustomValidator("platformCFlags", platformCFlags, validateFlagsFunction, msg);
        validator.addCustomValidator("platformCxxFlags", platformCxxFlags, validateFlagsFunction, msg);
        validator.addCustomValidator("platformObjcFlags", platformObjcFlags, validateFlagsFunction, msg);
        validator.addCustomValidator("platformObjcxxFlags", platformObjcxxFlags, validateFlagsFunction, msg);
        validator.addCustomValidator("platformCommonCompilerFlags", platformCommonCompilerFlags, validateFlagsFunction, msg);

        validator.setRequiredProperty("compilerVersion", compilerVersion);
        validator.setRequiredProperty("compilerVersionMajor", compilerVersionMajor);
        validator.setRequiredProperty("compilerVersionMinor", compilerVersionMinor);
        validator.setRequiredProperty("compilerVersionPatch", compilerVersionPatch);
        validator.addVersionValidator("compilerVersion", compilerVersion, 3, 3);
        validator.addRangeValidator("compilerVersionMajor", compilerVersionMajor, 1);
        validator.addRangeValidator("compilerVersionMinor", compilerVersionMinor, 0);
        validator.addRangeValidator("compilerVersionPatch", compilerVersionPatch, 0);

        validator.setRequiredProperty("compilerIncludePaths", compilerIncludePaths);
        validator.setRequiredProperty("compilerFrameworkPaths", compilerFrameworkPaths);
        validator.setRequiredProperty("compilerLibraryPaths", compilerLibraryPaths);

        validator.validate();
    }

    // Product should be linked if it's not multiplexed or aggregated at all,
    // or if it is multiplexed, if it's not the aggregate product
    readonly property bool shouldLink: !(product.multiplexed || product.aggregate)
                                       || product.multiplexConfigurationId

    Rule {
        name: "dynamicLibraryLinker"
        condition: product.cpp.shouldLink
        multiplex: true
        inputs: {
            var tags = ["obj", "res", "linkerscript", "versionscript"];
            if (product.bundle && product.bundle.embedInfoPlist
                    && product.qbs.targetOS.includes("darwin")) {
                tags.push("aggregate_infoplist");
            }
            return tags;
        }
        inputsFromDependencies: ["dynamiclibrary_symbols", "staticlibrary", "dynamiclibrary_import"]

        outputFileTags: {
            var tags = ["bundle.input", "dynamiclibrary", "dynamiclibrary_symlink",
                        "dynamiclibrary_symbols", "debuginfo_dll", "debuginfo_bundle",
                        "dynamiclibrary_import", "debuginfo_plist"];
            if (shouldSignArtifacts)
                tags.push("codesign.signed_artifact");
            return tags;
        }
        outputArtifacts: {
            var artifacts = [{
                filePath: FileInfo.joinPaths(product.destinationDirectory,
                                             PathTools.dynamicLibraryFilePath(product)),
                fileTags: ["bundle.input", "dynamiclibrary"]
                        .concat(product.cpp.shouldSignArtifacts
                                ? ["codesign.signed_artifact"] : []),
                bundle: {
                    _bundleFilePath: FileInfo.joinPaths(product.destinationDirectory,
                                                        PathTools.bundleExecutableFilePath(product))
                }
            }];
            if (product.cpp.imageFormat === "pe") {
                artifacts.push({
                    fileTags: ["dynamiclibrary_import"],
                    filePath: FileInfo.joinPaths(product.destinationDirectory,
                                                 PathTools.importLibraryFilePath(product)),
                    alwaysUpdated: false
                });
            } else {
                // List of libfoo's public symbols for smart re-linking.
                artifacts.push({
                    filePath: product.destinationDirectory + "/.sosymbols/"
                              + PathTools.dynamicLibraryFilePath(product),
                    fileTags: ["dynamiclibrary_symbols"],
                    alwaysUpdated: false,
                });
            }

            if (product.cpp.shouldCreateSymlinks && (!product.bundle || !product.bundle.isBundle)) {
                var maxVersionParts = product.cpp.internalVersion ? 3 : 1;
                for (var i = 0; i < maxVersionParts; ++i) {
                    var symlink = {
                        filePath: FileInfo.joinPaths(product.destinationDirectory,
                                                     PathTools.dynamicLibraryFilePath(
                                                         product, undefined, undefined, i)),
                        fileTags: ["dynamiclibrary_symlink"]
                    };
                    if (i > 0 && artifacts[i-1].filePath == symlink.filePath)
                        break; // Version number has less than three components.
                    artifacts.push(symlink);
                }
            }
            if (!product.aggregate)
                artifacts = artifacts.concat(Gcc.debugInfoArtifacts(product, undefined, "dll"));
            return artifacts;
        }

        prepare: Gcc.prepareLinker.apply(Gcc, arguments)
    }

    Rule {
        name: "staticLibraryLinker"
        condition: product.cpp.shouldLink
        multiplex: true
        inputs: ["obj", "res", "linkerscript"]
        inputsFromDependencies: ["dynamiclibrary_symbols", "dynamiclibrary_import", "staticlibrary"]

        outputFileTags: ["bundle.input", "staticlibrary", "c_staticlibrary", "cpp_staticlibrary"]
        outputArtifacts: {
            var tags = ["bundle.input", "staticlibrary"];
            var objs = inputs["obj"];
            var objCount = objs ? objs.length : 0;
            for (var i = 0; i < objCount; ++i) {
                var ft = objs[i].fileTags;
                if (ft.includes("c_obj"))
                    tags.push("c_staticlibrary");
                if (ft.includes("cpp_obj"))
                    tags.push("cpp_staticlibrary");
            }
            return [{
                filePath: FileInfo.joinPaths(product.destinationDirectory,
                                             PathTools.staticLibraryFilePath(product)),
                fileTags: tags,
                bundle: {
                    _bundleFilePath: FileInfo.joinPaths(product.destinationDirectory,
                                                        PathTools.bundleExecutableFilePath(product))
                }
            }];
        }

        prepare: {
            var args = ['rcs', output.filePath];
            for (var i in inputs.obj)
                args.push(inputs.obj[i].filePath);
            for (var i in inputs.res)
                args.push(inputs.res[i].filePath);
            var cmd = new Command(product.cpp.archiverPath, args);
            cmd.description = 'creating ' + output.fileName;
            cmd.highlight = 'linker'
            cmd.jobPool = "linker";
            cmd.responseFileUsagePrefix = '@';
            return cmd;
        }
    }

    Rule {
        name: "loadableModuleLinker"
        condition: product.cpp.shouldLink
        multiplex: true
        inputs: {
            var tags = ["obj", "res", "linkerscript"];
            if (product.bundle && product.bundle.embedInfoPlist
                    && product.qbs.targetOS.includes("darwin")) {
                tags.push("aggregate_infoplist");
            }
            return tags;
        }
        inputsFromDependencies: ["dynamiclibrary_symbols", "dynamiclibrary_import", "staticlibrary"]

        outputFileTags: {
            var tags = ["bundle.input", "loadablemodule", "debuginfo_loadablemodule",
                        "debuginfo_bundle", "debuginfo_plist"];
            if (shouldSignArtifacts)
                tags.push("codesign.signed_artifact");
            return tags;
        }
        outputArtifacts: {
            var app = {
                filePath: FileInfo.joinPaths(product.destinationDirectory,
                                             PathTools.loadableModuleFilePath(product)),
                fileTags: ["bundle.input", "loadablemodule"]
                        .concat(product.cpp.shouldSignArtifacts
                                ? ["codesign.signed_artifact"] : []),
                bundle: {
                    _bundleFilePath: FileInfo.joinPaths(product.destinationDirectory,
                                                        PathTools.bundleExecutableFilePath(product))
                }
            }
            var artifacts = [app];
            if (!product.aggregate)
                artifacts = artifacts.concat(Gcc.debugInfoArtifacts(product, undefined,
                                                                    "loadablemodule"));
            return artifacts;
        }

        prepare: Gcc.prepareLinker.apply(Gcc, arguments)
    }

    Rule {
        name: "applicationLinker"
        condition: product.cpp.shouldLink
        multiplex: true
        inputs: {
            var tags = ["obj", "res", "linkerscript"];
            if (product.bundle && product.bundle.embedInfoPlist
                    && product.qbs.targetOS.includes("darwin")) {
                tags.push("aggregate_infoplist");
            }
            return tags;
        }
        inputsFromDependencies: ["dynamiclibrary_symbols", "dynamiclibrary_import", "staticlibrary"]

        outputFileTags: {
            var tags = ["bundle.input", "application", "debuginfo_app", "debuginfo_bundle",
                        "debuginfo_plist"];
            if (shouldSignArtifacts)
                tags.push("codesign.signed_artifact");
            if (generateLinkerMapFile)
                tags.push("mem_map");
            return tags;
        }
        outputArtifacts: {
            var app = {
                filePath: FileInfo.joinPaths(product.destinationDirectory,
                                             PathTools.applicationFilePath(product)),
                fileTags: ["bundle.input", "application"].concat(
                    product.cpp.shouldSignArtifacts ? ["codesign.signed_artifact"] : []),
                bundle: {
                    _bundleFilePath: FileInfo.joinPaths(product.destinationDirectory,
                                                        PathTools.bundleExecutableFilePath(product))
                }
            }
            var artifacts = [app];
            if (!product.aggregate)
                artifacts = artifacts.concat(Gcc.debugInfoArtifacts(product, undefined, "app"));
            if (product.cpp.generateLinkerMapFile) {
                artifacts.push({
                    filePath: FileInfo.joinPaths(product.destinationDirectory,
                                                 product.targetName + product.cpp.linkerMapSuffix),
                    fileTags: ["mem_map"]
                });
            }
            return artifacts;
        }

        prepare: Gcc.prepareLinker.apply(Gcc, arguments)
    }

    Rule {
        name: "compiler"
        inputs: ["cpp", "c", "objcpp", "objc", "asm_cpp"]
        auxiliaryInputs: ["hpp"]
        explicitlyDependsOn: ["c_pch", "cpp_pch", "objc_pch", "objcpp_pch"]
        outputFileTags: Cpp.compilerOutputTags(false).concat(["c_obj", "cpp_obj"])
        outputArtifacts: Cpp.compilerOutputArtifacts(input, inputs)
        prepare: Gcc.prepareCompiler.apply(Gcc, arguments)
    }

    Rule {
        name: "assembler"
        inputs: ["asm"]
        outputFileTags: Cpp.assemblerOutputTags(false)
        outputArtifacts: Cpp.assemblerOutputArtifacts(input)
        prepare: Gcc.prepareAssembler.apply(Gcc, arguments)
    }

    Rule {
        condition: useCPrecompiledHeader
        inputs: ["c_pch_src"]
        auxiliaryInputs: ["hpp"]
        outputFileTags: Cpp.precompiledHeaderOutputTags("c", false)
        outputArtifacts: Cpp.precompiledHeaderOutputArtifacts(input, product, "c", false)
        prepare: Gcc.prepareCompiler.apply(Gcc, arguments)
    }

    Rule {
        condition: useCxxPrecompiledHeader
        inputs: ["cpp_pch_src"]
        auxiliaryInputs: ["hpp"]
        outputFileTags: Cpp.precompiledHeaderOutputTags("cpp", false)
        outputArtifacts: Cpp.precompiledHeaderOutputArtifacts(input, product, "cpp", false)
        prepare: Gcc.prepareCompiler.apply(Gcc, arguments)
    }

    Rule {
        condition: useObjcPrecompiledHeader
        inputs: ["objc_pch_src"]
        auxiliaryInputs: ["hpp"]
        outputFileTags: Cpp.precompiledHeaderOutputTags("objc", false)
        outputArtifacts: Cpp.precompiledHeaderOutputArtifacts(input, product, "objc", false)
        prepare: Gcc.prepareCompiler.apply(Gcc, arguments)
    }

    Rule {
        condition: useObjcxxPrecompiledHeader
        inputs: ["objcpp_pch_src"]
        auxiliaryInputs: ["hpp"]
        outputFileTags: Cpp.precompiledHeaderOutputTags("objcpp", false)
        outputArtifacts: Cpp.precompiledHeaderOutputArtifacts(input, product, "objcpp", false)
        prepare: Gcc.prepareCompiler.apply(Gcc, arguments)
    }

    FileTagger {
        patterns: "*.s"
        fileTags: ["asm"]
    }

    FileTagger {
        patterns: "*.S"
        fileTags: ["asm_cpp"]
    }

    FileTagger {
        patterns: "*.sx"
        fileTags: ["asm_cpp"]
    }

    Scanner {
        inputs: ["linkerscript"]
        recursive: true
        scan: {
            console.debug("scanning linkerscript " + filePath + " for dependencies");
            var retval = [];
            var linkerScript = new TextFile(filePath, TextFile.ReadOnly);
            var regexp = /[\s]*INCLUDE[\s]+(\S+).*/ // "INCLUDE filename"
            var match;
            while (!linkerScript.atEof()) {
                match = regexp.exec(linkerScript.readLine());
                if (match) {
                    var dependencyFileName = match[1];
                    retval.push(dependencyFileName);
                    console.debug("linkerscript " + filePath + " depends on " + dependencyFileName);
                }
            }
            linkerScript.close();
            return retval;
        }
        searchPaths: {
            var retval = [];
            for (var i = 0; i < (product.cpp.libraryPaths || []).length; i++)
                retval.push(product.cpp.libraryPaths[i]);
            var regexp = /[\s]*SEARCH_DIR\((\S+)\).*/ // "SEARCH_DIR(path)"
            var match;
            var linkerScript = new TextFile(input.filePath, TextFile.ReadOnly);
            while (!linkerScript.atEof()) {
                match = regexp.exec(linkerScript.readLine());
                if(match) {
                    var additionalPath = match[1];
                    // path can be quoted to use non-latin letters, remove quotes if present
                    if (additionalPath.startsWith("\"") && additionalPath.endsWith("\""))
                        additionalPath = additionalPath.slice(1, additionalPath.length - 1);
                    retval.push(additionalPath);
                }
            }
            linkerScript.close();
            return retval;
        }
    }
}
