Skip to content

Commit 0f1f667

Browse files
pimterrycclauss
andauthored
fix: create Python symlink only during builds, and clean it up after (#2721)
* fix: create Python symlink only during builds, and clean it up after Previously in b9ddcd5 this was created during configuration, and the symlink persisted indefinitely. This causes problems with many tools that do not expect a codebase to include symlinks to external absolute paths. This PR largely reverts that commit, and instead writes the path to link to into the config, and then creates the symlink only temporarily during the build process, always deleting it afterwards. * assert install_path == self.output, f"{install_path} != {self.output}" --------- Co-authored-by: Christian Clauss <[email protected]>
1 parent 445c28f commit 0f1f667

File tree

4 files changed

+35
-33
lines changed

4 files changed

+35
-33
lines changed

lib/build.js

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@ async function build (gyp, argv) {
3030
let arch
3131
let nodeDir
3232
let guessedSolution
33+
let python
34+
let buildBinsDir
3335

3436
await loadConfigGypi()
3537

@@ -56,6 +58,7 @@ async function build (gyp, argv) {
5658
buildType = config.target_defaults.default_configuration
5759
arch = config.variables.target_arch
5860
nodeDir = config.variables.nodedir
61+
python = config.variables.python
5962

6063
if ('debug' in gyp.opts) {
6164
buildType = gyp.opts.debug ? 'Debug' : 'Release'
@@ -67,6 +70,7 @@ async function build (gyp, argv) {
6770
log.verbose('build type', buildType)
6871
log.verbose('architecture', arch)
6972
log.verbose('node dev dir', nodeDir)
73+
log.verbose('python', python)
7074

7175
if (win) {
7276
await findSolutionFile()
@@ -182,13 +186,32 @@ async function build (gyp, argv) {
182186

183187
if (!win) {
184188
// Add build-time dependency symlinks (such as Python) to PATH
185-
const buildBinsDir = path.resolve('build', 'node_gyp_bins')
189+
buildBinsDir = path.resolve('build', 'node_gyp_bins')
186190
process.env.PATH = `${buildBinsDir}:${process.env.PATH}`
187-
log.verbose('bin symlinks', `adding symlinks (such as Python), at "${buildBinsDir}", to PATH`)
191+
await fs.mkdir(buildBinsDir, { recursive: true })
192+
const symlinkDestination = path.join(buildBinsDir, 'python3')
193+
try {
194+
await fs.unlink(symlinkDestination)
195+
} catch (err) {
196+
if (err.code !== 'ENOENT') throw err
197+
}
198+
await fs.symlink(python, symlinkDestination)
199+
log.verbose('bin symlinks', `created symlink to "${python}" in "${buildBinsDir}" and added to PATH`)
188200
}
189201

190202
const proc = gyp.spawn(command, argv)
191-
await new Promise((resolve, reject) => proc.on('exit', (code, signal) => {
203+
await new Promise((resolve, reject) => proc.on('exit', async (code, signal) => {
204+
if (buildBinsDir) {
205+
// Clean up the build-time dependency symlinks:
206+
if (fs.rm) {
207+
// fs.rm is only available in Node 14+
208+
await fs.rm(buildBinsDir, { recursive: true })
209+
} else {
210+
// Only used for old node, as recursive rmdir is deprecated in Node 14+
211+
await fs.rmdir(buildBinsDir, { recursive: true })
212+
}
213+
}
214+
192215
if (code !== 0) {
193216
return reject(new Error('`' + command + '` failed with exit code: ' + code))
194217
}

lib/configure.js

Lines changed: 2 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@ if (win) {
1818
function configure (gyp, argv, callback) {
1919
let python
2020
const buildDir = path.resolve('build')
21-
const buildBinsDir = path.join(buildDir, 'node_gyp_bins')
2221
const configNames = ['config.gypi', 'common.gypi']
2322
const configs = []
2423
let nodeDir
@@ -76,8 +75,7 @@ function configure (gyp, argv, callback) {
7675
function createBuildDir () {
7776
log.verbose('build dir', 'attempting to create "build" dir: %s', buildDir)
7877

79-
const deepestBuildDirSubdirectory = win ? buildDir : buildBinsDir
80-
fs.mkdir(deepestBuildDirSubdirectory, { recursive: true }, function (err, isNew) {
78+
fs.mkdir(buildDir, { recursive: true }, function (err, isNew) {
8179
if (err) {
8280
return callback(err)
8381
}
@@ -88,31 +86,11 @@ function configure (gyp, argv, callback) {
8886
findVisualStudio(release.semver, gyp.opts['msvs-version'],
8987
createConfigFile)
9088
} else {
91-
createPythonSymlink()
9289
createConfigFile()
9390
}
9491
})
9592
}
9693

97-
function createPythonSymlink () {
98-
const symlinkDestination = path.join(buildBinsDir, 'python3')
99-
100-
log.verbose('python symlink', `creating symlink to "${python}" at "${symlinkDestination}"`)
101-
102-
fs.unlink(symlinkDestination, function (err) {
103-
if (err && err.code !== 'ENOENT') {
104-
log.verbose('python symlink', 'error when attempting to remove existing symlink')
105-
log.verbose('python symlink', err.stack, 'errno: ' + err.errno)
106-
}
107-
fs.symlink(python, symlinkDestination, function (err) {
108-
if (err) {
109-
log.verbose('python symlink', 'error when attempting to create Python symlink')
110-
log.verbose('python symlink', err.stack, 'errno: ' + err.errno)
111-
}
112-
})
113-
})
114-
}
115-
11694
function createConfigFile (err, vsInfo) {
11795
if (err) {
11896
return callback(err)
@@ -121,7 +99,7 @@ function configure (gyp, argv, callback) {
12199
process.env.GYP_MSVS_VERSION = Math.min(vsInfo.versionYear, 2015)
122100
process.env.GYP_MSVS_OVERRIDE_PATH = vsInfo.path
123101
}
124-
createConfigGypi({ gyp, buildDir, nodeDir, vsInfo }).then(configPath => {
102+
createConfigGypi({ gyp, buildDir, nodeDir, vsInfo, python }).then(configPath => {
125103
configs.push(configPath)
126104
findConfigs()
127105
}).catch(err => {

lib/create-config-gypi.js

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ async function getBaseConfigGypi ({ gyp, nodeDir }) {
3535
return JSON.parse(JSON.stringify(process.config))
3636
}
3737

38-
async function getCurrentConfigGypi ({ gyp, nodeDir, vsInfo }) {
38+
async function getCurrentConfigGypi ({ gyp, nodeDir, vsInfo, python }) {
3939
const config = await getBaseConfigGypi({ gyp, nodeDir })
4040
if (!config.target_defaults) {
4141
config.target_defaults = {}
@@ -75,6 +75,9 @@ async function getCurrentConfigGypi ({ gyp, nodeDir, vsInfo }) {
7575
// set the node development directory
7676
variables.nodedir = nodeDir
7777

78+
// set the configured Python path
79+
variables.python = python
80+
7881
// disable -T "thin" static archives by default
7982
variables.standalone_static_library = gyp.opts.thin ? 0 : 1
8083

@@ -112,13 +115,13 @@ async function getCurrentConfigGypi ({ gyp, nodeDir, vsInfo }) {
112115
return config
113116
}
114117

115-
async function createConfigGypi ({ gyp, buildDir, nodeDir, vsInfo }) {
118+
async function createConfigGypi ({ gyp, buildDir, nodeDir, vsInfo, python }) {
116119
const configFilename = 'config.gypi'
117120
const configPath = path.resolve(buildDir, configFilename)
118121

119122
log.verbose('build/' + configFilename, 'creating config file')
120123

121-
const config = await getCurrentConfigGypi({ gyp, nodeDir, vsInfo })
124+
const config = await getCurrentConfigGypi({ gyp, nodeDir, vsInfo, python })
122125

123126
// ensures that any boolean values in config.gypi get stringified
124127
function boolsToString (k, v) {

test/test-configure-python.js

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,7 @@ const configure = requireInject('../lib/configure', {
1616
mkdir: function (dir, options, cb) { cb() },
1717
promises: {
1818
writeFile: function (file, data) { return Promise.resolve(null) }
19-
},
20-
unlink: function (path, cb) { cb() },
21-
symlink: function (target, path, cb) { cb() }
19+
}
2220
}
2321
})
2422

0 commit comments

Comments
 (0)