30b4ce1c创建于 2024年11月24日历史提交
import("core.base.option")
import("core.base.semver")
import("core.base.json")
import("core.base.hashset")
import("lib.detect.find_tool")
import("lib.detect.find_file")
import("net.http")
import("devel.git")
import("utils.archive")

local options = {
    {nil, "repo", "v", nil, "Set repository name.",
                            "e.g. ",
                            "  - github:xmake-io/xmake",
                            "  - gitlab:xmake-io/xmake"}
}

-- function to get Gitlab data
function get_gitlab_data(reponame)
    local glab = assert(find_tool("glab"), "glab not found!")
    local host = os.iorunv(glab.program, {"config", "get", "host"}):trim()
    local graphql_query = 'query={ project(fullPath: "' .. reponame .. '") { description webUrl sshUrlToRepo name } }'
    local repoinfo = os.iorunv(glab.program, {"api", "graphql", "-f", graphql_query})

    local data = {}
    if repoinfo then
        repoinfo = json.decode(repoinfo)
        if repoinfo.data and repoinfo.data.project then
            -- extract required data and restructure it
            local project_data = repoinfo.data.project
            data = {
                description = project_data.description,
                homepageUrl = project_data.webUrl,
                licenseInfo = "MIT", -- NOTE: Find a way to get the project license in gitlab
                url = project_data.webUrl,
                sshUrl = project_data.sshUrlToRepo,
                name = project_data.name,
            }
            repoinfo.data.project = data
        end
    end
    return {host = host, data = data}
end

local function get_github_data(reponame)
    local gh = assert(find_tool("gh"), "gh not found!")
    local host = "github.com"
    local data = os.iorunv(gh.program, {
        "repo",
        "view",
        reponame,
        "--json",
        "description,homepageUrl,licenseInfo,url,sshUrl,name,latestRelease",
    })
    if data then
        data = json.decode(data)
    end
    return {data = data, host = host}
end

local function get_license_spdx_id(key)
    local licenses = {
        ["apache-2.0"] = "Apache-2.0",
        ["lgpl-2.0"] = "LGPL-2.0",
        ["lgpl-2.1"] = "LGPL-2.1",
        ["agpl-3.0"] = "AGPL-3.0",
        ["bsd-2-clause"] = "BSD-2-Clause",
        ["bsd-3-clause"] = "BSD-3-Clause",
        ["bsl-1.0"] = "BSL-1.0",
        ["cc0-1.0"] = "CC0-1.0",
        ["epl-2.0"] = "EPL-2.0",
        ["gpl-2.0"] = "GPL-2.0",
        ["gpl-3.0"] = "GPL-3.0",
        ["mpl-2.0"] = "MPL-2.0",
        zlib = "zlib",
        mit = "MIT",
    }
    local license = licenses[key]
    if license then
        return license
    end

    local url = string.format("https://api.github.com/licenses/%s", key)
    local tmpfile = os.tmpfile({ramdisk = false})
    local ok = try { function () http.download(url, tmpfile); return true end }
    if not ok then
        os.tryrm(tmpfile)
        return nil
    end
    local license_detail = json.loadfile(tmpfile)
    license = license_detail["spdx_id"]
    os.tryrm(tmpfile)
    return license
end

function generate_package(reponame, get_data)
    local repo_data = get_data(reponame)
    local data = repo_data.data
    local host = repo_data.host

    -- generate package header
    local packagename = assert(data.name, "package name not found!"):lower()
    local packagefile = path.join("packages", string.sub(packagename, 1, 1), packagename, "xmake.lua")
    local file = io.open(packagefile, "w")

    -- define package and homepage
    file:print('package("%s")', packagename)
    local homepage = data.homepageUrl and data.homepageUrl ~= "" and data.homepageUrl or data.url
    if homepage then
        file:print('    set_homepage("%s")', homepage)
    end

    local description = data.description or ("The " .. packagename .. " package")
    file:print('    set_description("%s")', description)

    -- define license if available
    if type(data.licenseInfo) == "table" and data.licenseInfo.key then
        local license = get_license_spdx_id(data.licenseInfo.key)
        if license then
            file:print('    set_license("%s")', license)
        end
    end
    file:print("")

    -- define package URLs and versions
    local repodir
    local has_xmake, has_cmake, has_meson, has_bazel, has_autoconf, need_autogen
    local latest_release = data.latestRelease

    if type(latest_release) == "table" then
        local url = string.format("https://%s/%s/archive/refs/tags/%s.tar.gz", host, reponame, latest_release.tagName)
        local giturl = string.format("https://%s/%s.git", host, reponame)
        local tmpfile = os.tmpfile({ramdisk = false}) .. ".tar.gz"
        repodir = tmpfile .. ".dir"

        file:write('    add_urls("https://' .. host .. '/' .. reponame .. '/archive/refs/tags/$(version).tar.gz",\n')
        file:print('             "%s")\n', giturl)

        print("downloading %s", url)
        http.download(url, tmpfile)

        file:print('    add_versions("%s", "%s")', latest_release.tagName, hash.sha256(tmpfile))
        archive.extract(tmpfile, repodir)
        os.rm(tmpfile)
    else
        local giturl = string.format("https://%s/%s.git", host, reponame)
        repodir = os.tmpfile({ ramdisk = false })

        file:print('    add_urls("%s")', giturl)

        print("downloading %s", giturl)
        git.clone(giturl, { outputdir = repodir, depth = 1 })

        local commit = git.lastcommit({ repodir = repodir })
        local version = try {
            function()
                return os.iorunv("git", {
                    "log",
                    "-1",
                    "--date=format:%Y.%m.%d",
                    "--format=%ad",
                }, { curdir = repodir })
            end
        }
        if version then
            file:print('    add_versions("%s", "%s")', version:trim(), commit)
        end
    end

    local build_systems = {
        ["xmake.lua"] = {
            deps = {},
            priority = 1,
            install = function(configs, package)
                return ([=[
        io.writefile("xmake.lua", [[
            add_rules("mode.release", "mode.debug")
            target("%s")
                set_kind("$(kind)")
                add_files("src/*.c")
                add_headerfiles("src/(*.h)")
        ]])
        import("package.tools.xmake").install(package)]=]):format(packagename)
            end,
        },
        ["CMakeLists.txt"] = {
            deps = {"cmake"},
            priority = 2,
            install = function(configs, package)
                return [[
        table.insert(configs, "-DCMAKE_BUILD_TYPE=" .. (package:is_debug() and "Debug" or "Release"))
        table.insert(configs, "-DBUILD_SHARED_LIBS=" .. (package:config("shared") and "ON" or "OFF"))
        import("package.tools.cmake").install(package, configs)]]
            end,
        },
        ["configure,configure.ac,autogen.sh"] = {
            deps = {"autoconf", "automake", "libtool"},
            priority = 3,
            install = function(configs, package)
                return [[
        table.insert(configs, "--enable-shared=" .. (package:config("shared") and "yes" or "no"))
        if package:is_debug() then
            table.insert(configs, "--enable-debug")
        end
        import("package.tools.autoconf").install(package, configs)]]
            end,
        },
        ["meson.build"] = {
            deps = {"meson", "ninja"},
            priority = 4,
            install = function(configs, package)
                return [[
        table.insert(configs, "-Ddefault_library=" .. (package:config("shared") and "shared" or "static"))
        import("package.tools.meson").install(package, configs)]]
            end,
        },
        ["BUILD,BUILD.bazel"] = {
            deps = {"bazel"},
            priority = 5,
            install = function(configs, package)
                return [[
        import("package.tools.bazel").install(package, configs)]]
            end,
        }
    }

    -- detect build system
    local build_system_detected = {}
    if repodir then
        local files = os.files(path.join(repodir, "*")) or {}
        table.join2(files, os.files(path.join(repodir, "*", "*")))
        for _, file in ipairs(files) do
            local filename = path.filename(file)
            for k, v in pairs(build_systems) do
                local filenames = hashset.from(k:split(","))
                if filenames:has(filename) then
                    table.insert(build_system_detected, v)
                end
            end
        end
        os.rm(repodir)
    end
    local build_system
    if #build_system_detected > 0 then
        table.sort(build_system_detected, function (a, b) return a.priority < b.priority end)
        build_system = build_system_detected[1]
    end
    if not build_system then
        build_system = build_systems["xmake.lua"]
    end

    -- add dependencies
    if build_system then
        local deps = table.wrap(build_system.deps)
        if deps and #deps > 0 then
            file:print('')
            file:print('    add_deps("' .. table.concat(deps, '", "') .. '")')
        end
    end

    -- generate install scripts
    file:print('')
    file:print('    on_install(function (package)')
    file:print('        local configs = {}')
    if build_system then
        file:print(build_system.install(configs, package))
    end
    file:print('    end)')

    -- generate test scripts
    file:print('')
    file:print('    on_test(function (package)')
    file:print('        assert(package:has_cfuncs("foo", {includes = "foo.h"}))')
    file:print('    end)')
    file:close()

    io.cat(packagefile)
    cprint("${bright}%s generated!", packagefile)
end

function main(...)
    local opt = option.parse(table.pack(...), options, "New a package.", "", "Usage: xmake l scripts/new.lua [options]")
    local repo = assert(opt.repo, "repository name must be set!")
    local reponame = repo:sub(8)

    if repo:startswith("github:") then
        generate_package(reponame, get_github_data)
        return
    end

    if repo:startswith("gitlab:") then
        generate_package(reponame, get_gitlab_data)
        return
    end

    raise("unsupported repository source. only 'github' and 'gitlab' are supported.")
end