diff --git a/README.md b/README.md index d3593a2..88b8b4c 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,10 @@ # xmake -xmake包仓库 \ No newline at end of file +xmake包仓库 + + +# 测试包 + +```sh +xmake l ./scripts/test.lua -v -D "sioclient 3.1.0" +``` \ No newline at end of file diff --git a/scripts/automerge.lua b/scripts/automerge.lua new file mode 100644 index 0000000..bf14b54 --- /dev/null +++ b/scripts/automerge.lua @@ -0,0 +1,36 @@ + +function _get_autoupdate_pr_list() + local result = {} + local list = os.iorun("gh pr list --label auto-update --state open -R xmake-io/xmake-repo") + if list then + for _, line in ipairs(list:split("\n")) do + if line:find("Auto-update", 1, true) then + local id = line:match("(%d+)%s+Auto%-update") + if id then + table.insert(result, {id = id, title = line}) + end + end + end + end + return result +end + +function _check_pr_passed(id) + local ok = os.vexecv("gh", {"pr", "checks", id, "-R", "xmake-io/xmake-repo"}, {try = true}) + if ok == 0 then + return true + end +end + +function main() + local pr_list = _get_autoupdate_pr_list() + for _, info in ipairs(pr_list) do + local id = info.id + local title = info.title + print("checking %s ...", title) + if _check_pr_passed(id) then + print("pull/%d passed, it will be merged next.", id) + os.vexec("gh pr merge %d --squash -d -R xmake-io/xmake-repo", id) + end + end +end diff --git a/scripts/autoupdate.lua b/scripts/autoupdate.lua new file mode 100644 index 0000000..91fc8ed --- /dev/null +++ b/scripts/autoupdate.lua @@ -0,0 +1,158 @@ +import("core.package.package") +import("core.base.semver") +import("core.base.hashset") +import("devel.git") +import("packages", {alias = "packages_util"}) + +function _load_package(packagename, packagedir, packagefile) + local funcinfo = debug.getinfo(package.load_from_repository) + if funcinfo and funcinfo.nparams == 3 then -- >= 2.7.8 + return package.load_from_repository(packagename, packagedir, {packagefile = packagefile}) + else + -- deprecated + return package.load_from_repository(packagename, nil, packagedir, packagefile) + end +end + +function _get_all_packages(pattern) + local packages = _g.packages + if not packages then + packages = {} + for _, packagedir in ipairs(os.dirs(path.join("packages", "*", "*"))) do + local packagename = path.filename(packagedir) + if not pattern or packagename:match(pattern) then + local packagefile = path.join(packagedir, "xmake.lua") + local instance = _load_package(packagename, packagedir, packagefile) + local basename = instance:get("base") + if instance and basename then + local basedir = path.join("packages", basename:sub(1, 1):lower(), basename:lower()) + local basefile = path.join(basedir, "xmake.lua") + instance._BASE = _load_package(basename, basedir, basefile) + end + if instance then + table.insert(packages, instance) + end + end + end + _g.packages = packages + end + return packages +end + +function _is_pending(instance, version) + local branch = "autoupdate-" .. instance:name() .. "-" .. version + local repourl = "git@github.com:xmake-io/xmake-repo.git" + local is_pending = false + local remote_branches = os.iorun("git ls-remote --head %s", repourl) + if remote_branches then + for _, remote_branch in ipairs(remote_branches:split("\n")) do + remote_branch = remote_branch:split("%s")[2] + if remote_branch == "refs/heads/" .. branch then + is_pending = true + break + end + end + end + return is_pending +end + +function _update_version(instance, version, shasum) + local branch = "autoupdate-" .. instance:name() .. "-" .. version + local branch_current = os.iorun("git branch --show-current"):trim() + local repourl = "git@github.com:xmake-io/xmake-repo.git" + os.vexec("git reset --hard HEAD") + os.vexec("git clean -fdx") + os.execv("git", {"branch", "-D", branch}, {try = true}) + os.vexec("git checkout dev") + os.vexec("git pull %s dev", repourl) + os.vexec("git branch %s", branch) + os.vexec("git checkout %s", branch) + local inserted = false + local scriptfile = path.join(instance:scriptdir(), "xmake.lua") + local version_current + if os.isfile(scriptfile) then + io.gsub(scriptfile, "add_versions%(\"(.-)\",%s+\"(.-)\"%)", function (v, h) + if not version_current or semver.compare(v, version_current) > 0 then + version_current = v + end + if not inserted then + inserted = true + return string.format('add_versions("%s", "%s")\n add_versions("%s", "%s")', version, shasum, v, h) + end + end) + end + if not inserted then + local versionfiles = instance:get("versionfiles") + if versionfiles then + for _, versionfile in ipairs(table.wrap(versionfiles)) do + if not os.isfile(versionfile) then + versionfile = path.join(instance:scriptdir(), versionfile) + end + if os.isfile(versionfile) then + io.insert(versionfile, 1, string.format("%s %s", version, shasum)) + inserted = true + end + end + end + end + if inserted then + local body = string.format("New version of %s detected (package version: %s, last github version: %s)", + instance:name(), version_current, version) + os.vexec("git add .") + os.vexec("git commit -a -m \"Update %s to %s\"", instance:name(), version) + os.vexec("git push %s %s:%s", repourl, branch, branch) + os.vexec("gh pr create --label \"auto-update\" --title \"Auto-update %s to %s\" --body \"%s\" -R xmake-io/xmake-repo -B dev -H %s", + instance:name(), version, body, branch) + end + os.vexec("git reset --hard HEAD") + os.vexec("git checkout %s", branch_current) +end + +function _report_issue(instance) + local package_name = instance:name() + local curr_open_issue = os.iorun("gh issue list --label \"help wanted\" --label \"auto-update\" --search \"in:title [auto-update] %s requires manual handling\" -R xmake-io/xmake-repo --json number", + package_name) + if curr_open_issue == "[]\n" then + local body = string.format("Failed to get tags of %s, which may be due to changes in repository visibility.", + package_name) + local title = "[auto-update] " .. package_name .. " requires manual handling." + os.vexec("gh issue create --title \"%s\" --body \"%s\" --label \"help wanted,auto-update\" -R xmake-io/xmake-repo", + title, body) + else + print("Found a known open issue #%s for package %s", curr_open_issue:trim(), package_name) + end +end + +function main(pattern) + local count = 0 + local maxcount = 5 + local instances = _get_all_packages(pattern) + if #instances < maxcount then + maxcount = #instances + end + math.randomseed(os.time()) + while count < maxcount and #instances > 0 do + local idx = math.random(#instances) + local instance = instances[idx] + local checkupdate_filepath = path.join(instance:scriptdir(), "checkupdate.lua") + if not os.isfile(checkupdate_filepath) then + checkupdate_filepath = path.join(os.scriptdir(), "checkupdate.lua") + end + local updated = false + if os.isfile(checkupdate_filepath) then + local checkupdate = import("checkupdate", {rootdir = path.directory(checkupdate_filepath), anonymous = true}) + local version, shasum = checkupdate(instance) + if version == false then + _report_issue(instance) + elseif version and shasum and not _is_pending(instance, version) then + cprint("package(%s): new version ${bright}%s${clear} found, shasum: ${bright}%s", instance:name(), version, shasum) + _update_version(instance, version, shasum) + updated = true + end + end + if updated then + count = count + 1 + end + table.remove(instances, idx) + end +end diff --git a/scripts/build_artifacts.lua b/scripts/build_artifacts.lua new file mode 100644 index 0000000..da8e598 --- /dev/null +++ b/scripts/build_artifacts.lua @@ -0,0 +1,169 @@ +import("core.package.package") +import("core.base.semver") +import("core.base.hashset") +import("packages", {alias = "packages_util"}) + +-- load package +function _load_package(packagename, packagedir, packagefile) + local funcinfo = debug.getinfo(package.load_from_repository) + if funcinfo and funcinfo.nparams == 3 then -- >= 2.7.8 + return package.load_from_repository(packagename, packagedir, {packagefile = packagefile}) + else + -- deprecated + return package.load_from_repository(packagename, nil, packagedir, packagefile) + end +end + +function _need_artifact(instance) + return (not instance:is_headeronly()) and (packages_util.is_supported(instance, "windows", "x64") or packages_util.is_supported(instance, "windows", "x86")) +end + +function _build_artifacts(name, versions) + local buildinfo = {name = name, versions = versions} + print(buildinfo) + os.tryrm("build-artifacts") + os.exec("git clone git@github.com:xmake-mirror/build-artifacts.git -b build") + local oldir = os.cd("build-artifacts") + local trycount = 0 + while trycount < 2 do + local ok = try + { + function () + io.save("build.txt", buildinfo) + os.exec("git add -A") + os.exec("git commit -a -m \"autobuild %s by xmake-repo/ci\"", name) + os.exec("git push origin build") + return true + end, + catch + { + function () + os.exec("git reset --hard HEAD^") + os.exec("git pull origin build") + end + } + } + if ok then + break + end + trycount = trycount + 1 + end + assert(trycount < 2) + os.cd(oldir) +end + +function _get_latest_modified_packages() + print("find latest modified packages ..") + local instances = {} + local files = os.iorun("git diff --name-only HEAD^") + for _, file in ipairs(files:split('\n')) do + file = file:trim() + if file:find("packages", 1, true) and path.filename(file) == "xmake.lua" then + assert(file == file:lower(), "%s must be lower case!", file) + local packagedir = path.directory(file) + local packagename = path.filename(packagedir) + if #path.filename(path.directory(packagedir)) == 1 then + local instance = _load_package(packagename, packagedir, file) + if instance and _need_artifact(instance) then + table.insert(instances, instance) + print(" > %s", instance:name()) + end + end + end + end + print("%d found", #instances) + return instances +end + +function _get_all_packages() + local packages = _g.packages + if not packages then + packages = {} + for _, packagedir in ipairs(os.dirs(path.join("packages", "*", "*"))) do + local packagename = path.filename(packagedir) + local packagefile = path.join(packagedir, "xmake.lua") + local instance = _load_package(packagename, packagedir, packagefile) + local basename = instance:get("base") + if instance and basename then + local basedir = path.join("packages", basename:sub(1, 1):lower(), basename:lower()) + local basefile = path.join(basedir, "xmake.lua") + instance._BASE = _load_package(basename, basedir, basefile) + end + if instance and _need_artifact(instance) then + table.insert(packages, instance) + end + end + _g.packages = packages + end + return packages +end + +function _get_packagerefs_of(instance) + local packagerefs = {} + if instance:is_library() then + local packages = _get_all_packages() + for _, packageref in ipairs(packages) do + local deps = packageref:get("deps") + if deps and table.contains(table.wrap(deps), instance:name()) then + table.insert(packagerefs, packageref) + end + end + end + return packagerefs +end + +function _get_packagerefs_in_latest_24h() + print("find packagerefs in latest 24h ..") + local instances = {} + local list = os.iorun("git log --since=\"24 hours ago\" --oneline") + local lines = list:split('\n') + if #lines > 0 then + local line = lines[#lines] + local commit = line:split(" ")[1] + if commit and #commit == 8 then + local files = os.iorun("git diff --name-only " .. commit .. "^") + for _, file in ipairs(files:split('\n')) do + file = file:trim() + if file:find("packages", 1, true) and path.filename(file) == "xmake.lua" then + assert(file == file:lower(), "%s must be lower case!", file) + local packagedir = path.directory(file) + local packagename = path.filename(packagedir) + if #path.filename(path.directory(packagedir)) == 1 then + local instance = _load_package(packagename, packagedir, file) + if instance and _need_artifact(instance) then + table.insert(instances, instance) + end + end + end + end + end + end + local packagerefs = hashset.new() + for _, instance in ipairs(instances) do + print("%s: ", instance:name()) + for _, packageref in ipairs(_get_packagerefs_of(instance)) do + packagerefs:insert(packageref) + print(" -> %s", packageref:name()) + end + end + local result = {} + for _, packageref in packagerefs:keys() do + if #result < 24 then + table.insert(result, packageref) + end + end + print("%d found", #result) + return result +end + +function main(updaterefs) + local instances = updaterefs and _get_packagerefs_in_latest_24h() or _get_latest_modified_packages() + for _, instance in ipairs(instances) do + local versions = instance:versions() + if versions and #versions > 0 then + table.sort(versions, function (a, b) return semver.compare(a, b) > 0 end) + local version_latest = versions[1] + _build_artifacts(instance:name(), table.wrap(version_latest)) + end + end +end diff --git a/scripts/checkupdate.lua b/scripts/checkupdate.lua new file mode 100644 index 0000000..f085e90 --- /dev/null +++ b/scripts/checkupdate.lua @@ -0,0 +1,135 @@ +import("core.base.semver") +import("net.http") +import("devel.git") +import("private.action.require.impl.utils.filter") + +function shasum_of(package, url, version) + local shasum + local tmpfile = os.tmpfile() + package:version_set(version) + url = filter.handle(url, package) + local ok = try { function() http.download(url, tmpfile); return true end } + if ok and os.isfile(tmpfile) and os.filesize(tmpfile) > 1024 then + shasum = hash.sha256(tmpfile) + end + os.tryrm(tmpfile) + return shasum +end + +function _is_valid_version(version) + if not semver.is_valid(version) then + return false + end + local v = semver.new(version) + local prerelease = v:prerelease() + if prerelease and #prerelease > 0 then + return false + end + return true +end + +function _get_version_and_shasum(package, url, version_latest) + if version_latest then + local has_prefix_v = false + for _, version in ipairs(package:versions()) do + if version:startswith("v") then + has_prefix_v = true + end + if semver.compare(version, version_latest) >= 0 then + version_latest = nil + break + end + end + if version_latest then + if has_prefix_v and not version_latest:startswith("v") then + version_latest = "v" .. version_latest + elseif not has_prefix_v and version_latest:startswith("v") then + version_latest = version_latest:sub(2) + end + end + end + if version_latest then + local shasum = shasum_of(package, url, version_latest) + if shasum then + return version_latest, shasum + end + end +end + +function _check_version_from_github_tags(package, url) + local repourl = url:match("https://github%.com/.-/.-/") + if repourl then + print("checking version from github tags %s ..", repourl) + local version_latest + local tags = try {function() return git.tags(repourl) end} + if not tags then + return false -- failed to get tags + end + for _, tag in ipairs(tags) do + if _is_valid_version(tag) and (not version_latest or semver.compare(tag, version_latest) > 0) then + version_latest = tag + end + end + if version_latest then + return _get_version_and_shasum(package, url, version_latest) + end + end +end + +function _check_version_from_github_releases(package, url) + local repourl = url:match("https://github%.com/.-/.-/") + if repourl then + print("checking version from github releases %s ..", repourl) + local list = try {function() return os.iorunv("gh", {"release", "list", "--exclude-drafts", "--exclude-pre-releases", "-R", repourl}) end} + if not list then + list = try {function() return os.iorunv("gh", {"release", "list", "-R", repourl}) end} + end + if not list then + return false -- failed to get tags + end + if list then + local version_latest + for _, line in ipairs(list:split("\n")) do + local splitinfo = line:split("%s+") + local release = splitinfo[1] + local version = splitinfo[#splitinfo - 1] + if not version or not _is_valid_version(version) and _is_valid_version(release) then + version = release + end + if version and _is_valid_version(version) then + version_latest = version + break + end + end + if version_latest then + return _get_version_and_shasum(package, url, version_latest) + end + end + end +end + +function _version_is_behind_conditions(package) + local scriptfile = path.join(package:scriptdir(), "xmake.lua") + local file = io.open(scriptfile) + for line in file:lines() do + local pos = line:find("add_versions", 1, true) or line:find("add_versionfiles", 1, true) + if pos and pos > 5 then + return true + end + end + file:close() +end + +function main(package) + local checkers = { + ["https://github%.com/.-/.-/archive/refs/tags/.*"] = _check_version_from_github_tags, + ["https://github%.com/.-/.-/releases/download/.*"] = _check_version_from_github_releases + } + for _, url in ipairs(package:urls()) do + for pattern, checker in pairs(checkers) do + if url:match(pattern) and not _version_is_behind_conditions(package) then + return checker(package, url) + end + end + end +end diff --git a/scripts/list.lua b/scripts/list.lua new file mode 100644 index 0000000..d045046 --- /dev/null +++ b/scripts/list.lua @@ -0,0 +1,14 @@ +import("packages") + +function main(...) + for plat, pkgs in pairs(packages()) do + cprint("${magenta}%s${clear}:", plat) + for _, pkg in ipairs(pkgs) do + if pkg.generic then + cprint(" ${yellow}->${clear} %s", pkg.name) + else + cprint(" ${yellow}->${clear} %s (%s)", pkg.name, table.concat(pkg.archs, ", ")) + end + end + end +end diff --git a/scripts/new.lua b/scripts/new.lua new file mode 100644 index 0000000..2d423b7 --- /dev/null +++ b/scripts/new.lua @@ -0,0 +1,297 @@ +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 diff --git a/scripts/packages.lua b/scripts/packages.lua new file mode 100644 index 0000000..cdb55c9 --- /dev/null +++ b/scripts/packages.lua @@ -0,0 +1,71 @@ +-- imports +import("core.package.package") +import("core.platform.platform") +import("private.core.base.select_script") + +-- is supported platform and architecture? +function is_supported(instance, plat, arch, opt) + opt = opt or {} + if instance:is_template() then + return false + end + + local script = instance:get(instance:is_fetchonly() and "fetch" or "install") + if not select_script(script, {plat = plat, arch = arch}) then + if opt.native and select_script(script, { + plat = plat, arch = arch, subhost = plat, subarch = arch}) then + return true + end + return false + end + return true +end + +-- load package +function _load_package(packagename, packagedir, packagefile) + local funcinfo = debug.getinfo(package.load_from_repository) + if funcinfo and funcinfo.nparams == 3 then -- >= 2.7.8 + return package.load_from_repository(packagename, packagedir, {packagefile = packagefile}) + else + -- deprecated + return package.load_from_repository(packagename, nil, packagedir, packagefile) + end +end + +-- the main entry +function main(opt) + opt = opt or {} + local packages = {} + for _, packagedir in ipairs(os.dirs(path.join("packages", "*", "*"))) do + local packagename = path.filename(packagedir) + local packagefile = path.join(packagedir, "xmake.lua") + local instance = _load_package(packagename, packagedir, packagefile) + local basename = instance:get("base") + if instance and basename then + local basedir = path.join("packages", basename:sub(1, 1):lower(), basename:lower()) + local basefile = path.join(basedir, "xmake.lua") + instance._BASE = _load_package(basename, basedir, basefile) + end + if instance then + for _, plat in ipairs({"windows", "linux", "macosx", "iphoneos", "android", "mingw", "msys", "bsd", "wasm", "cross"}) do + local archs = platform.archs(plat) + if archs then + local package_archs = {} + for _, arch in ipairs(archs) do + if is_supported(instance, plat, arch, opt) then + table.insert(package_archs, arch) + end + end + if #package_archs > 0 then + packages[plat] = packages[plat] or {} + table.insert(packages[plat], {name = instance:name(), instance = instance, archs = package_archs, generic = #package_archs == #archs}) + end + end + end + end + end + for _, packages_plat in pairs(packages) do + table.sort(packages_plat, function(a, b) return a.name < b.name end) + end + return packages +end diff --git a/scripts/sync.lua b/scripts/sync.lua new file mode 100644 index 0000000..7d3699b --- /dev/null +++ b/scripts/sync.lua @@ -0,0 +1,16 @@ +import("core.base.option") + +function main() + print("update xmake-repo ..") + os.exec("git clone git@github.com:xmake-io/xmake-repo.git -b dev --recurse-submodules") + os.cd("xmake-repo") + os.exec("git push git@gitlab.com:tboox/xmake-repo.git dev") + os.exec("git push git@gitee.com:tboox/xmake-repo.git dev") + os.exec("git push git@gitcode.com:xmake-io/xmake-repo.git dev") + os.exec("git checkout master") + os.exec("git merge dev") + os.exec("git push git@github.com:xmake-io/xmake-repo.git master") + os.exec("git push git@gitlab.com:tboox/xmake-repo.git master") + os.exec("git push git@gitee.com:tboox/xmake-repo.git master") + os.exec("git push git@gitcode.com:xmake-io/xmake-repo.git master") +end diff --git a/scripts/test.lua b/scripts/test.lua new file mode 100644 index 0000000..e67253b --- /dev/null +++ b/scripts/test.lua @@ -0,0 +1,397 @@ +-- imports +import("core.base.option") +import("core.platform.platform") +import("core.package.package", {alias = "core_package"}) +import("packages", {alias = "get_packages"}) + +-- the options +local options = +{ + {'v', "verbose", "k", nil, "Enable verbose information." } +, {'D', "diagnosis", "k", nil, "Enable diagnosis information." } +, {nil, "shallow", "k", nil, "Only install the root packages." } +, {'k', "kind", "kv", nil, "Enable static/shared library." } +, {'p', "plat", "kv", nil, "Set the given platform." } +, {'a', "arch", "kv", nil, "Set the given architecture." } +, {'m', "mode", "kv", nil, "Set the given mode." } +, {'j', "jobs", "kv", nil, "Set the build jobs." } +, {'f', "configs", "kv", nil, "Set the configs." } +, {'d', "debugdir", "kv", nil, "Set the debug source directory." } +, {nil, "policies", "kv", nil, "Set the policies." } +, {nil, "fetch", "k", nil, "Fetch package only." } +, {nil, "precompiled", "k", nil, "Attemp to install the precompiled package." } +, {nil, "remote", "k", nil, "Test package on the remote server." } +, {nil, "linkjobs", "kv", nil, "Set the link jobs." } +, {nil, "cflags", "kv", nil, "Set the cflags." } +, {nil, "cxxflags", "kv", nil, "Set the cxxflags." } +, {nil, "ldflags", "kv", nil, "Set the ldflags." } +, {nil, "ndk", "kv", nil, "Set the Android NDK directory." } +, {nil, "ndk_sdkver", "kv", nil, "Set the Android NDK platform sdk version." } +, {nil, "sdk", "kv", nil, "Set the SDK directory of cross toolchain." } +, {nil, "vs", "kv", nil, "Set the VS Compiler version." } +, {nil, "vs_sdkver", "kv", nil, "Set the Windows SDK version." } +, {nil, "vs_toolset", "kv", nil, "Set the Windows Toolset version." } +, {nil, "vs_runtime", "kv", nil, "Set the VS Runtime library (deprecated)." } +, {nil, "runtimes", "kv", nil, "Set the Runtime libraries." } +, {nil, "xcode_sdkver", "kv", nil, "The SDK Version for Xcode" } +, {nil, "target_minver", "kv", nil, "The Target Minimal Version" } +, {nil, "appledev", "kv", nil, "The Apple Device Type" } +, {nil, "mingw", "kv", nil, "Set the MingW directory." } +, {nil, "toolchain", "kv", nil, "Set the toolchain name." } +, {nil, "toolchain_host", "kv", nil, "Set the host toolchain name." } +, {nil, "packages", "vs", nil, "The package list." } +} + +-- check package is supported? +function _check_package_is_supported() + for _, names in pairs(core_package.apis()) do + for _, name in ipairs(names) do + if type(name) == "string" and name == "package.on_check" then + return true + end + end + end +end + +-- config packages +function _config_packages(argv, packages) + local config_argv = {"f", "-c"} + if argv.verbose then + table.insert(config_argv, "-v") + end + if argv.diagnosis then + table.insert(config_argv, "-D") + end + if argv.plat then + table.insert(config_argv, "--plat=" .. argv.plat) + end + if argv.arch then + table.insert(config_argv, "--arch=" .. argv.arch) + end + if argv.mode then + table.insert(config_argv, "--mode=" .. argv.mode) + end + if argv.policies then + table.insert(config_argv, "--policies=" .. argv.policies) + end + if argv.ndk then + table.insert(config_argv, "--ndk=" .. argv.ndk) + end + if argv.sdk then + table.insert(config_argv, "--sdk=" .. argv.sdk) + end + if argv.ndk_sdkver then + table.insert(config_argv, "--ndk_sdkver=" .. argv.ndk_sdkver) + end + if argv.vs then + table.insert(config_argv, "--vs=" .. argv.vs) + end + if argv.vs_sdkver then + table.insert(config_argv, "--vs_sdkver=" .. argv.vs_sdkver) + end + if argv.vs_toolset then + table.insert(config_argv, "--vs_toolset=" .. argv.vs_toolset) + end + local runtimes = argv.runtimes or argv.vs_runtime + if runtimes then + if is_host("windows") then + table.insert(config_argv, "--vs_runtime=" .. runtimes) + else + table.insert(config_argv, "--runtimes=" .. runtimes) + end + end + if argv.xcode_sdkver then + table.insert(config_argv, "--xcode_sdkver=" .. argv.xcode_sdkver) + end + if argv.target_minver then + table.insert(config_argv, "--target_minver=" .. argv.target_minver) + end + if argv.appledev then + table.insert(config_argv, "--appledev=" .. argv.appledev) + end + if argv.mingw then + table.insert(config_argv, "--mingw=" .. argv.mingw) + end + if argv.toolchain then + table.insert(config_argv, "--toolchain=" .. argv.toolchain) + end + if argv.toolchain_host then + table.insert(config_argv, "--toolchain_host=" .. argv.toolchain_host) + end + if argv.cflags then + table.insert(config_argv, "--cflags=" .. argv.cflags) + end + if argv.cxxflags then + table.insert(config_argv, "--cxxflags=" .. argv.cxxflags) + end + if argv.ldflags then + table.insert(config_argv, "--ldflags=" .. argv.ldflags) + end + os.vexecv(os.programfile(), config_argv) +end + +-- get extra string +function _get_extra_str(argv) + local extra = {} + if argv.mode == "debug" then + extra.debug = true + end + -- Some packages set shared=true as default, so we need to force set + -- shared=false to test static build. + extra.configs = extra.configs or {} + extra.configs.shared = argv.kind == "shared" + local configs = argv.configs + if configs then + extra.system = false + extra.configs = extra.configs or {} + local extra_configs, errors = ("{" .. configs .. "}"):deserialize() + if extra_configs then + table.join2(extra.configs, extra_configs) + else + raise(errors) + end + end + return string.serialize(extra, {indent = false, strip = true}) +end + +-- load packages +function _load_packages(argv, packages) + _config_packages(argv, packages) + local info_argv = {"require", "-f", "-y", "--info"} + if argv.verbose then + table.insert(info_argv, "-v") + end + if argv.diagnosis then + table.insert(info_argv, "-D") + end + local extra_str = _get_extra_str(argv) + table.insert(info_argv, "--extra=" .. extra_str) + + -- call `xrepo info` to test on_load + if #packages > 0 then + print("testing to load packages ...") + print(" > if it causes errors, please remove assert/raise() to on_check.") + os.vexecv(os.programfile(), table.join(info_argv, packages)) + end +end + +-- require packages +function _require_packages(argv, packages) + _config_packages(argv, packages) + local require_argv = {"require", "-f", "-y"} + local check_argv = {"require", "-f", "-y", "--check"} + if not argv.precompiled then + table.insert(require_argv, "--build") + end + if argv.verbose then + table.insert(require_argv, "-v") + table.insert(check_argv, "-v") + end + if argv.diagnosis then + table.insert(require_argv, "-D") + table.insert(check_argv, "-D") + end + local is_debug = false + if argv.debugdir then + is_debug = true + table.insert(require_argv, "--debugdir=" .. argv.debugdir) + end + if argv.shallow or is_debug then + table.insert(require_argv, "--shallow") + end + if argv.jobs then + table.insert(require_argv, "--jobs=" .. argv.jobs) + end + if argv.linkjobs then + table.insert(require_argv, "--linkjobs=" .. argv.linkjobs) + end + if argv.fetch then + table.insert(require_argv, "--fetch") + end + local extra_str = _get_extra_str(argv) + table.insert(require_argv, "--extra=" .. extra_str) + table.insert(check_argv, "--extra=" .. extra_str) + + -- test on_check + local install_packages = {} + if _check_package_is_supported() then + print("testing to check packages ...") + for _, package in ipairs(packages) do + local ok = os.vexecv(os.programfile(), table.join(check_argv, package), {try = true}) + if ok == 0 then + table.insert(install_packages, package) + end + end + else + install_packages = packages + end + + -- test installation + if #install_packages > 0 then + print("testing to install packages ...") + os.vexecv(os.programfile(), table.join(require_argv, install_packages)) + else + print("no testable packages on %s or you're using lower version xmake!", argv.plat or os.subhost()) + end +end + +-- the given package is supported? +function _package_is_supported(argv, packagename) + local packages = get_packages() + if packages then + local plat = argv.plat or os.subhost() + local packages_plat = packages[plat] + for _, package in ipairs(packages_plat) do + if package and packagename:split("%s+")[1] == package.name then + local arch = argv.arch + if not arch and plat ~= os.subhost() then + arch = table.wrap(platform.archs(plat))[1] + end + if not arch then + arch = os.subarch() + end + for _, package_arch in ipairs(package.archs) do + print(package_arch, package.archs) + if arch == package_arch then + return true + end + end + end + end + end +end + +function append_package_version(packages, line) + local version = line:match("add_versions%(\"(.-)\"") or line:match("package:add%(\"versions\",%s*\"(.-)\"") + if version then + if version:find(":", 1, true) then + version = version:split(":")[2] + end + if #packages > 0 and version then + local lastpackage = packages[#packages] + local splitinfo = lastpackage:split("%s+") + table.insert(packages, splitinfo[1] .. " " .. version) + end + end +end + +function get_modified_packages() + local new_packages = {} + local old_packages = {} + local diff = os.iorun("git --no-pager diff HEAD^") + for _, line in ipairs(diff:split("\n")) do + if line:startswith("+++ b/") then + local file = line:sub(7) + if file:startswith("packages") then + assert(file == file:lower(), "%s must be lower case!", file) + local package = file:match("packages/%w/(%S-)/") + table.insert(new_packages, package) + table.insert(old_packages, package) + end + elseif line:startswith("+") and (line:find("add_versions", 1, true) or line:find("package:add(\"versions\"", 1, true)) then + append_package_version(new_packages, line) + elseif line:startswith("-") and (line:find("add_versions", 1, true) or line:find("package:add(\"versions\"", 1, true)) then + append_package_version(old_packages, line) + end + end + if #old_packages > 0 then + table.remove_if(old_packages, function (_, package) + local splitinfo = package:split("%s+") + return #splitinfo == 1 + end) + table.remove_if(new_packages, function (_, package) + return table.contains(old_packages, package) + end) + -- { + -- "pkgname", <-- remove this + -- "pkgname pkgver" + -- } + for i = #new_packages - 1, 1, -1 do + if new_packages[i + 1]:startswith(new_packages[i] .. " ") then + table.remove(new_packages, i) + end + end + end + return table.unique(new_packages) +end + +-- @see https://github.com/xmake-io/xmake-repo/issues/6940 +function _lock_packages(packages) + local locked_packages = {} + for _, package in ipairs(packages) do + if table.contains(locked_packages, package) then + raise("package(%s) has been locked, please do not submit it, @see https://github.com/xmake-io/xmake-repo/issues/6940", package) + end + end +end + +-- the main entry +function main(...) + + -- parse arguments + local argv = option.parse({...}, options, "Test all the given or changed packages.") + + -- get packages + local packages = argv.packages or {} + if #packages == 0 then + packages = get_modified_packages() + end + if #packages == 0 then + table.insert(packages, "tbox dev") + end + + -- prepare test project + local repodir = os.curdir() + local workdir = path.join(os.tmpdir(), "xmake-repo") + print(packages) + os.setenv("XMAKE_STATS", "false") + if not os.isfile(path.join(workdir, "test", "xmake.lua")) then + os.tryrm(workdir) + os.mkdir(workdir) + os.cd(workdir) + os.execv(os.programfile(), {"create", "test"}) + else + os.cd(workdir) + end + os.cd("test") + print(os.curdir()) + -- do action for remote? + if os.isdir("xmake-repo") then + os.execv(os.programfile(), {"service", "--disconnect"}) + end + if argv.remote then + os.tryrm("xmake-repo") + os.cp(path.join(repodir, "packages"), "xmake-repo/packages") + os.execv(os.programfile(), {"service", "--connect"}) + repodir = "xmake-repo" + end + os.execv(os.programfile(), {"repo", "--add", "local-repo", repodir}) + os.execv(os.programfile(), {"repo", "-l"}) + + local packages_original = table.clone(packages) + + -- load packages + _load_packages(argv, packages_original) + + local old_dir = os.cd(repodir) + -- remove unsupported packages + for idx, package in irpairs(packages) do + assert(package == package:lower(), "package(%s) must be lower case!", package) + if not _package_is_supported(argv, package) then + table.remove(packages, idx) + end + end + os.cd(old_dir) + + -- no testable packages + if #packages == 0 then + print("no testable packages on %s!", argv.plat or os.subhost()) + return + end + + -- lock packages + -- _lock_packages(packages) + + -- require packages + _require_packages(argv, packages) +end