From 2b0d93e1710987dd6b54939510f7738e2896bc9e Mon Sep 17 00:00:00 2001 From: Jon Edvald Date: Sat, 30 Jun 2018 15:01:37 +0000 Subject: [PATCH] fix: fixed more issues with cross-repo versioning Turns out I missed a beat on the last fix. Hopefully this is the rest of it. --- gulpfile.ts | 64 ++++++------------- src/plugin-context.ts | 33 +--------- src/vcs/base.ts | 34 +++++++++- src/vcs/git.ts | 14 ++++ garden.yml => static/garden.yml | 0 .../test-project-a/module-a/.garden-version | 5 +- test/src/plugin-context.ts | 11 ---- test/src/vcs/base.ts | 55 +++++++++------- 8 files changed, 103 insertions(+), 113 deletions(-) rename garden.yml => static/garden.yml (100%) diff --git a/gulpfile.ts b/gulpfile.ts index 335531c36d..a5b3972a4e 100644 --- a/gulpfile.ts +++ b/gulpfile.ts @@ -2,9 +2,6 @@ import { spawn as _spawn, ChildProcess, } from "child_process" -import { - writeFileSync, -} from "fs" import { ensureDir, pathExists, @@ -13,12 +10,14 @@ import { writeFile, } from "fs-extra" import * as handlebars from "handlebars" -import { - join, - relative, -} from "path" +import { join } from "path" import { generateDocs } from "./src/docs/generate" import { getUrlChecksum } from "./support/support-util" +import * as Bluebird from "bluebird" +import { GitHandler } from "./src/vcs/git" +import { Garden } from "./src/garden" +import { RootLogNode } from "./src/logger/logger" +import { LogLevel } from "./src/logger/types" import execa = require("execa") const gulp = require("gulp") @@ -49,12 +48,6 @@ const licenseHeaderPath = "support/license-header.txt" const destDir = "build" -class TaskError extends Error { - toString() { - return this.message - } -} - const children: ChildProcess[] = [] process.env.FORCE_COLOR = "true" @@ -89,37 +82,25 @@ function die() { process.on("SIGINT", die) process.on("SIGTERM", die) -gulp.task("add-version-files", (cb) => { - const gardenBinPath = join("static", "bin", "garden") - const proc = _spawn("node", [gardenBinPath, "scan", "--output=json"]) +// make sure logger is initialized +try { + RootLogNode.initialize({ level: LogLevel.info }) +} catch (_) { } - proc.on("error", err => cb(err)) +gulp.task("add-version-files", async () => { + const staticPath = join(__dirname, "static") + const garden = await Garden.factory(staticPath) - let output = "" - let outputWithError = "" - proc.stdout.on("data", d => { - output += d - outputWithError += d - }) - proc.stderr.on("data", d => outputWithError += d) + const modules = await garden.getModules() - proc.on("close", () => { - let results - try { - results = JSON.parse(output) - } catch { - const msg = "Got unexpected output from `garden scan`" - console.error(msg + "\n" + outputWithError) - return cb(msg) - } + return Bluebird.map(modules, async (module) => { + const path = module.path + const versionFilePath = join(path, ".garden-version") - for (const module of results.result) { - const relPath = relative(__dirname, module.path) - const versionFilePath = join(__dirname, relPath, ".garden-version") - writeFileSync(versionFilePath, JSON.stringify(module.version)) - } + const vcsHandler = new GitHandler(path) + const treeVersion = await vcsHandler.getTreeVersion([path]) - cb() + await writeFile(versionFilePath, JSON.stringify(treeVersion, null, 4) + "\n") }) }) @@ -266,10 +247,7 @@ gulp.task("watch-code", () => { }) gulp.task("lint", gulp.parallel("check-licenses", "tslint", "tslint-tests", "tsfmt")) -gulp.task("build", gulp.series( - gulp.parallel("generate-docs", "pegjs", "tsc"), - "add-version-files", -)) +gulp.task("build", gulp.parallel("add-version-files", "generate-docs", "pegjs", "tsc")) gulp.task("test", gulp.parallel("build", "lint", "mocha")) gulp.task("watch", gulp.series( "build", diff --git a/src/plugin-context.ts b/src/plugin-context.ts index 0512cb7793..1391bcb61f 100644 --- a/src/plugin-context.ts +++ b/src/plugin-context.ts @@ -8,24 +8,15 @@ import Bluebird = require("bluebird") import chalk from "chalk" -import { - pathExists, - readFile, -} from "fs-extra" -import { join } from "path" import { CacheContext } from "./cache" -import { GARDEN_VERSIONFILE_NAME } from "./constants" -import { ConfigurationError } from "./exceptions" import { Garden, } from "./garden" import { EntryStyle } from "./logger/types" import { PrimitiveMap, - validate, } from "./types/common" import { Module } from "./types/module" -import { moduleVersionSchema } from "./vcs/base" import { ModuleActions, Provider, @@ -482,29 +473,7 @@ export function createPluginContext(garden: Garden): PluginContext { const dependencies = await garden.getModules(moduleDependencies) const cacheContexts = dependencies.concat([module]).map(m => m.getCacheContext()) - // the version file is used internally to specify versions outside of source control - const versionFilePath = join(module.path, GARDEN_VERSIONFILE_NAME) - const versionFileContents = await pathExists(versionFilePath) - && (await readFile(versionFilePath)).toString().trim() - - let version: ModuleVersion - - if (!!versionFileContents) { - try { - version = validate(JSON.parse(versionFileContents), moduleVersionSchema) - } catch (err) { - throw new ConfigurationError( - `Unable to parse ${GARDEN_VERSIONFILE_NAME} as valid version file in module directory ${module.path}`, - { - modulePath: module.path, - versionFilePath, - versionFileContents, - }, - ) - } - } else { - version = await garden.vcs.resolveVersion(module, dependencies) - } + const version = await garden.vcs.resolveVersion(module, dependencies) garden.cache.set(cacheKey, version, ...cacheContexts) return version diff --git a/src/vcs/base.ts b/src/vcs/base.ts index c5c794a3d9..91e167eafa 100644 --- a/src/vcs/base.ts +++ b/src/vcs/base.ts @@ -11,6 +11,11 @@ import * as Bluebird from "bluebird" import { mapValues, keyBy, sortBy, orderBy, omit } from "lodash" import { createHash } from "crypto" import * as Joi from "joi" +import { validate } from "../types/common" +import { join } from "path" +import { GARDEN_VERSIONFILE_NAME } from "../constants" +import { pathExists, readFile } from "fs-extra" +import { ConfigurationError } from "../exceptions" export const NEW_MODULE_VERSION = "0000000000" @@ -63,11 +68,38 @@ export const moduleVersionSchema = Joi.object() export abstract class VcsHandler { constructor(protected projectRoot: string) { } + abstract name: string abstract async getTreeVersion(paths: string[]): Promise + async resolveTreeVersion(module: Module): Promise { + // the version file is used internally to specify versions outside of source control + const versionFilePath = join(module.path, GARDEN_VERSIONFILE_NAME) + const versionFileContents = await pathExists(versionFilePath) + && (await readFile(versionFilePath)).toString().trim() + + if (!!versionFileContents) { + try { + return validate(JSON.parse(versionFileContents), treeVersionSchema) + } catch (err) { + throw new ConfigurationError( + `Unable to parse ${GARDEN_VERSIONFILE_NAME} as valid version file in module directory ${module.path}`, + { + modulePath: module.path, + versionFilePath, + versionFileContents, + }, + ) + } + } else { + return this.getTreeVersion([module.path]) + } + } + async resolveVersion(module: Module, dependencies: Module[]): Promise { const treeVersion = await this.getTreeVersion([module.path]) + validate(treeVersion, treeVersionSchema, { context: `${this.name} tree version for module at ${module.path}` }) + if (dependencies.length === 0) { return { versionString: treeVersion.latestCommit, @@ -78,7 +110,7 @@ export abstract class VcsHandler { const namedDependencyVersions = await Bluebird.map( dependencies, - async (m: Module) => ({ name: m.name, ...await this.getTreeVersion([m.path]) }), + async (m: Module) => ({ name: m.name, ...await this.resolveTreeVersion(m) }), ) const dependencyVersions = mapValues(keyBy(namedDependencyVersions, "name"), v => omit(v, "name")) diff --git a/src/vcs/git.ts b/src/vcs/git.ts index ec9d5a2edb..578271358e 100644 --- a/src/vcs/git.ts +++ b/src/vcs/git.ts @@ -11,9 +11,12 @@ import { NEW_MODULE_VERSION, TreeVersion, VcsHandler } from "./base" import { join } from "path" import { sortBy } from "lodash" import { pathExists, stat } from "fs-extra" +import { argv } from "process" import Bluebird = require("bluebird") export class GitHandler extends VcsHandler { + name = "git" + private repoRoot: string async getTreeVersion(directories: string[]) { @@ -118,3 +121,14 @@ export class GitHandler extends VcsHandler { return exec("git " + args, { cwd }) } } + +// used by the build process to resolve and store the tree version for plugin modules +if (require.main === module) { + const path = argv[2] + const handler = new GitHandler(path) + + handler.getTreeVersion([path]) + .then((treeVersion) => { + console.log(JSON.stringify(treeVersion, null, 4)) + }) +} diff --git a/garden.yml b/static/garden.yml similarity index 100% rename from garden.yml rename to static/garden.yml diff --git a/test/data/test-project-a/module-a/.garden-version b/test/data/test-project-a/module-a/.garden-version index fbce17f2a0..1d754b7141 100644 --- a/test/data/test-project-a/module-a/.garden-version +++ b/test/data/test-project-a/module-a/.garden-version @@ -1,5 +1,4 @@ { - "versionString": "1234567890", - "dirtyTimestamp": null, - "dependencyVersions": {} + "latestCommit": "1234567890", + "dirtyTimestamp": null } diff --git a/test/src/plugin-context.ts b/test/src/plugin-context.ts index 9178d4bd42..d1b0f7a127 100644 --- a/test/src/plugin-context.ts +++ b/test/src/plugin-context.ts @@ -125,17 +125,6 @@ describe("PluginContext", () => { expect(result).to.eql(version) }) - it("should return version from version file if it exists", async () => { - const module = await ctx.getModule("module-a") - const result = await ctx.resolveVersion("module-a", []) - - expect(result).to.eql({ - versionString: "1234567890", - dirtyTimestamp: null, - dependencyVersions: {}, - }) - }) - it("should otherwise return version from VCS handler", async () => { const resolve = td.replace(garden.vcs, "resolveVersion") const version: ModuleVersion = { diff --git a/test/src/vcs/base.ts b/test/src/vcs/base.ts index 967e79e78c..666046f489 100644 --- a/test/src/vcs/base.ts +++ b/test/src/vcs/base.ts @@ -4,13 +4,12 @@ import { PluginContext } from "../../../src/plugin-context" import { expect } from "chai" class TestVcsHandler extends VcsHandler { + name = "test" private testVersions: TreeVersions = {} async getTreeVersion(paths: string[]) { - const versionString = NEW_MODULE_VERSION return this.testVersions[paths[0]] || { - versionString, - latestCommit: versionString, + latestCommit: NEW_MODULE_VERSION, dirtyTimestamp: null, } } @@ -29,12 +28,36 @@ describe("VcsHandler", () => { ctx = await makeTestContextA() }) + describe("resolveTreeVersion", () => { + it("should return the version from a version file if it exists", async () => { + const module = await ctx.getModule("module-a") + const result = await handler.resolveTreeVersion(module) + + expect(result).to.eql({ + latestCommit: "1234567890", + dirtyTimestamp: null, + }) + }) + + it("should call getTreeVersion if there is no version file", async () => { + const module = await ctx.getModule("module-b") + + const version = { + latestCommit: "qwerty", + dirtyTimestamp: 456, + } + handler.setTestVersion(module.path, version) + + const result = await handler.resolveTreeVersion(module) + expect(result).to.eql(version) + }) + }) + describe("resolveVersion", () => { it("should return module version if there are no dependencies", async () => { const module = await ctx.getModule("module-a") const versionString = "abcdef" const version = { - versionString, latestCommit: versionString, dirtyTimestamp: null, } @@ -53,16 +76,13 @@ describe("VcsHandler", () => { it("should return the latest dirty version if any", async () => { const [moduleA, moduleB, moduleC] = await ctx.getModules(["module-a", "module-b", "module-c"]) - const versionStringA = "abcdef" + // note: module-a has a version file const versionA = { - versionString: versionStringA, - latestCommit: versionStringA, + latestCommit: "1234567890", dirtyTimestamp: null, } - handler.setTestVersion(moduleA.path, versionA) const versionB = { - versionString: "qwerty-456", latestCommit: "qwerty", dirtyTimestamp: 456, } @@ -70,7 +90,6 @@ describe("VcsHandler", () => { const versionStringC = "asdfgh" const versionC = { - versionString: versionStringC, latestCommit: versionStringC, dirtyTimestamp: 123, } @@ -89,17 +108,13 @@ describe("VcsHandler", () => { it("should hash together the version of the module and all dependencies if none are dirty", async () => { const [moduleA, moduleB, moduleC] = await ctx.getModules(["module-a", "module-b", "module-c"]) - const versionStringA = "abcdef" const versionA = { - versionString: versionStringA, - latestCommit: versionStringA, + latestCommit: "1234567890", dirtyTimestamp: null, } - handler.setTestVersion(moduleA.path, versionA) const versionStringB = "qwerty" const versionB = { - versionString: versionStringB, latestCommit: versionStringB, dirtyTimestamp: null, } @@ -107,14 +122,13 @@ describe("VcsHandler", () => { const versionStringC = "asdfgh" const versionC = { - versionString: versionStringC, latestCommit: versionStringC, dirtyTimestamp: null, } handler.setTestVersion(moduleC.path, versionC) expect(await handler.resolveVersion(moduleC, [moduleA, moduleB])).to.eql({ - versionString: "vfd75ce5f36", + versionString: "v5ff3a146d9", dirtyTimestamp: null, dependencyVersions: { "module-a": versionA, @@ -126,17 +140,13 @@ describe("VcsHandler", () => { it("should hash together the dirty versions if there are multiple with same timestamp", async () => { const [moduleA, moduleB, moduleC] = await ctx.getModules(["module-a", "module-b", "module-c"]) - const versionStringA = "abcdef" const versionA = { - versionString: versionStringA, - latestCommit: versionStringA, + latestCommit: "1234567890", dirtyTimestamp: null, } - handler.setTestVersion(moduleA.path, versionA) const versionStringB = "qwerty" const versionB = { - versionString: versionStringB, latestCommit: versionStringB, dirtyTimestamp: 1234, } @@ -144,7 +154,6 @@ describe("VcsHandler", () => { const versionStringC = "asdfgh" const versionC = { - versionString: versionStringC, latestCommit: versionStringC, dirtyTimestamp: 1234, }