UNPKG

renovate

Version:

Automated dependency updates. Flexible so you don't need to be.

284 lines (283 loc) • 12.6 kB
import "../../../constants/error-messages.js"; import { getEnv } from "../../../util/env.js"; import { regEx } from "../../../util/regex.js"; import { GlobalConfig } from "../../../config/global.js"; import { logger } from "../../../logger/index.js"; import { coerceArray } from "../../../util/array.js"; import { ensureCacheDir, findLocalSiblingOrParent, isValidLocalPath, readLocalFile, writeLocalFile } from "../../../util/fs/index.js"; import { filterMap } from "../../../util/filter-map.js"; import { isVersion } from "../../versioning/semver/index.js"; import { getGitEnvironmentVariables } from "../../../util/git/auth.js"; import { exec } from "../../../util/exec/index.js"; import { getRepoStatus } from "../../../util/git/index.js"; import { getExtraDepsNotice } from "./artifacts-extra.js"; import { isString } from "@sindresorhus/is"; import { quote } from "shlex"; import upath from "upath"; import semver from "semver"; //#region lib/modules/manager/gomod/artifacts.ts const { major: major$1, valid: valid$1 } = semver; function getUpdateImportPathCmds(updatedDeps, { constraints }) { const invalidMajorDeps = updatedDeps.filter(({ newVersion }) => !valid$1(newVersion)); if (invalidMajorDeps.length > 0) invalidMajorDeps.forEach(({ depName }) => logger.warn({ depName }, "Ignoring dependency: Could not get major version")); const updateImportCommands = updatedDeps.filter(({ newVersion }) => valid$1(newVersion) && !newVersion.endsWith("+incompatible")).map(({ depName, newVersion }) => ({ depName, newMajor: major$1(newVersion) })).filter(({ depName, newMajor }) => depName.startsWith("gopkg.in/") || newMajor > 1).map(({ depName, newMajor }) => `mod upgrade --mod-name=${depName} -t=${newMajor}`); if (updateImportCommands.length > 0) { let installMarwanModArgs = "install github.com/marwan-at-work/mod/cmd/mod@latest"; const gomodModCompatibility = constraints?.gomodMod; if (gomodModCompatibility) if (gomodModCompatibility.startsWith("v") && isVersion(gomodModCompatibility.replace(regEx(/^v/), ""))) installMarwanModArgs = installMarwanModArgs.replace(regEx(/@latest$/), `@${gomodModCompatibility}`); else logger.debug({ gomodModCompatibility }, "marwan-at-work/mod compatibility range is not valid - skipping"); else logger.debug("No marwan-at-work/mod compatibility range found - installing marwan-at-work/mod latest"); updateImportCommands.unshift(`go ${installMarwanModArgs}`); } return updateImportCommands; } function useModcacherw(goVersion) { if (!isString(goVersion)) return true; return semver.intersects(goVersion, `>=1.14`); } async function updateArtifacts({ packageFileName: goModFileName, updatedDeps, newPackageFileContent: newGoModContent, config }) { logger.debug(`gomod.updateArtifacts(${goModFileName})`); const sumFileName = goModFileName.replace(regEx(/\.mod$/), ".sum"); if (!await readLocalFile(sumFileName)) { logger.debug("No go.sum found"); return null; } const goModDir = upath.dirname(goModFileName); const goModFileBaseName = upath.basename(goModFileName); const modFileFlag = goModFileBaseName === "go.mod" ? "" : ` -modfile=${quote(goModFileBaseName)}`; const vendorDir = await findLocalSiblingOrParent(goModFileName, "vendor"); const vendorModulesFileName = upath.join(vendorDir ?? "", "modules.txt"); const useVendor = !!config.postUpdateOptions?.includes("gomodVendor") || !config.postUpdateOptions?.includes("gomodSkipVendor") && vendorDir && await readLocalFile(vendorModulesFileName) !== null; let massagedGoMod = newGoModContent; const useGoGenerate = !!config.postUpdateOptions?.includes("goGenerate"); const goGenerateAllowed = GlobalConfig.get("allowedUnsafeExecutions")?.includes("goGenerate"); if (config.postUpdateOptions?.includes("gomodMassage")) { massagedGoMod = massagedGoMod.split("\n").map((line) => { if (line.trim().startsWith("//")) return line.replace(")", "renovate-replace-bracket"); return line; }).join("\n"); const inlineReplaceRegEx = regEx(/(\r?\n)(replace\s+[^\s]+\s+=>\s+\.\.\/.*)/g); const inlineCommentOut = "$1// renovate-replace $2"; const blockReplaceRegEx = regEx(/(\r?\n)replace\s*\([^)]+\s*\)/g); /** * replacerFunction for commenting out replace blocks * @param match A string representing a golang replace directive block * @returns A commented out block with // renovate-replace */ const blockCommentOut = (match) => match.replace(/(\r?\n)/g, "$1// renovate-replace "); massagedGoMod = massagedGoMod.replace(inlineReplaceRegEx, inlineCommentOut).replace(blockReplaceRegEx, blockCommentOut); if (massagedGoMod !== newGoModContent) logger.debug("Removed some relative replace statements and comments from go.mod"); } const goConstraints = deriveGoToolchainConstraints(config, newGoModContent); try { await writeLocalFile(goModFileName, massagedGoMod); const cmd = "go"; const env = getEnv(); const execOptions = { cwdFile: goModFileName, extraEnv: { GOPATH: await ensureCacheDir("go"), GOPROXY: env.GOPROXY, GOPRIVATE: env.GOPRIVATE, GONOPROXY: env.GONOPROXY, GONOSUMDB: env.GONOSUMDB, GOSUMDB: env.GOSUMDB, GOINSECURE: env.GOINSECURE, /* v8 ignore next -- TODO: add test */ GOFLAGS: useModcacherw(goConstraints) ? "-modcacherw" : null, CGO_ENABLED: GlobalConfig.get("binarySource") === "docker" ? "0" : null, ...getGitEnvironmentVariables(["go"]) }, docker: {}, toolConstraints: [{ toolName: "golang", constraint: goConstraints }] }; const execCommands = []; let goGetDirs; if (config.goGetDirs) { goGetDirs = config.goGetDirs.filter((dir) => { const isValid = isValidLocalPath(dir); if (!isValid) logger.warn({ dir }, "Invalid path in goGetDirs"); return isValid; }).map(quote).join(" "); if (goGetDirs === "") throw new Error("Invalid goGetDirs"); } let args = `get${modFileFlag} `; if (goConstraints && !semver.intersects(goConstraints, `>=1.18`)) args += `-d `; args += `-t ${goGetDirs ?? "./..."}`; logger.trace({ cmd, args }, "go get command included"); execCommands.push(`${cmd} ${args}`); const isImportPathUpdateRequired = config.postUpdateOptions?.includes("gomodUpdateImportPaths") && config.updateType === "major"; if (isImportPathUpdateRequired) { const updateImportCmds = getUpdateImportPathCmds(updatedDeps, config); if (updateImportCmds.length > 0) { logger.debug(updateImportCmds, "update import path commands included"); execCommands.push(...updateImportCmds); } } const mustSkipGoModTidy = !config.postUpdateOptions?.includes("gomodUpdateImportPaths") && config.updateType === "major"; if (mustSkipGoModTidy) logger.debug("go mod tidy command skipped"); let tidyOpts = ""; if (config.postUpdateOptions?.includes("gomodTidy1.17")) tidyOpts += " -compat=1.17"; if (config.postUpdateOptions?.includes("gomodTidyE")) tidyOpts += " -e"; const isGoModTidyRequired = !mustSkipGoModTidy && (config.postUpdateOptions?.includes("gomodTidy") === true || config.postUpdateOptions?.includes("gomodTidy1.17") === true || config.postUpdateOptions?.includes("gomodTidyE") === true || config.updateType === "major" && isImportPathUpdateRequired); if (isGoModTidyRequired) { args = `mod tidy${modFileFlag}${tidyOpts}`; logger.debug("go mod tidy command included"); execCommands.push(`${cmd} ${args}`); } let goWorkSumFileName = upath.join(goModDir, "go.work.sum"); if (useVendor) { const goWorkFile = await findLocalSiblingOrParent(goModFileName, "go.work"); if (goWorkFile) { goWorkSumFileName = upath.join(upath.dirname(goWorkFile), "go.work.sum"); args = "work vendor"; logger.debug("using go work vendor"); execCommands.push(`${cmd} ${args}`); args = "work sync"; logger.debug("using go work sync"); execCommands.push(`${cmd} ${args}`); } else { args = `mod vendor${modFileFlag}`; logger.debug("using go mod vendor"); execCommands.push(`${cmd} ${args}`); } if (isGoModTidyRequired) { args = `mod tidy${modFileFlag}${tidyOpts}`; logger.debug("go mod tidy command included"); execCommands.push(`${cmd} ${args}`); } } if (isGoModTidyRequired) { args = `mod tidy${modFileFlag}${tidyOpts}`; logger.debug("go mod tidy command included"); execCommands.push(`${cmd} ${args}`); } if (useGoGenerate) if (goGenerateAllowed) { logger.debug("go generate command included"); execCommands.push(`${cmd} generate ./...`); } else logger.once.warn(`go generate command requested as a post update action, but goGenerate is not permitted in the allowedUnsafeExecutions`); await exec(execCommands, execOptions); const status = await getRepoStatus(); if (!status.modified.includes(sumFileName) && !status.modified.includes(goModFileName) && !status.modified.includes(goWorkSumFileName)) return null; const res = []; if (status.modified.includes(sumFileName)) { logger.debug("Returning updated go.sum"); res.push({ file: { type: "addition", path: sumFileName, contents: await readLocalFile(sumFileName) } }); } if (status.modified.includes(goWorkSumFileName)) { logger.debug("Returning updated go.work.sum"); res.push({ file: { type: "addition", path: goWorkSumFileName, contents: await readLocalFile(goWorkSumFileName) } }); } if (isImportPathUpdateRequired) { logger.debug("Returning updated go source files for import path changes"); for (const f of status.modified) if (f.endsWith(".go")) res.push({ file: { type: "addition", path: f, contents: await readLocalFile(f) } }); } const alreadyAdded = /* @__PURE__ */ new Set(); const alreadyDeleted = /* @__PURE__ */ new Set(); if (useVendor) { for (const f of status.modified.concat(status.not_added)) if (vendorDir && f.startsWith(vendorDir)) { alreadyAdded.add(f); res.push({ file: { type: "addition", path: f, contents: await readLocalFile(f) } }); } for (const f of coerceArray(status.deleted)) if (vendorDir && f.startsWith(vendorDir)) { alreadyDeleted.add(f); res.push({ file: { type: "deletion", path: f } }); } } const finalGoModContent = (await readLocalFile(goModFileName, "utf8")).replace(regEx(/\/\/ renovate-replace /g), "").replace(regEx(/renovate-replace-bracket/g), ")"); if (finalGoModContent !== newGoModContent) { const artifactResult = { file: { type: "addition", path: goModFileName, contents: finalGoModContent } }; const extraDepsNotice = getExtraDepsNotice(newGoModContent, finalGoModContent, filterMap(updatedDeps, (dep) => dep?.depName)); if (extraDepsNotice) artifactResult.notice = { file: goModFileName, message: extraDepsNotice }; logger.debug("Found updated go.mod after go.sum update"); res.push(artifactResult); alreadyAdded.add(goModFileName); } if (useGoGenerate && goGenerateAllowed) { logger.debug("Updating all modified files since generated files were added"); for (const f of status.modified.concat(status.created)) if (!alreadyAdded.has(f)) res.push({ file: { type: "addition", path: f, contents: await readLocalFile(f) } }); for (const f of coerceArray(status.deleted)) if (!alreadyDeleted.has(f)) res.push({ file: { type: "deletion", path: f } }); } return res; } catch (err) { if (err.message === "temporary-error") throw err; logger.debug({ err }, "Failed to update go.sum"); return [{ artifactError: { fileName: sumFileName, stderr: err.message } }]; } } function getGoConstraints(content) { const toolchainVer = regEx(/^toolchain\s*go(?<gover>\d+\.\d+\.\d+)$/m).exec(content)?.groups?.gover; if (toolchainVer) { logger.debug(`Using go version ${toolchainVer} found in toolchain directive`); return toolchainVer; } const goFullVersion = regEx(/^go\s*(?<gover>\d+\.\d+\.\d+)$/m).exec(content)?.groups?.gover; if (goFullVersion) return goFullVersion; const match = regEx(/^go\s*(?<gover>\d+\.\d+)$/m).exec(content); if (!match?.groups?.gover) return; return `^${match.groups.gover}`; } /** * Derive the version of the Go toolchain needed to run this project. * * This matches with the `golang` Containerbase tool. * * In precedence order: * * 1. config: \`constraints.go\` * 1. \`go.mod\`: \`toolchain\` directive * 1. \`go.mod\`: \`go\` directive * * NOTE that the \`constraints.golang\` is not used (TODO #42601) */ function deriveGoToolchainConstraints(config, newGoModContent) { return config.constraints?.go ?? getGoConstraints(newGoModContent); } //#endregion export { updateArtifacts }; //# sourceMappingURL=artifacts.js.map