diff --git a/packages/lde-build/.gitignore b/packages/lde-build/.gitignore new file mode 100644 index 0000000..8a3d203 --- /dev/null +++ b/packages/lde-build/.gitignore @@ -0,0 +1,2 @@ +/target/ +/lde.lock \ No newline at end of file diff --git a/packages/lde-build/lde.json b/packages/lde-build/lde.json new file mode 100644 index 0000000..65aa7a9 --- /dev/null +++ b/packages/lde-build/lde.json @@ -0,0 +1,10 @@ +{ + "name": "lde-build", + "version": "0.1.0", + "dependencies": { + "curl-sys": { "git": "https://github.com/lde-org/curl-sys" }, + "fs": { "git": "https://github.com/lde-org/fs" }, + "path": { "git": "https://github.com/lde-org/path" }, + "archive": { "git": "https://github.com/lde-org/archive" } + } +} diff --git a/packages/lde-build/src/build.lua b/packages/lde-build/src/build.lua new file mode 100644 index 0000000..a0c16d0 --- /dev/null +++ b/packages/lde-build/src/build.lua @@ -0,0 +1,99 @@ +local curl = require("curl-sys") +local fs = require("fs") +local path = require("path") +local archive = require("archive") + +---@class lde.build.Instance +---@field outDir string +local Instance = {} +Instance.__index = Instance + +---@param outDir string +---@return lde.build.Instance +function Instance.new(outDir) + return setmetatable({ outDir = outDir }, Instance) +end + +---@return string +function Instance:fetch(url) + local res, err = curl.get(url) + if not res then + error("failed to fetch " .. url .. ": " .. err) + end + + return res.body +end + +---@param rel string # Relative path at output dir +---@param content string +function Instance:write(rel, content) + local full = path.join(self.outDir, rel) + fs.mkdirAll(path.dirname(full)) + assert(fs.write(full, content), "failed to write " .. full) +end + +---@param rel string # Relative path at output dir +---@return string +function Instance:read(rel) + local full = path.join(self.outDir, rel) + local res = fs.read(full) + assert(res, "failed to read " .. full) + return res +end + +---@param rel string # Relative path at output dir +---@param dest string # Relative path at output dir +function Instance:extract(rel, dest) + local full = path.join(self.outDir, rel) + + local ok, err = archive.new(full):extract(path.join(self.outDir, dest)) + if not ok then + error("failed to extract " .. full .. ": " .. err) + end +end + +---@param rel string # Relative path at output dir +---@param dest string # Relative path at output dir +function Instance:copy(rel, dest) + local full = path.join(self.outDir, rel) + + local ok, err = fs.copy(full, path.join(self.outDir, dest)) + if not ok then + error("failed to copy " .. full .. ": " .. err) + end +end + +---@param rel string # Relative path at output dir +function Instance:delete(rel) + local full = path.join(self.outDir, rel) + + local ok, err = fs.delete(full) + if not ok then + error("failed to remove " .. full .. ": " .. err) + end +end + +---@param rel string # Relative path at output dir +---@param dest string # Relative path at output dir +function Instance:move(rel, dest) + local full = path.join(self.outDir, rel) + + local ok, err = fs.move(full, path.join(self.outDir, dest)) + if not ok then + error("failed to move " .. full .. ": " .. err) + end +end + +---@param rel string # Relative path at output dir +function Instance:exists(rel) + local full = path.join(self.outDir, rel) + return fs.exists(full) +end + +---@param cmd string +function Instance:sh(cmd) + local res = os.execute(cmd) + assert(res == 0, "failed to execute " .. cmd) +end + +return Instance diff --git a/packages/lde-build/src/init.lua b/packages/lde-build/src/init.lua new file mode 100644 index 0000000..b7d63fd --- /dev/null +++ b/packages/lde-build/src/init.lua @@ -0,0 +1,6 @@ +---@meta + +---@type lde.build.Instance +local t = require("lde-build.build").new() + +return t diff --git a/packages/lde-core/lde.json b/packages/lde-core/lde.json index 2aca042..3f1a894 100644 --- a/packages/lde-core/lde.json +++ b/packages/lde-core/lde.json @@ -14,6 +14,7 @@ "curl-sys": { "git": "https://github.com/lde-org/curl-sys" }, "semver": { "path": "../semver" }, "lde-test": { "path": "../lde-test" }, + "lde-build": { "path": "../lde-build" }, "git2-sys": { "git": "https://github.com/lde-org/git2-sys" }, "rocked": { "path": "../rocked" }, "luarocks": { "path": "../luarocks" }, diff --git a/packages/lde-core/src/global/init.lua b/packages/lde-core/src/global/init.lua index ebef817..9e138c5 100644 --- a/packages/lde-core/src/global/init.lua +++ b/packages/lde-core/src/global/init.lua @@ -31,6 +31,81 @@ local function sanitize(s) return (string.gsub(s, "[^%w_%-]", "_")) end +--- Returns "github", "gitlab", or nil if the URL is not a recognized git host. +---@param url string +---@return string? +local function isRecognizedGitHost(url) + if url:match("^https?://github%.com/") then return "github" end + if url:match("^https?://gitlab%.com/") then return "gitlab" end + return nil +end + +--- Builds a tarball URL for a recognized git host at a given ref. +---@param url string # git clone URL (may have .git suffix or /tree/... paths) +---@param ref string # commit SHA, branch name, or tag +---@param hostType string # "github" or "gitlab" +---@return string +local function buildTarballUrl(url, ref, hostType) + local base = url:gsub("%.git$", "") + base = base:gsub("/tree/.*$", "") + base = base:gsub("/$", "") + + if hostType == "github" then + return base .. "/archive/" .. ref .. ".tar.gz" + elseif hostType == "gitlab" then + local repoName = base:match("/([^/]+)$") + return base .. "/-/archive/" .. ref .. "/" .. repoName .. "-" .. ref .. ".tar.gz" + end + + error("Unknown host type: " .. hostType) +end + +--- Downloads and extracts a git tarball for a recognized host into repoDir. +---@param url string +---@param commit string +---@param hostType string +---@param repoDir string +---@param label string +local function downloadTarball(url, commit, hostType, repoDir, label) + local tarballUrl = buildTarballUrl(url, commit, hostType) + local bar = lde.verbose and ansi.ProgressBar("Downloading " .. label) or nil + fs.mkdir(repoDir) + + local archiveFile = repoDir .. ".archive" + + local dlOpts + if bar then + dlOpts = { + progress = function(dltotal, dlnow) + local ratio = dltotal > 0 and (dlnow / dltotal) or nil + local info = dltotal > 0 + and (ansi.formatBytes(dlnow) .. " / " .. ansi.formatBytes(dltotal)) + or ansi.formatBytes(dlnow) + bar:update(ratio, info) + end + } + end + + local ok, dlErr = curl.download(tarballUrl, archiveFile, dlOpts) + if not ok then + fs.rmdir(repoDir) + fs.delete(archiveFile) + if bar then bar:fail("Downloading " .. label) end + error("Failed to download " .. tarballUrl .. ": " .. (dlErr or "")) + end + + local ok2, err2 = Archive.new(archiveFile):extract(repoDir, { stripComponents = true }) + fs.delete(archiveFile) + + if not ok2 then + fs.rmdir(repoDir) + if bar then bar:fail("Downloading " .. label) end + error("Failed to extract " .. label .. ": " .. (err2 or "")) + end + + if bar then bar:done("Downloaded " .. label) end +end + ---@type string? local dirOverride = nil @@ -157,75 +232,78 @@ function global.resolveRegistryVersion(portfile, version) return latest, versions[latest] end ---- Builds the cache directory name for a git repo. ---- Format: name, name-branch, or name-branch-commit +--- Builds the cache directory name for a git repo: -. ---@param repoName string ----@param branch string? ----@param commit string? +---@param commit string ---@return string -function global.getGitRepoDir(repoName, branch, commit) - local parts = { sanitize(repoName) } - - if branch then - parts[#parts + 1] = sanitize(branch) - end - - if commit then - parts[#parts + 1] = sanitize(commit) - end - - local fullName = table.concat(parts, "-") - return path.join(global.getGitCacheDir(), fullName) +function global.getGitRepoDir(repoName, commit) + return path.join(global.getGitCacheDir(), sanitize(repoName) .. "-" .. sanitize(commit)) end +--- Git clone fallback for unrecognized hosts. Always checks out the specific commit. ---@param repoName string ---@param repoUrl string ----@param branch string? ----@param commit string? +---@param commit string ---@param progress fun(stats: table)? -function global.cloneDir(repoName, repoUrl, branch, commit, progress) - local repoDir = global.getGitRepoDir(repoName, branch, commit) - local repo, err = git2.clone(repoUrl, repoDir, branch, nil, progress) +function global.cloneDir(repoName, repoUrl, commit, progress) + local repoDir = global.getGitRepoDir(repoName, commit) + local repo, err = git2.clone(repoUrl, repoDir, nil, nil, progress) if not repo then return nil, err end repo:updateSubmodules(nil, progress) - if commit then - local ok, cerr = repo:checkout(commit) - if not ok then return nil, cerr end - end + local ok, cerr = repo:checkout(commit) + if not ok then return nil, cerr end return true end +--- Ensures a git repo is cached locally (via tarball for GitHub/GitLab, git clone otherwise). +--- Always resolves to a specific commit. Returns the cache directory and the pinned commit. ---@param repoName string ---@param repoUrl string ---@param branch string? ---@param commit string? +---@return string repoDir +---@return string commit function global.getOrInitGitRepo(repoName, repoUrl, branch, commit) - local repoDir = global.getGitRepoDir(repoName, branch, commit) + if not commit then + local ref = branch and ("refs/heads/" .. branch) or "HEAD" + local sha, err = git2.lsRemote(repoUrl, ref) + if not sha then + error("Failed to resolve '" .. ref .. "' for " .. repoUrl .. ": " .. (err or "")) + end + commit = sha + end + + local repoDir = global.getGitRepoDir(repoName, commit) if not fs.exists(repoDir) then - local progress - local bar = lde.verbose and ansi.ProgressBar("Cloning " .. repoName) or nil - if bar then - local totalObjs = 0 - progress = function(stats) - if stats.total_objects > 0 then - totalObjs = stats.total_objects + local hostType = isRecognizedGitHost(repoUrl) + if hostType then + downloadTarball(repoUrl, commit, hostType, repoDir, repoName) + else + local progress + local bar = lde.verbose and ansi.ProgressBar("Cloning " .. repoName) or nil + if bar then + local totalObjs = 0 + progress = function(stats) + if stats.total_objects > 0 then + totalObjs = stats.total_objects + end + local ratio = totalObjs > 0 and (stats.indexed_objects / totalObjs) or nil + local info = totalObjs > 0 + and string.format("%d/%d objects", stats.indexed_objects, totalObjs) + or string.format("%d objects, %s", stats.received_objects, ansi.formatBytes(stats.received_bytes)) + bar:update(ratio, info) end - local ratio = totalObjs > 0 and (stats.indexed_objects / totalObjs) or nil - local info = totalObjs > 0 - and string.format("%d/%d objects", stats.indexed_objects, totalObjs) - or string.format("%d objects, %s", stats.received_objects, ansi.formatBytes(stats.received_bytes)) - bar:update(ratio, info) end + local ok, err = global.cloneDir(repoName, repoUrl, commit, progress) + if not ok then + if bar then bar:fail("Cloning " .. repoName) end + error("Failed to clone git repository: " .. err) + end + if bar then bar:done("Cloned " .. repoName) end end - local ok, err = global.cloneDir(repoName, repoUrl, branch, commit, progress) - if not ok then - if bar then bar:fail("Cloning " .. repoName) end - error("Failed to clone git repository: " .. err) - end - if bar then bar:done("Cloned " .. repoName) end end - return repoDir + return repoDir, commit end --- Downloads and extracts an archive URL (.zip, .tar.gz, .tar.bz2, etc.) into the cache. @@ -305,22 +383,37 @@ function global.repoNameFromUrl(url) return url:match("([^/]+)%.git$") or url:match("([^/]+)$") end ---- Clones or retrieves a cached git repo directory (simple name+branch key, no commit). +--- Clones or retrieves a cached git repo directory. Always resolves to the latest commit. ---@param repoName string ---@param cloneUrl string ---@param branch string? ---@return string repoDir +---@return string commit function global.getOrCloneRepo(repoName, cloneUrl, branch) - local safeName = branch and (repoName .. "-" .. branch) or repoName - local repoDir = global.getGitRepoDir(safeName) + local ref = branch and ("refs/heads/" .. branch) or "HEAD" + local commit, err = git2.lsRemote(cloneUrl, ref) + if not commit then + error("Failed to resolve ref for " .. cloneUrl .. ": " .. (err or "")) + end + + local repoDir = global.getGitRepoDir(repoName, commit) if not fs.exists(repoDir) then - local repo, err = git2.clone(cloneUrl, repoDir, branch) - if not repo then - error("Failed to clone git repository: " .. (err or "unknown error")) + local hostType = isRecognizedGitHost(cloneUrl) + if hostType then + downloadTarball(cloneUrl, commit, hostType, repoDir, repoName) + else + local repo, cerr = git2.clone(cloneUrl, repoDir, branch) + if not repo then + error("Failed to clone git repository: " .. (cerr or "unknown error")) + end + repo:updateSubmodules() + local ok, cerr2 = repo:checkout(commit) + if not ok then + error("Failed to checkout commit: " .. (cerr2 or "unknown error")) + end end - repo:updateSubmodules() end - return repoDir + return repoDir, commit end --- Finds a named package inside a directory by scanning for lde.json files. diff --git a/packages/lde-core/src/package/init.lua b/packages/lde-core/src/package/init.lua index c7bf812..a81c637 100644 --- a/packages/lde-core/src/package/init.lua +++ b/packages/lde-core/src/package/init.lua @@ -59,9 +59,14 @@ local function defaultBuildFn(pkg, outputDir) return nil, "No build script found: " .. buildScriptPath end + local buildMod = require("lde-build.build") + local buildInstance = buildMod.new(outputDir) + return pkg:runFile(buildScriptPath, nil, { LDE_OUTPUT_DIR = outputDir, LPM_OUTPUT_DIR = outputDir -- compat + }, nil, nil, nil, { + ["lde-build"] = function() return buildInstance end }) end @@ -184,7 +189,7 @@ function Package:getDependencyPath(dir, info, relativeTo) relativeTo = relativeTo or self.dir if info.git then - return global.getGitRepoDir(dir, info.branch, info.commit) + return global.getGitRepoDir(dir, info.commit) elseif info.path then return path.normalize(path.join(relativeTo, info.path)) elseif info.archive then diff --git a/packages/lde-core/src/package/install/git.lua b/packages/lde-core/src/package/install/git.lua index 2b0d97b..ae9d521 100644 --- a/packages/lde-core/src/package/install/git.lua +++ b/packages/lde-core/src/package/install/git.lua @@ -1,26 +1,14 @@ local fs = require("fs") local path = require("path") -local git2 = require("git2-sys") local lde = require("lde-core") ---@param packageName string ---@param depInfo lde.Package.Config.GitDependency ---@return lde.Package, lde.Lockfile.GitDependency local function resolve(packageName, depInfo) - local repoDir = lde.global.getOrInitGitRepo(packageName, depInfo.git, depInfo.branch, depInfo.commit) - - local resolvedCommit = depInfo.commit - if not resolvedCommit then - local repo, openErr = git2.open(repoDir) - if not repo then - error("Failed to resolve HEAD commit for git dependency: " .. (openErr or "")) - end - local sha, revErr = repo:revparse("HEAD") - if not sha then - error("Failed to resolve HEAD commit for git dependency: " .. (revErr or "")) - end - resolvedCommit = sha - end + local repoDir, resolvedCommit = lde.global.getOrInitGitRepo( + packageName, depInfo.git, depInfo.branch, depInfo.commit + ) ---@type lde.Lockfile.GitDependency local lockEntry = { diff --git a/packages/lde-core/src/package/run.lua b/packages/lde-core/src/package/run.lua index 63b4636..b1b85c5 100644 --- a/packages/lde-core/src/package/run.lua +++ b/packages/lde-core/src/package/run.lua @@ -27,7 +27,7 @@ end ---@param cwd string ---@param profile boolean? ---@param flamegraph string? -local function runFileWithLDE(package, scriptPath, args, vars, cwd, profile, flamegraph) +local function runFileWithLDE(package, scriptPath, args, vars, cwd, profile, flamegraph, preload) local luaPath, luaCPath = getLuaPathsForPackage(package) return runtime.executeFile(scriptPath, { @@ -37,7 +37,8 @@ local function runFileWithLDE(package, scriptPath, args, vars, cwd, profile, fla packagePath = luaPath, packageCPath = luaCPath, profile = profile, - flamegraph = flamegraph + flamegraph = flamegraph, + preload = preload }) end @@ -96,7 +97,7 @@ end ---@param flamegraph string? ---@return boolean? ---@return string -local function runFile(package, scriptPath, args, vars, cwd, profile, flamegraph) +local function runFile(package, scriptPath, args, vars, cwd, profile, flamegraph, preload) package:build() local config = package:readConfig() @@ -116,7 +117,7 @@ local function runFile(package, scriptPath, args, vars, cwd, profile, flamegraph local engine = config.engine or "lde" if engine == "lde" or engine == "lpm" --[[ compat ]] then - return runFileWithLDE(package, scriptPath, args, vars, cwd, profile, flamegraph) + return runFileWithLDE(package, scriptPath, args, vars, cwd, profile, flamegraph, preload) end if profile or flamegraph then return nil, "Profiling is only supported when engine is 'lde'" diff --git a/packages/lde-core/src/package/update.lua b/packages/lde-core/src/package/update.lua index cef28ab..97c3328 100644 --- a/packages/lde-core/src/package/update.lua +++ b/packages/lde-core/src/package/update.lua @@ -7,33 +7,27 @@ local luarocks = require("luarocks") local global = require("lde-core.global") local util = require("lde-core.util") ---- Updates a single git dependency by pulling latest changes. ---- Only applies to git dependencies without a pinned commit. +--- Checks a git dependency for newer commits via ls-remote. ---@param name string ---@param depInfo lde.Package.Config.GitDependency ---@return boolean updated ---@return string message local function updateGitDependency(name, depInfo) - if depInfo.commit then - return false, "skipped (pinned to commit)" + local ref = depInfo.branch and ("refs/heads/" .. depInfo.branch) or "HEAD" + local latestCommit, err = git2.lsRemote(depInfo.git, ref) + if not latestCommit then + return false, "failed: " .. (err or "unknown error") end - local repoDir = global.getGitRepoDir(name, depInfo.branch, depInfo.commit) - if not fs.exists(repoDir) then - return false, "skipped (not installed)" + if depInfo.commit and latestCommit == depInfo.commit then + return false, "already up to date (" .. latestCommit:sub(1, 7) .. ")" end - local repo, openErr = git2.open(repoDir) - if not repo then - return false, "failed: " .. (openErr or "unknown error") - end - - local ok, pullErr = repo:pull() - if not ok then - return false, "failed: " .. (pullErr or "unknown error") - end + local msg = depInfo.commit + and (depInfo.commit:sub(1, 7) .. " -> " .. latestCommit:sub(1, 7)) + or ("at " .. latestCommit:sub(1, 7)) - return true, "updated" + return true, msg end --- Updates a registry dependency to the latest compatible version (same major). diff --git a/packages/lde-core/src/util/init.lua b/packages/lde-core/src/util/init.lua index 532d2f2..c7fef81 100644 --- a/packages/lde-core/src/util/init.lua +++ b/packages/lde-core/src/util/init.lua @@ -2,7 +2,6 @@ local util = {} local fs = require("fs") local path = require("path") -local git2 = require("git2-sys") local json = require("json") local rocked = require("rocked") local ansi = require("ansi") @@ -126,10 +125,8 @@ function util.openRockspecUrl(name, url, branch, commit) local dir, lockEntry if sourceUrl:match("^git") then sourceUrl = util.normalizeGitUrl(sourceUrl) - dir = lde.global.getOrInitGitRepo(name, sourceUrl, branch or sourceTag, commit) - local repo = git2.open(dir) - local sha = repo and repo:revparse("HEAD") - lockEntry = { git = sourceUrl, commit = sha or commit, rockspec = url } + dir, commit = lde.global.getOrInitGitRepo(name, sourceUrl, branch or sourceTag, commit) + lockEntry = { git = sourceUrl, commit = commit, rockspec = url } elseif sourceUrl:match("^https?://") then dir = lde.global.getOrInitArchive(sourceUrl) lockEntry = { archive = sourceUrl, rockspec = url } diff --git a/packages/lde-core/tests/build.test.lua b/packages/lde-core/tests/build.test.lua index a6eaaa5..e7b378e 100644 --- a/packages/lde-core/tests/build.test.lua +++ b/packages/lde-core/tests/build.test.lua @@ -637,14 +637,105 @@ end) -- Regression: make build.variables / install_variables substitution + bin promotion -- -test.skipIf(jit.os == "Windows")( - "rockspec: make build.variables are substituted and passed to make", function() - local dir = path.join(tmpBase, "make-vars-rock") - fs.mkdir(dir) - -- Makefile that writes MY_INCDIR to built.txt on build, then copies it on install. - -- install must NOT depend on build (a phony dep would re-run build with install's vars, - -- overwriting built.txt with an empty MY_INCDIR since it only appears in build.variables). - fs.write(path.join(dir, "Makefile"), [[ +-- +-- lde-build exposed to build scripts via preload +-- + +test.it("build script can require('lde-build') and uses correct outDir", function() + local dir = path.join(tmpBase, "ldebuild-exposed") + fs.mkdir(dir) + fs.mkdir(path.join(dir, "src")) + fs.write(path.join(dir, "src", "init.lua"), 'return true') + fs.write(path.join(dir, "lde.json"), json.encode({ + name = "ldebuild-exposed", + version = "0.1.0", + dependencies = {} + })) + + -- build.lua that uses lde-build to write a file + fs.write(path.join(dir, "build.lua"), [[ +local build = require("lde-build") +build:write("output.txt", "hello from lde-build") +]]) + + local pkg = lde.Package.open(dir) + local outputDir = path.join(dir, "target", pkg:getName()) + local ok, err = pkg:runBuildScript(outputDir) + test.truthy(ok, err) + + local writtenPath = path.join(outputDir, "output.txt") + test.truthy(fs.exists(writtenPath)) + test.equal(fs.read(writtenPath), "hello from lde-build") +end) + +test.it("build script lde-build instance has correct outDir matching LDE_OUTPUT_DIR", function() + local dir = path.join(tmpBase, "ldebuild-outdir") + fs.mkdir(dir) + fs.mkdir(path.join(dir, "src")) + fs.write(path.join(dir, "src", "init.lua"), 'return true') + fs.write(path.join(dir, "lde.json"), json.encode({ + name = "ldebuild-outdir", + version = "0.1.0", + dependencies = {} + })) + + -- build.lua that checks outDir matches LDE_OUTPUT_DIR + fs.write(path.join(dir, "build.lua"), [[ +local build = require("lde-build") +local outputDir = os.getenv("LDE_OUTPUT_DIR") +assert(build.outDir == outputDir, +"outDir mismatch: got " .. tostring(build.outDir) .. " expected " .. tostring(outputDir)) +]]) + + local pkg = lde.Package.open(dir) + local outputDir = path.join(dir, "target", pkg:getName()) + local ok, err = pkg:runBuildScript(outputDir) + test.truthy(ok, err) +end) + +test.it("build script lde-build fetch, write, sh, and read methods work", function() + local dir = path.join(tmpBase, "ldebuild-methods") + fs.mkdir(dir) + fs.mkdir(path.join(dir, "src")) + fs.write(path.join(dir, "src", "init.lua"), 'return true') + fs.write(path.join(dir, "lde.json"), json.encode({ + name = "ldebuild-methods", + version = "0.1.0", + dependencies = {} + })) + + -- build.lua that exercises fetch, write, sh, and read + fs.write(path.join(dir, "build.lua"), [[ +local build = require("lde-build") + +-- write and read +build:write("hello.txt", "world") +local content = build:read("hello.txt") +assert(content == "world", "read/write mismatch: " .. content) + +-- sh should work (echo is available everywhere) +build:sh("echo hello > " .. build.outDir .. "/shell.txt") +local shellContent = build:read("shell.txt") +assert(shellContent:match("hello"), "sh/read mismatch: " .. shellContent) +]]) + + local pkg = lde.Package.open(dir) + local outputDir = path.join(dir, "target", pkg:getName()) + local ok, err = pkg:runBuildScript(outputDir) + test.truthy(ok, err) + + -- Verify end result + test.equal(fs.read(path.join(outputDir, "hello.txt")), "world") + test.truthy(fs.read(path.join(outputDir, "shell.txt")):match("hello")) +end) + +test.skipIf(jit.os == "Windows")("rockspec: make build.variables are substituted and passed to make", function() + local dir = path.join(tmpBase, "make-vars-rock") + fs.mkdir(dir) + -- Makefile that writes MY_INCDIR to built.txt on build, then copies it on install. + -- install must NOT depend on build (a phony dep would re-run build with install's vars, + -- overwriting built.txt with an empty MY_INCDIR since it only appears in build.variables). + fs.write(path.join(dir, "Makefile"), [[ build: echo "$(MY_INCDIR)" > built.txt @@ -655,7 +746,7 @@ install: echo "#!/bin/sh" > $(PREFIX)/bin/myprog chmod 755 $(PREFIX)/bin/myprog ]]) - fs.write(path.join(dir, "make-vars-1.0-1.rockspec"), [[ + fs.write(path.join(dir, "make-vars-1.0-1.rockspec"), [[ package = "make-vars" version = "1.0-1" source = { url = "https://example.com" } @@ -666,25 +757,25 @@ build = { } ]]) - local pkg, err = lde.Package.openRockspec(dir) - test.truthy(pkg, err) + local pkg, err = lde.Package.openRockspec(dir) + test.truthy(pkg, err) - local outputDir = path.join(dir, "target", "make-vars") - local ok, berr = pkg:runBuildScript(outputDir) - test.truthy(ok, berr) + local outputDir = path.join(dir, "target", "make-vars") + local ok, berr = pkg:runBuildScript(outputDir) + test.truthy(ok, berr) - -- vars.txt must exist in modulesDir (= target/) - local modulesDir = path.join(dir, "target") - test.truthy(fs.exists(path.join(modulesDir, "vars.txt"))) + -- vars.txt must exist in modulesDir (= target/) + local modulesDir = path.join(dir, "target") + test.truthy(fs.exists(path.join(modulesDir, "vars.txt"))) - -- vars.txt must contain the LuaJIT include path (substituted from $(LUA_INCDIR)) - local content = fs.read(path.join(modulesDir, "vars.txt")) or "" - test.truthy(content:find("luajit", 1, true) or content:find("include", 1, true)) + -- vars.txt must contain the LuaJIT include path (substituted from $(LUA_INCDIR)) + local content = fs.read(path.join(modulesDir, "vars.txt")) or "" + test.truthy(content:find("luajit", 1, true) or content:find("include", 1, true)) - -- myprog binary must be promoted from target/bin/ into target/make-vars/ - test.truthy(fs.exists(path.join(outputDir, "myprog"))) + -- myprog binary must be promoted from target/bin/ into target/make-vars/ + test.truthy(fs.exists(path.join(outputDir, "myprog"))) - -- readConfig must discover the promoted bin - local cfg = pkg:readConfig() - test.equal(cfg.bin, "myprog") - end) + -- readConfig must discover the promoted bin + local cfg = pkg:readConfig() + test.equal(cfg.bin, "myprog") +end) diff --git a/packages/lde-core/tests/main.test.lua b/packages/lde-core/tests/main.test.lua index 01fb52c..54f7dea 100644 --- a/packages/lde-core/tests/main.test.lua +++ b/packages/lde-core/tests/main.test.lua @@ -369,7 +369,7 @@ test.it("git dep: installs root package, not a sub-package, when repo has lde.js -- Pinning a fake commit in the dep skips the getCommitHash call entirely, -- keeping the test self-contained (no real git repo needed). local fakeCommit = "abc1234567890abcdef1234567890abcdef123456" - local repoDir = lde.global.getGitRepoDir("my-root-pkg", nil, fakeCommit) + local repoDir = lde.global.getGitRepoDir("my-root-pkg", fakeCommit) fs.rmdir(repoDir) fs.mkdir(repoDir) diff --git a/packages/lde/tests/main.test.lua b/packages/lde/tests/main.test.lua index c210a46..3b092d5 100644 --- a/packages/lde/tests/main.test.lua +++ b/packages/lde/tests/main.test.lua @@ -11,11 +11,15 @@ local lde = require("lde-core") local ldecli = require("tests.lib.ldecli") test.it("should not ignore --git in ldx", function() - -- Pre-populate the git cache so no real clone happens - local repoDir = lde.global.getGitRepoDir("hood") + local cloneUrl = "https://github.com/codebycruz/hood" + + -- Resolve the real commit so the cache key matches what getOrCloneRepo expects. + local commit = assert(git2.lsRemote(cloneUrl, "HEAD")) + + -- Pre-populate the cache with a fake repo that lacks a "triangle" package. + local repoDir = lde.global.getGitRepoDir("hood", commit) fs.rmdir(repoDir) fs.mkdir(repoDir) - git2.init(repoDir, true) fs.write(path.join(repoDir, "lde.json"), json.encode({ name = "hood", version = "1.0.0", @@ -24,7 +28,7 @@ test.it("should not ignore --git in ldx", function() fs.mkdir(path.join(repoDir, "src")) fs.write(path.join(repoDir, "src", "init.lua"), "") - local _, out = ldecli { "x", "triangle", "--git", "https://github.com/codebycruz/hood" } + local _, out = ldecli { "x", "triangle", "--git", cloneUrl } test.falsy(out:find("not found in lde registry")) test.includes(out, "No package named 'triangle'")