@platforma-sdk/r-builder 0.0.2 → 0.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +3 -5
- package/scripts/builder.js +0 -8
- package/scripts/lib/linux.js +36 -15
- package/scripts/lib/macos.js +1 -0
- package/scripts/lib/util.js +32 -37
- package/scripts/lib/windows.js +2 -7
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "@platforma-sdk/r-builder",
|
|
3
3
|
"description": "Build R run environment for Platforma",
|
|
4
4
|
"type": "module",
|
|
5
|
-
"version": "0.0.
|
|
5
|
+
"version": "0.0.4",
|
|
6
6
|
"files": [
|
|
7
7
|
"assets/",
|
|
8
8
|
"scripts/"
|
|
@@ -12,12 +12,10 @@
|
|
|
12
12
|
},
|
|
13
13
|
"license": "UNLICENSED",
|
|
14
14
|
"dependencies": {
|
|
15
|
-
"undici": "~7.
|
|
15
|
+
"undici": "~7.4.0",
|
|
16
16
|
"fs-extra": "^11.2.0",
|
|
17
17
|
"tar": "^7.4.3",
|
|
18
|
-
"
|
|
19
|
-
"winston": "^3.17.0",
|
|
20
|
-
"xz": "^2.0.2"
|
|
18
|
+
"winston": "^3.17.0"
|
|
21
19
|
},
|
|
22
20
|
"devDependencies": {
|
|
23
21
|
"@milaboratories/eslint-config": "^1.0.1"
|
package/scripts/builder.js
CHANGED
|
@@ -22,12 +22,6 @@ import * as windows from './lib/windows.js';
|
|
|
22
22
|
|
|
23
23
|
export async function buildRDist(logger, version) {
|
|
24
24
|
const currentOS = os.platform();
|
|
25
|
-
|
|
26
|
-
const doneFile = `${version}.build-done`
|
|
27
|
-
if (fs.existsSync(doneFile)) {
|
|
28
|
-
logger.info(`Build of R is already done in this environment.\nTo force rebuild remove '${path.resolve(doneFile)}' file and run again.`)
|
|
29
|
-
return
|
|
30
|
-
}
|
|
31
25
|
|
|
32
26
|
switch (currentOS) {
|
|
33
27
|
case 'darwin': {
|
|
@@ -50,8 +44,6 @@ export async function buildRDist(logger, version) {
|
|
|
50
44
|
process.exit(1);
|
|
51
45
|
}
|
|
52
46
|
}
|
|
53
|
-
|
|
54
|
-
fs.writeFileSync(doneFile, '');
|
|
55
47
|
}
|
|
56
48
|
|
|
57
49
|
export async function buildRDeps(logger, version) {
|
package/scripts/lib/linux.js
CHANGED
|
@@ -7,6 +7,39 @@ import * as util from './util.js';
|
|
|
7
7
|
|
|
8
8
|
const platformName = os.arch() === 'arm64' ? 'linux-aarch64' : `linux-x64`;
|
|
9
9
|
|
|
10
|
+
function useCaching(logger) {
|
|
11
|
+
if (util.isOK('which ccache')) {
|
|
12
|
+
useCCache(logger)
|
|
13
|
+
} else if (util.isOK('which sccache')) {
|
|
14
|
+
useSCCache(logger)
|
|
15
|
+
} else {
|
|
16
|
+
logger.warn('No caching tool found. Builds will be slow.')
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Setup current process environment to integrate with sccache
|
|
22
|
+
*
|
|
23
|
+
* @param {winston.Logger} logger
|
|
24
|
+
*/
|
|
25
|
+
function useSCCache(logger) {
|
|
26
|
+
logger.info("Using sccache as build caching tool...")
|
|
27
|
+
process.env.SCCACHE_GHA_ENABLED = "true"
|
|
28
|
+
process.env.CC = `sccache gcc`
|
|
29
|
+
process.env.CXX = `sccache g++`
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Setup current process environment to integrate with ccache
|
|
34
|
+
*
|
|
35
|
+
* @param {winston.Logger} logger
|
|
36
|
+
*/
|
|
37
|
+
function useCCache(logger) {
|
|
38
|
+
logger.info("Using ccache as build caching tool...")
|
|
39
|
+
process.env.CC = `ccache gcc`
|
|
40
|
+
process.env.CXX = `ccache g++`
|
|
41
|
+
}
|
|
42
|
+
|
|
10
43
|
async function installSystemPackages(logger) {
|
|
11
44
|
const originalAptSources = '/etc/apt/sources.list';
|
|
12
45
|
logger.info(`Analyzing '${originalAptSources}'`);
|
|
@@ -224,19 +257,6 @@ function patchBinElf(logger, binaryFile, rpath) {
|
|
|
224
257
|
util.runInherit(`patchelf --remove-rpath '${binaryFile}'`);
|
|
225
258
|
util.runInherit(`patchelf --set-rpath '${rpath}' '${binaryFile}'`);
|
|
226
259
|
}
|
|
227
|
-
/**
|
|
228
|
-
* Setup current process environment to integrate with sccache
|
|
229
|
-
*
|
|
230
|
-
* @param {winston.Logger} logger
|
|
231
|
-
*/
|
|
232
|
-
function useSCCache(logger) {
|
|
233
|
-
if (util.isOK('which sccache')) {
|
|
234
|
-
logger.info("Using sccache as build caching tool...")
|
|
235
|
-
process.env.SCCACHE_GHA_ENABLED = "true"
|
|
236
|
-
process.env.CC = `sccache gcc`
|
|
237
|
-
process.env.CXX = `sccache g++`
|
|
238
|
-
}
|
|
239
|
-
}
|
|
240
260
|
|
|
241
261
|
/**
|
|
242
262
|
* Build portable R distribution and all required packages from sources
|
|
@@ -250,7 +270,7 @@ export async function buildR(logger, version) {
|
|
|
250
270
|
process.exit(1);
|
|
251
271
|
}
|
|
252
272
|
|
|
253
|
-
|
|
273
|
+
useCaching(logger)
|
|
254
274
|
|
|
255
275
|
const distDir = path.join(util.rDistDir, platformName);
|
|
256
276
|
|
|
@@ -267,6 +287,7 @@ export async function buildR(logger, version) {
|
|
|
267
287
|
|
|
268
288
|
fs.renameSync(path.join(distDir, 'bin/R'), path.join(distDir, 'bin/R.orig'))
|
|
269
289
|
fs.copyFileSync(pkg.asset('R.linux.sh'), path.join(distDir, 'bin/R'));
|
|
290
|
+
fs.chmodSync(path.join(distDir, 'bin/R'), 0o755)
|
|
270
291
|
|
|
271
292
|
const gccVersion = getGCCMajorVersion()
|
|
272
293
|
const gccUtilsPath = path.join('/usr/lib/gcc/x86_64-linux-gnu', gccVersion.toString())
|
|
@@ -280,7 +301,7 @@ export async function buildDeps(logger, version) {
|
|
|
280
301
|
process.exit(1);
|
|
281
302
|
}
|
|
282
303
|
|
|
283
|
-
|
|
304
|
+
useCaching(logger);
|
|
284
305
|
|
|
285
306
|
const distDir = path.join(util.rDistDir, platformName);
|
|
286
307
|
|
package/scripts/lib/macos.js
CHANGED
|
@@ -52,6 +52,7 @@ export async function buildR(logger, version) {
|
|
|
52
52
|
|
|
53
53
|
fs.renameSync(path.join(distDir, 'bin/R'), path.join(distDir, 'bin/R.orig'))
|
|
54
54
|
fs.copyFileSync(pkg.asset('R.macos.sh'), path.join(distDir, 'bin/R'))
|
|
55
|
+
fs.chmodSync(path.join(distDir, 'bin/R'), 0o755)
|
|
55
56
|
|
|
56
57
|
util.installBasicRPackages(logger, distDir);
|
|
57
58
|
}
|
package/scripts/lib/util.js
CHANGED
|
@@ -8,7 +8,6 @@ import { fetch } from 'undici';
|
|
|
8
8
|
import * as tar from 'tar';
|
|
9
9
|
import winston from 'winston';
|
|
10
10
|
import zlib from 'zlib';
|
|
11
|
-
import xz from 'xz';
|
|
12
11
|
|
|
13
12
|
import * as pkg from './pkg.js';
|
|
14
13
|
|
|
@@ -20,7 +19,10 @@ export const downloadsDir = `${buildDir}/artifacts/dld`;
|
|
|
20
19
|
export const artifactsDir = `${buildDir}/artifacts/pkg`;
|
|
21
20
|
|
|
22
21
|
export const dependenciesDir = 'dependencies'; // where to search for dependencies specification (init.R or renv.lock)
|
|
23
|
-
|
|
22
|
+
|
|
23
|
+
// directory with all renv cache stored inside final R run environment distribution.
|
|
24
|
+
// When changing this name, remember to update R preparation steps in workflow-tengo SDK.
|
|
25
|
+
const renvDirName = 'renv-root'
|
|
24
26
|
|
|
25
27
|
const pipelineAsync = promisify(pipeline);
|
|
26
28
|
|
|
@@ -134,15 +136,10 @@ export async function download(logger, url, destination) {
|
|
|
134
136
|
}
|
|
135
137
|
|
|
136
138
|
export async function extractTarXz(archivePath, destination) {
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
await pipelineAsync(
|
|
142
|
-
archiveStream,
|
|
143
|
-
decompress,
|
|
144
|
-
tar.extract({ cwd: destination })
|
|
145
|
-
);
|
|
139
|
+
if (!fs.existsSync(destination)) {
|
|
140
|
+
fs.mkdirSync(destination, { recursive: true });
|
|
141
|
+
}
|
|
142
|
+
runInherit(`tar -xJf '${archivePath}' -C '${destination}'`);
|
|
146
143
|
}
|
|
147
144
|
|
|
148
145
|
export async function extractTarGz(archivePath, destination) {
|
|
@@ -234,7 +231,9 @@ export function createLogger(level = 'debug') {
|
|
|
234
231
|
|
|
235
232
|
format: winston.format.combine(
|
|
236
233
|
winston.format.printf(({ level, message }) => {
|
|
237
|
-
const
|
|
234
|
+
const now = new Date();
|
|
235
|
+
const time = `[${now.getHours().toString().padStart(2, '0')}:${now.getMinutes().toString().padStart(2, '0')}:${now.getSeconds().toString().padStart(2, '0')}]`;
|
|
236
|
+
const indent = ' '.repeat(time.length + 1 + level.length + 2); // For ': ' after the level
|
|
238
237
|
const indentedMessage = message
|
|
239
238
|
.split('\n')
|
|
240
239
|
.map((line, index) => (index === 0 ? line : indent + line))
|
|
@@ -242,7 +241,7 @@ export function createLogger(level = 'debug') {
|
|
|
242
241
|
|
|
243
242
|
const colorize = (l) => winston.format.colorize().colorize(l, l);
|
|
244
243
|
|
|
245
|
-
return `${colorize(level)}: ${indentedMessage}`;
|
|
244
|
+
return `${time} ${colorize(level)}: ${indentedMessage}`;
|
|
246
245
|
})
|
|
247
246
|
),
|
|
248
247
|
|
|
@@ -270,7 +269,7 @@ export function runR(logger, rRoot, args, opts = {}) {
|
|
|
270
269
|
const cmdToRun = `${path.resolve(rRoot, 'bin/R')} ${args.join(' ')}`;
|
|
271
270
|
const renvRoot = path.join(path.resolve(rRoot), renvDirName)
|
|
272
271
|
|
|
273
|
-
env = {
|
|
272
|
+
const env = {
|
|
274
273
|
RHOME: path.resolve(rRoot),
|
|
275
274
|
R_HOME_DIR: path.resolve(rRoot),
|
|
276
275
|
RENV_PATHS_ROOT: renvRoot,
|
|
@@ -403,22 +402,25 @@ export function buildRDependencies(
|
|
|
403
402
|
'--no-echo',
|
|
404
403
|
'-e', ...quote(`renv::restore( clean = TRUE )`),
|
|
405
404
|
], {
|
|
406
|
-
wd:
|
|
405
|
+
wd: depsDir,
|
|
407
406
|
paths: additionalPaths,
|
|
408
407
|
})
|
|
409
408
|
|
|
409
|
+
} else if (fs.existsSync(path.join(depsDir, initFile))) {
|
|
410
|
+
runInRenv(logger, rRoot, ['--no-echo', `--file=${initFile}`], {
|
|
411
|
+
wd: depsDir,
|
|
412
|
+
paths: additionalPaths,
|
|
413
|
+
})
|
|
414
|
+
|
|
415
|
+
if (!fs.existsSync(path.join(depsDir, lockFile))) {
|
|
416
|
+
runInRenv(logger, rRoot, ['--no-echo', '-e', ...quote(`renv::snapshot()`)])
|
|
417
|
+
}
|
|
418
|
+
|
|
410
419
|
} else {
|
|
411
|
-
if (!fs.existsSync(path.join(depsDir, initFile))) {
|
|
412
420
|
throw new Error(
|
|
413
421
|
`directory ${depsDir} has no ${lockFile} or ${initFile}. No dependencies detected.\n`+
|
|
414
422
|
`Create empty init.R file if you really need empty R run environment without any preinstalled dependencies`
|
|
415
423
|
)
|
|
416
|
-
}
|
|
417
|
-
|
|
418
|
-
runInRenv(logger, rRoot, ['--no-echo', `--file=${initFile}`], {
|
|
419
|
-
wd: tempRenvDir,
|
|
420
|
-
paths: additionalPaths,
|
|
421
|
-
})
|
|
422
424
|
}
|
|
423
425
|
|
|
424
426
|
const lockData = fs.readFileSync(lockPath, { encoding: 'utf-8' });
|
|
@@ -433,8 +435,12 @@ export function buildRDependencies(
|
|
|
433
435
|
}
|
|
434
436
|
}
|
|
435
437
|
|
|
436
|
-
|
|
438
|
+
// Store resulting 'renv.lock' file in R installation root directory,
|
|
439
|
+
// so we can use it when preparing renv on Platforma Backend side (workflow-tengo SDK).
|
|
440
|
+
fs.copyFileSync(path.join(depsDir, lockFile), path.join(rRoot, renvDirName, lockFile))
|
|
441
|
+
|
|
437
442
|
if (fs.existsSync(origProjectsFile)) {
|
|
443
|
+
// Restore original 'projects' file to not leave traces of environment preparation.
|
|
438
444
|
fs.renameSync(origProjectsFile, projectsFile)
|
|
439
445
|
}
|
|
440
446
|
|
|
@@ -474,6 +480,8 @@ function patchREnviron(
|
|
|
474
480
|
envs = {},
|
|
475
481
|
comment = "",
|
|
476
482
|
) {
|
|
483
|
+
logger.info(`Patching Renviron file in ${rRoot} directory`)
|
|
484
|
+
|
|
477
485
|
let rEnvironPath = path.join(rRoot, 'etc', 'Renviron')
|
|
478
486
|
if (!fs.existsSync(rEnvironPath)) {
|
|
479
487
|
rEnvironPath = path.join(rRoot, 'etc', 'Renviron.site')
|
|
@@ -487,20 +495,7 @@ function patchREnviron(
|
|
|
487
495
|
}
|
|
488
496
|
|
|
489
497
|
for (const [envName, envValue] of Object.entries(envs)) {
|
|
490
|
-
line = `${envName}=\${${envName}:-'${envValue}'}`
|
|
498
|
+
const line = `${envName}=\${${envName}:-'${envValue}'}`
|
|
491
499
|
fs.appendFileSync(rEnvironPath, line + '\n', 'utf8');
|
|
492
500
|
}
|
|
493
501
|
}
|
|
494
|
-
|
|
495
|
-
/**
|
|
496
|
-
* Recreate directory by removing it and creating back.
|
|
497
|
-
*
|
|
498
|
-
* @param {string} dir - path to directory
|
|
499
|
-
*/
|
|
500
|
-
function recreateDir(dir) {
|
|
501
|
-
if (fs.existsSync(dir)) {
|
|
502
|
-
fs.rmSync(dir, { recursive: true })
|
|
503
|
-
}
|
|
504
|
-
fs.mkdirSync(dir, { recursive: true })
|
|
505
|
-
|
|
506
|
-
}
|
package/scripts/lib/windows.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import path from 'node:path';
|
|
2
2
|
import os from 'node:os';
|
|
3
|
-
import
|
|
3
|
+
import fsextra from 'fs-extra';
|
|
4
4
|
|
|
5
5
|
import * as pkg from './pkg.js';
|
|
6
6
|
import * as util from './util.js';
|
|
@@ -17,12 +17,7 @@ export async function buildR(logger, version) {
|
|
|
17
17
|
|
|
18
18
|
// Copy Windows R stub files to dist directory
|
|
19
19
|
const stubDir = pkg.asset('windows-x64-stub');
|
|
20
|
-
await
|
|
21
|
-
recursive: true,
|
|
22
|
-
preserveTimestamps: true,
|
|
23
|
-
force: true,
|
|
24
|
-
errorOnExist: false
|
|
25
|
-
});
|
|
20
|
+
await fsextra.copy(stubDir, distDir);
|
|
26
21
|
|
|
27
22
|
// TODO: R for windows
|
|
28
23
|
}
|