Skip to content

Commit f713e59

Browse files
gatsbybotJGAntunes
andauthored
fix: re-install lmdb when detecting the absence of @LMDB prebuilt packages (#38691) (#38707)
* fix: re-install lmdb when detecting the absence of @LMDB prebuilt packages * chore: set the correct root lmdb and add further comments * chore: linter * chore: linting * chore: make the install check for reliable and install @LMDB instead of lmdb * fix: no need to touch the patch, we can leverage the force binary flag * chore: add integration test for lmdb fix * chore: add CCI config for new integration test * add integration_tests_esm_in_gatsby_files to jobs * fix: integration test for lmdb-regeneration * fix: cli path in test * chore: actually add lmdb's dist directory * fix: back to the npmrc and just rm -rf @LMDB modules --------- Co-authored-by: Michal Piechowiak <misiek.piechowiak@gmail.com> (cherry picked from commit e3365ab) Co-authored-by: João Antunes <me@jgantunes.com>
1 parent c5ec678 commit f713e59

File tree

13 files changed

+299
-3
lines changed

13 files changed

+299
-3
lines changed

.circleci/config.yml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -313,6 +313,13 @@ jobs:
313313
test_path: integration-tests/esm-in-gatsby-files
314314
test_command: yarn test
315315

316+
integration_tests_lmdb_regeneration:
317+
executor: node
318+
steps:
319+
- e2e-test:
320+
test_path: integration-tests/lmdb-regeneration
321+
test_command: yarn test
322+
316323
e2e_tests_path-prefix:
317324
<<: *e2e-executor
318325
steps:
@@ -590,6 +597,8 @@ workflows:
590597
<<: *e2e-test-workflow
591598
- integration_tests_esm_in_gatsby_files:
592599
<<: *e2e-test-workflow
600+
- integration_tests_lmdb_regeneration:
601+
<<: *e2e-test-workflow
593602
- integration_tests_gatsby_cli:
594603
requires:
595604
- bootstrap
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
__tests__/__debug__
2+
node_modules
3+
yarn.lock
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
build_from_source=true
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2018 gatsbyjs
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
## Artifacts test suite
2+
3+
This integration test suite helps us assert our mechanism to heal back from a broken lmdb installation is working by validating we install and use the correct prebundled binary for our platform
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
const path = require(`path`)
2+
const execa = require(`execa`)
3+
const mod = require("module")
4+
const fs = require(`fs-extra`)
5+
6+
jest.setTimeout(100000)
7+
8+
const rootPath = path.resolve(__dirname, "../")
9+
10+
describe(`Lmdb regeneration`, () => {
11+
test(`gatbsy build detects lmdb setup built from source and installs pre-buit package`, async () => {
12+
const lmdbNodeModulesPath = path.resolve(rootPath, "node_modules", "lmdb")
13+
// Make sure we clear out the current `@lmdb` optional dependencies
14+
const pathsToRemove = [
15+
path.resolve(rootPath, "node_modules", "@lmdb"),
16+
path.resolve(rootPath, "node_modules", "gatsby", "node_modules", "@lmdb"),
17+
]
18+
for (let path of pathsToRemove) {
19+
fs.rmSync(path, { force: true, recursive: true })
20+
}
21+
// Check the lmdb instance we have installed does have a binary built from source since we need it to reproduce the fix we're trying to test
22+
// If this check fails then it means our fixture is wrong and we're relying on an lmdb instance with prebuilt binaries
23+
const builtFromSource = fs.existsSync(
24+
path.resolve(lmdbNodeModulesPath, "build", "Release", "lmdb.node")
25+
)
26+
expect(builtFromSource).toEqual(true)
27+
28+
const options = {
29+
stderr: `inherit`,
30+
stdout: `inherit`,
31+
cwd: rootPath,
32+
}
33+
const gatsbyBin = path.resolve(rootPath, `node_modules`, `gatsby`, `cli.js`)
34+
await execa(gatsbyBin, [`build`], options)
35+
36+
// lmdb module with prebuilt binaries for our platform
37+
const lmdbPackage = `@lmdb/lmdb-${process.platform}-${process.arch}`
38+
39+
// If the fix worked correctly we should have installed the prebuilt binary for our platform under our `.cache` directory
40+
const lmdbRequire = mod.createRequire(
41+
path.resolve(rootPath, ".cache", "internal-packages", "package.json")
42+
)
43+
expect(() => {
44+
lmdbRequire.resolve(lmdbPackage)
45+
}).not.toThrow()
46+
47+
// The resulting query-engine bundle should not contain the binary built from source
48+
const binaryBuiltFromSource = path.resolve(
49+
rootPath,
50+
".cache",
51+
"query-engine",
52+
"assets",
53+
"build",
54+
"Release",
55+
"lmdb.node"
56+
)
57+
expect(fs.existsSync(binaryBuiltFromSource)).toEqual(false)
58+
})
59+
})
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
module.exports = {
2+
siteMetadata: {
3+
siteUrl: `https://www.yourdomain.tld`,
4+
},
5+
plugins: [],
6+
flags: {
7+
DEV_SSR: true,
8+
},
9+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
module.exports = {
2+
testPathIgnorePatterns: [`/node_modules/`, `__tests__/fixtures`, `.cache`],
3+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
{
2+
"name": "lmdb-regeneration",
3+
"private": true,
4+
"author": "Sid Chatterjee",
5+
"description": "A simplified bare-bones starter for Gatsby with DSG",
6+
"version": "0.1.0",
7+
"license": "MIT",
8+
"scripts": {
9+
"build": "gatsby build",
10+
"clean": "gatsby clean",
11+
"test": "jest --runInBand"
12+
},
13+
"dependencies": {
14+
"gatsby": "next",
15+
"react": "^18.2.0",
16+
"react-dom": "^18.2.0"
17+
},
18+
"devDependencies": {
19+
"fs-extra": "^11.1.0",
20+
"jest": "^29.3.1"
21+
}
22+
}
Loading
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
import * as React from "react"
2+
import { Link } from "gatsby"
3+
4+
const pageStyles = {
5+
color: "#232129",
6+
padding: "96px",
7+
fontFamily: "-apple-system, Roboto, sans-serif, serif",
8+
}
9+
const headingStyles = {
10+
marginTop: 0,
11+
marginBottom: 64,
12+
maxWidth: 320,
13+
}
14+
15+
const paragraphStyles = {
16+
marginBottom: 48,
17+
}
18+
const codeStyles = {
19+
color: "#8A6534",
20+
padding: 4,
21+
backgroundColor: "#FFF4DB",
22+
fontSize: "1.25rem",
23+
borderRadius: 4,
24+
}
25+
26+
const NotFoundPage = () => {
27+
return (
28+
<main style={pageStyles}>
29+
<h1 style={headingStyles}>Page not found</h1>
30+
<p style={paragraphStyles}>
31+
Sorry 😔, we couldn’t find what you were looking for.
32+
<br />
33+
{process.env.NODE_ENV === "development" ? (
34+
<>
35+
<br />
36+
Try creating a page in <code style={codeStyles}>src/pages/</code>.
37+
<br />
38+
</>
39+
) : null}
40+
<br />
41+
<Link to="/">Go home</Link>.
42+
</p>
43+
</main>
44+
)
45+
}
46+
47+
export default NotFoundPage
48+
49+
export const Head = () => <title>Not found</title>
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import * as React from "react";
2+
3+
const DSG = () => {
4+
return <h1>DSG</h1>;
5+
};
6+
7+
export default DSG;
8+
9+
export const Head = () => <title>DSG</title>;
10+
11+
export async function config() {
12+
return () => {
13+
return {
14+
defer: true,
15+
};
16+
};
17+
}

packages/gatsby/src/schema/graphql-engine/bundle-webpack.ts

Lines changed: 103 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,10 @@
22

33
import * as path from "path"
44
import * as fs from "fs-extra"
5+
import execa, { Options as ExecaOptions } from "execa"
56
import webpack, { Module, NormalModule, Compilation } from "webpack"
67
import ConcatenatedModule from "webpack/lib/optimize/ConcatenatedModule"
8+
import { dependencies } from "gatsby/package.json"
79
import { printQueryEnginePlugins } from "./print-plugins"
810
import mod from "module"
911
import { WebpackLoggingPlugin } from "../../utils/webpack/plugins/webpack-logging"
@@ -12,6 +14,7 @@ import { schemaCustomizationAPIs } from "./print-plugins"
1214
import type { GatsbyNodeAPI } from "../../redux/types"
1315
import * as nodeApis from "../../utils/api-node-docs"
1416
import { store } from "../../redux"
17+
import { PackageJson } from "../../.."
1518

1619
type Reporter = typeof reporter
1720

@@ -35,6 +38,92 @@ function getApisToRemoveForQueryEngine(): Array<GatsbyNodeAPI> {
3538
return apisToRemove
3639
}
3740

41+
const getInternalPackagesCacheDir = (): string =>
42+
path.join(process.cwd(), `.cache/internal-packages`)
43+
44+
// Create a directory and JS module where we install internally used packages
45+
const createInternalPackagesCacheDir = async (): Promise<void> => {
46+
const cacheDir = getInternalPackagesCacheDir()
47+
await fs.ensureDir(cacheDir)
48+
await fs.emptyDir(cacheDir)
49+
50+
const packageJsonPath = path.join(cacheDir, `package.json`)
51+
52+
await fs.outputJson(packageJsonPath, {
53+
name: `gatsby-internal-packages`,
54+
description: `This directory contains internal packages installed by Gatsby used to comply with the current platform requirements`,
55+
version: `1.0.0`,
56+
private: true,
57+
author: `Gatsby`,
58+
license: `MIT`,
59+
})
60+
}
61+
62+
// lmdb module with prebuilt binaries for our platform
63+
const lmdbPackage = `@lmdb/lmdb-${process.platform}-${process.arch}`
64+
65+
// Detect if the prebuilt binaries for lmdb have been installed. These are installed under @lmdb and are tied to each platform/arch. We've seen instances where regular installations lack these modules because of a broken lockfile or skipping optional dependencies installs
66+
function installPrebuiltLmdb(): boolean {
67+
// Read lmdb's package.json, go through its optional depedencies and validate if there's a prebuilt lmdb module with a compatible binary to our platform and arch
68+
let packageJson: PackageJson
69+
try {
70+
const modulePath = path
71+
.dirname(require.resolve(`lmdb`))
72+
.replace(`/dist`, ``)
73+
const packageJsonPath = path.join(modulePath, `package.json`)
74+
packageJson = JSON.parse(fs.readFileSync(packageJsonPath, `utf-8`))
75+
} catch (e) {
76+
// If we fail to read lmdb's package.json there's bigger problems here so just skip installation
77+
return false
78+
}
79+
// If there's no lmdb prebuilt package for our arch/platform listed as optional dep no point in trying to install it
80+
const { optionalDependencies } = packageJson
81+
if (!optionalDependencies) return false
82+
if (!Object.keys(optionalDependencies).find(p => p === lmdbPackage))
83+
return false
84+
try {
85+
const lmdbRequire = mod.createRequire(require.resolve(`lmdb`))
86+
lmdbRequire.resolve(lmdbPackage)
87+
return false
88+
} catch (e) {
89+
return true
90+
}
91+
}
92+
93+
// Install lmdb's native system module under our internal cache if we detect the current installation
94+
// isn't using the pre-build binaries
95+
async function installIfMissingLmdb(): Promise<string | undefined> {
96+
if (!installPrebuiltLmdb()) return undefined
97+
98+
await createInternalPackagesCacheDir()
99+
100+
const cacheDir = getInternalPackagesCacheDir()
101+
const options: ExecaOptions = {
102+
stderr: `inherit`,
103+
cwd: cacheDir,
104+
}
105+
106+
const npmAdditionalCliArgs = [
107+
`--no-progress`,
108+
`--no-audit`,
109+
`--no-fund`,
110+
`--loglevel`,
111+
`error`,
112+
`--color`,
113+
`always`,
114+
`--legacy-peer-deps`,
115+
`--save-exact`,
116+
]
117+
118+
await execa(
119+
`npm`,
120+
[`install`, ...npmAdditionalCliArgs, `${lmdbPackage}@${dependencies.lmdb}`],
121+
options
122+
)
123+
124+
return path.join(cacheDir, `node_modules`, lmdbPackage)
125+
}
126+
38127
export async function createGraphqlEngineBundle(
39128
rootDir: string,
40129
reporter: Reporter,
@@ -57,6 +146,19 @@ export async function createGraphqlEngineBundle(
57146
require.resolve(`gatsby-plugin-typescript`)
58147
)
59148

149+
// Alternative lmdb path we've created to self heal from a "broken" lmdb installation
150+
const alternativeLmdbPath = await installIfMissingLmdb()
151+
152+
// We force a specific lmdb binary module if we detected a broken lmdb installation or if we detect the presence of an adapter
153+
let forcedLmdbBinaryModule: string | undefined = undefined
154+
if (store.getState().adapter.instance) {
155+
forcedLmdbBinaryModule = `${lmdbPackage}/node.abi83.glibc.node`
156+
}
157+
// We always force the binary if we've installed an alternative path
158+
if (alternativeLmdbPath) {
159+
forcedLmdbBinaryModule = `${alternativeLmdbPath}/node.abi83.glibc.node`
160+
}
161+
60162
const compiler = webpack({
61163
name: `Query Engine`,
62164
// mode: `production`,
@@ -121,9 +223,7 @@ export async function createGraphqlEngineBundle(
121223
{
122224
loader: require.resolve(`./lmdb-bundling-patch`),
123225
options: {
124-
forcedBinaryModule: store.getState().adapter.instance
125-
? `@lmdb/lmdb-${process.platform}-${process.arch}/node.abi83.glibc.node`
126-
: undefined,
226+
forcedBinaryModule: forcedLmdbBinaryModule,
127227
},
128228
},
129229
],

0 commit comments

Comments
 (0)