@sap/cds-dk
Version:
Command line client and development toolkit for the SAP Cloud Application Programming Model
311 lines (287 loc) • 12.4 kB
JavaScript
const fs = require('fs')
const path = require('path')
const cds = require('../../cds')
const { hasJavaNature, BuildError, normalizePath } = require('../util')
const BuildTaskProvider = require('../buildTaskProvider')
const { FILE_EXT_CDS, BUILD_TASK_HANA, BUILD_TASK_FIORI, BUILD_TASK_JAVA, BUILD_TASK_JAVA_CF, BUILD_TASK_NODEJS, BUILD_TASK_NODE_CF, BUILD_TASK_MTX,
BUILD_TASKS, BUILD_TASK_MTX_SIDECAR, MTX_SIDECAR_FOLDER, BUILD_TASK_MTX_EXTENSION } = require("../constants")
const ResourcesTarBuilder = require('./mtx/resourcesTarBuilder')
const DEBUG = cds.debug('cli|build')
class InternalBuildTaskProvider extends BuildTaskProvider {
providesTask(key) {
return BUILD_TASKS.includes(key.for)
}
async lookupTasks(tasks, dependencies) {
return this._createTasks(tasks, dependencies)
}
async applyTaskDefaults(task) {
InternalBuildTaskProvider._setDefaultSrcFolder(task)
}
getPlugin(task) {
return require(`./${task.for}`)
}
async _createTasks(tasks, dependencies) {
let db = typeof cds.env.folders.db === "string" ? [normalizePath(cds.env.folders.db)] : cds.env.folders.db
let srv = typeof cds.env.folders.srv === "string" ? [normalizePath(cds.env.folders.srv)] : cds.env.folders.srv
const dbOptions = {
model: []
}
const srvOptions = {
model: []
}
if (Array.isArray(db) && db.length > 0) {
db = InternalBuildTaskProvider._getModuleFolder(db) || null
if (!db && !dependencies) { // log once
DEBUG?.("No database module found")
}
}
if (Array.isArray(srv) && srv.length > 0) {
srv = InternalBuildTaskProvider._getModuleFolder(srv) || null
if (!srv && !dependencies) { // log once
DEBUG?.("No service module found")
}
}
// order of creation is relevant
if (dependencies) {
const mtxTask = tasks.find(task => task.for === BUILD_TASK_MTX || task.for === BUILD_TASK_MTX_SIDECAR)
if (mtxTask) {
//restore used tasks as they might have been filtered by 'cds build --for mtx'
if (mtxTask._uses) {
mtxTask._uses.map(use => use.task).forEach(task => {
if (!tasks.includes(task)) {
tasks.push(task)
}
})
}
let dbTask = ResourcesTarBuilder.getHanaTenantDbTask(tasks, mtxTask)
if (!dbTask) {
// db task might be missing if mtx task is enforced by 'cds build --for mtx'
if (cds.env.build.tasks) {
dbTask = ResourcesTarBuilder.getHanaTenantDbTask(cds.env.build.tasks, mtxTask)
}
if (!dbTask) {
dbTask = this._createDbTask(db, dbOptions)
}
if (dbTask) {
tasks.push(dbTask)
}
}
if (dbTask && !mtxTask._uses?.map(use => use.task).includes(dbTask)) {
const use = { task: dbTask }
mtxTask._uses ? mtxTask._uses.push(use) : mtxTask._uses = [use]
}
}
} else {
!db && DEBUG?.(`project doesn't have a database module '${cds.env.folders.db}'`)
!srv && DEBUG?.(`project doesn't have a service module '${cds.env.folders.srv}'`)
// create hana build task
const dbTask = this._createDbTask(db, dbOptions)
if (dbTask) {
tasks.push(dbTask)
}
// create java or node build task
const srvTask = this._createSrvTask(srv, srvOptions)
if (srvTask) {
tasks.push(srvTask)
}
// create mtx build task
const mtxTask = this._createMtxTask(srv)
if (mtxTask) {
tasks.push(mtxTask)
}
}
}
_createDbTask(src, taskOptions) {
DEBUG?.("determining database kind")
if (!src || InternalBuildTaskProvider._isMtxExtension()) {
return null
}
let task = null
if (this._useHana()) {
DEBUG?.("found HANA database")
// legacy build supports dest property
const compileDest = cds.env.data?.dest
if (compileDest) {
//../db/src/gen
// compileDest is relative to src folder in modular build - resolve correctly
taskOptions.compileDest = path.relative(path.resolve(cds.root, src), path.resolve(cds.root, compileDest))
}
task = {
src: src,
for: BUILD_TASK_HANA,
options: taskOptions
}
} else {
DEBUG?.("found sqlite database - skipping HANA build task")
}
return task
}
_useHana() {
if (cds.env.requires.db?.kind === "hana" || cds.env.requires.db?.dialect === "hana") {
return true
}
// false if other db has been defined
if (cds.env.requires.db?.kind) {
return false
}
// check whether cds config represents a legacy build system config for which requires.db was not configured
// Note: compat layer sets requires.db: {}
const userEnv = cds.env.for("cds", cds.root, false)
return userEnv && (userEnv.data?.model || userEnv.service?.model)
}
_createMtxTask(src) {
DEBUG?.("determining MTX architecture")
// preserve order of creation
if (InternalBuildTaskProvider._isMtxExtension()) {
DEBUG?.("MTX extension app")
return { for: BUILD_TASK_MTX_EXTENSION }
}
if (InternalBuildTaskProvider._isMtxs()) {
const sidecarPath = path.join(cds.root, MTX_SIDECAR_FOLDER)
let sidecarEnv
if (fs.existsSync(sidecarPath)) {
sidecarEnv = cds.env.for("cds", sidecarPath)
}
if (
cds.env.profiles?.includes('with-mtx-sidecar') || // new presets
cds.env.requires["cds.xt.ModelProviderService"]?.external // for compatibility with former mtxs presets
) {
DEBUG?.("MTX app with sidecar")
if (!sidecarEnv) {
throw new BuildError(`MTX sidecar directory '${sidecarPath}' not existing. Custom build task configuration required if the folder is named differently.`)
}
if (!sidecarEnv.requires["cds.xt.ModelProviderService"]?._in_sidecar) {
if (sidecarEnv.profiles?.includes('mtx-sidecar')) {
// profile has been set, but cannot be resolved
throw new BuildError("MTX configuration cannot be resolved. Run the 'npm install' command to install up-to-date versions of @sap/cds-mtxs and @sap/cds.")
}
throw new BuildError(`Invalid MTX sidecar configuration - profile 'mtx-sidecar' not set.`)
}
return { src: MTX_SIDECAR_FOLDER, for: BUILD_TASK_MTX_SIDECAR }
}
if (sidecarEnv?.requires["cds.xt.ModelProviderService"]?._in_sidecar || sidecarEnv?.profiles?.includes('mtx-sidecar')) {
throw new BuildError(`MTX sidecar requires profile 'with-mtx-sidecar' in project root configuration.`)
}
if (cds.env.requires["cds.xt.ModelProviderService"]?._in_sidecar || cds.env.profiles?.includes('mtx-sidecar')) {
// cds build is executed in sidecar folder
throw new BuildError(`Seems that you are executing 'cds build' in the 'mtx/sidecar' folder. Execute 'cds build' in the project root folder instead.`)
}
// mtxs nodejs app without sidecar
if (!hasJavaNature()) {
DEBUG?.("MTX Nodejs app without sidecar")
return {
for: BUILD_TASK_MTX,
src
}
}
}
}
_createSrvTask(src, taskOptions) {
DEBUG?.("determining implementation technology")
if (!src || InternalBuildTaskProvider._isMtxExtension()) {
return null
}
let task = null
if (hasJavaNature()) {
task = this._createJavaTask(src, taskOptions)
} else {
task = this._createNodeTask(src, taskOptions)
}
return task
}
_createJavaTask(src, taskOptions) {
DEBUG?.("found implementation technology Java")
// legacy build supports dest property
const compileDest = cds.env.service?.dest
if (compileDest) {
// compileDest is relative to src folder in modular build - resolve correctly
taskOptions.compileDest = path.relative(path.resolve(cds.root, src), path.resolve(cds.root, compileDest))
}
return {
src: src,
for: BUILD_TASK_JAVA,
options: taskOptions
}
}
_createNodeTask(src, taskOptions) {
DEBUG?.("found implementation technology Nodejs")
if (!fs.existsSync(path.join(cds.root, 'package.json'))) {
throw new BuildError(`No 'package.json' found in project root folder '${cds.root}'`)
}
return {
src: src,
for: BUILD_TASK_NODEJS,
options: taskOptions
}
}
static _isMtxExtension() {
return !!cds.env.extends
}
/**
* Distinguishes whether the Nodejs project is a Streamlined MTX (cds >=6) or an old MTX project.
*/
static _isMtxs() {
return cds.env.requires.toggles
|| cds.env.profiles.includes('with-mtx-sidecar')
|| cds.env.requires['cds.xt.ModelProviderService']
|| (typeof cds.env.requires.multitenancy === "object")
}
static _setDefaultSrcFolder(task) {
switch (task.for) {
case BUILD_TASK_HANA:
task.src ||= normalizePath(cds.env.folders.db)
break
case BUILD_TASK_JAVA:
case BUILD_TASK_JAVA_CF:
case BUILD_TASK_NODEJS:
case BUILD_TASK_NODE_CF:
task.src ||= normalizePath(cds.env.folders.srv)
break
case BUILD_TASK_FIORI:
task.src ||= normalizePath(cds.env.folders.app)
break
case BUILD_TASK_MTX_SIDECAR:
task.src ||= MTX_SIDECAR_FOLDER
break
case BUILD_TASK_MTX_EXTENSION:
if (task.src && task.src !== cds.root) {
throw new BuildError("Invalid 'src' property value for build task 'mtx-extension', the only allowed value is '.'")
}
task.src ||= "."
break
case BUILD_TASK_MTX:
// mtxs with nodejs, but without sidecar
task.src ||= normalizePath(cds.env.folders.srv)
break
default:
throw new Error(`Unknown build task '${task.for}'`)
}
}
/**
* Determines the module folder from the given list that may represent files or folders w or w/o .cds file extension.
* @param {Array} filesOrFolders
*/
static _getModuleFolder(filesOrFolders) {
const resources = [...filesOrFolders]
filesOrFolders.forEach(fileOrFolder => {
if (path.extname(fileOrFolder) !== FILE_EXT_CDS) {
resources.push(fileOrFolder + FILE_EXT_CDS)
}
})
return resources.reduce((acc, resource) => {
if (!acc) {
let resourcePath = path.resolve(cds.root, resource)
if (fs.existsSync(resourcePath)) {
if (fs.lstatSync(resourcePath).isDirectory()) {
acc = resource
} else {
// represents file
acc = path.dirname(resource)
}
}
}
return acc
}, null)
}
}
module.exports = InternalBuildTaskProvider