lib_test_build.js

// Licensed to the Software Freedom Conservancy (SFC) under one
// or more contributor license agreements.  See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership.  The SFC licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License.  You may obtain a copy of the License at
//
//   http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied.  See the License for the
// specific language governing permissions and limitations
// under the License.

'use strict'

const fs = require('node:fs')
const path = require('node:path')
const { spawn } = require('node:child_process')
const PROJECT_ROOT = path.normalize(path.join(__dirname, '../../../../..'))
const WORKSPACE_FILE = path.join(PROJECT_ROOT, 'WORKSPACE')

function isDevMode() {
  return fs.existsSync(WORKSPACE_FILE)
}

function checkIsDevMode() {
  if (!isDevMode()) {
    throw Error('Cannot execute build; not running in dev mode')
  }
}

/**
 * Targets that have been previously built.
 * @type {!Object}
 */
let builtTargets = {}

/**
 * @param {!Array.<string>} targets The targets to build.
 * @throws {Error} If not running in dev mode.
 * @constructor
 */
const Build = function (targets) {
  checkIsDevMode()
  this.targets_ = targets
}

/** @private {boolean} */
Build.prototype.cacheResults_ = false

/**
 * Configures this build to only execute if it has not previously been
 * run during the life of the current process.
 * @return {!Build} A self reference.
 */
Build.prototype.onlyOnce = function () {
  this.cacheResults_ = true
  return this
}

/**
 * Executes the build.
 * @return {!Promise} A promise that will be resolved when
 *     the build has completed.
 * @throws {Error} If no targets were specified.
 */
Build.prototype.go = function () {
  let targets = this.targets_
  if (!targets.length) {
    throw Error('No targets specified')
  }

  // Filter out cached results.
  if (this.cacheResults_) {
    targets = targets.filter(function (target) {
      return !Object.prototype.hasOwnProperty.call(builtTargets, target)
    })

    if (!targets.length) {
      return Promise.resolve()
    }
  }

  console.log('\nBuilding', targets.join(' '), '...')

  let cmd,
    args = targets
  if (process.platform === 'win32') {
    cmd = 'cmd.exe'
    args.unshift('/c', path.join(PROJECT_ROOT, 'go.bat'))
  } else {
    cmd = path.join(PROJECT_ROOT, 'go')
  }

  return new Promise((resolve, reject) => {
    spawn(cmd, args, {
      cwd: PROJECT_ROOT,
      env: process.env,
      stdio: ['ignore', process.stdout, process.stderr],
    }).on('exit', function (code, signal) {
      if (code === 0) {
        targets.forEach(function (target) {
          builtTargets[target] = 1
        })
        return resolve()
      }

      let msg = 'Unable to build artifacts'
      if (code) {
        // May be null.
        msg += '; code=' + code
      }
      if (signal) {
        msg += '; signal=' + signal
      }

      reject(Error(msg))
    })
  })
}

// PUBLIC API

exports.isDevMode = isDevMode

/**
 * Creates a build of the listed targets.
 * @param {...string} var_args The targets to build.
 * @return {!Build} The new build.
 * @throws {Error} If not running in dev mode.
 */
exports.of = function (_) {
  let targets = Array.prototype.slice.call(arguments, 0)
  return new Build(targets)
}

/**
 * @return {string} Absolute path of the project's root directory.
 * @throws {Error} If not running in dev mode.
 */
exports.projectRoot = function () {
  return PROJECT_ROOT
}