From a8ef8be1358b030b00af5a04c5fd79bcb9842291 Mon Sep 17 00:00:00 2001 From: Alex Eagle Date: Fri, 16 Feb 2018 14:31:02 -0800 Subject: [PATCH] Introduce npm_package rule Fixes #10 --- defs.bzl | 2 + internal/node/node_repositories.bzl | 12 ++++- internal/npm_package/BUILD.bazel | 12 +++++ internal/npm_package/npm_package.bzl | 75 ++++++++++++++++++++++++++++ internal/npm_package/packager.js | 61 ++++++++++++++++++++++ 5 files changed, 161 insertions(+), 1 deletion(-) create mode 100644 internal/npm_package/BUILD.bazel create mode 100644 internal/npm_package/npm_package.bzl create mode 100644 internal/npm_package/packager.js diff --git a/defs.bzl b/defs.bzl index e2ddf95ed9..c131df09ac 100644 --- a/defs.bzl +++ b/defs.bzl @@ -26,6 +26,7 @@ load("//internal/jasmine_node_test:jasmine_node_test.bzl", _jasmine_node_test = load("//internal/npm_install:npm_install.bzl", _npm_install = "npm_install") load("//internal/yarn_install:yarn_install.bzl", _yarn_install = "yarn_install") load("//internal/rollup:rollup_bundle.bzl", _rollup_bundle = "rollup_bundle") +load("//internal/npm_package:npm_package.bzl", _npm_package = "npm_package") check_bazel_version = _check_bazel_version nodejs_binary = _nodejs_binary @@ -35,3 +36,4 @@ jasmine_node_test = _jasmine_node_test npm_install = _npm_install yarn_install = _yarn_install rollup_bundle = _rollup_bundle +npm_package = _npm_package diff --git a/internal/node/node_repositories.bzl b/internal/node/node_repositories.bzl index 5b34d6ed41..962466acd0 100644 --- a/internal/node/node_repositories.bzl +++ b/internal/node/node_repositories.bzl @@ -61,13 +61,21 @@ def _node_impl(repository_ctx): npm = "bin/npm" repository_ctx.file("BUILD.bazel", content="""#Generated by node_repositories.bzl package(default_visibility = ["//visibility:public"]) -exports_files(["{0}"]) +exports_files(["{0}", "run_npm.sh.template"]) alias(name = "node", actual = "{0}") sh_binary( name = "npm", srcs = ["npm.sh"], ) """.format(node)) + + # `yarn publish` is not ready for use under Bazel, see https://github.com/yarnpkg/yarn/issues/610 + repository_ctx.file("run_npm.sh.template", content=""" +NODE="{}" +NPM="{}" +"$NODE" "$NPM" TMPL_args "$@" +""".format(repository_ctx.path(node), repository_ctx.path(npm))) + repository_ctx.file("npm.sh", content="""#!/bin/bash #Generated by node_repositories.bzl """ + "".join([""" @@ -109,12 +117,14 @@ def _yarn_impl(repository_ctx): # speed and correctness. We download a specific version of Yarn to ensure a hermetic build. repository_ctx.file("BUILD.bazel", content="""#Generated by node_repositories.bzl package(default_visibility = ["//visibility:public"]) +exports_files(["bin/yarn.js"]) sh_binary( name = "yarn", srcs = ["yarn.sh"], ) """) node = get_node_label(repository_ctx) + repository_ctx.file("yarn.sh", content="""#!/bin/bash #Generated by node_repositories.bzl """ + "".join([""" diff --git a/internal/npm_package/BUILD.bazel b/internal/npm_package/BUILD.bazel new file mode 100644 index 0000000000..412a0d701f --- /dev/null +++ b/internal/npm_package/BUILD.bazel @@ -0,0 +1,12 @@ +load("@build_bazel_rules_nodejs//:defs.bzl", "nodejs_binary") + +nodejs_binary( + name = "packager", + data = [ + "packager.js", + "@nodejs//:run_npm.sh.template", + ], + entry_point = "build_bazel_rules_nodejs/internal/npm_package/packager.js", + node_modules = "@build_bazel_rules_nodejs_npm_install_deps//:node_modules", + visibility = ["//visibility:public"], +) diff --git a/internal/npm_package/npm_package.bzl b/internal/npm_package/npm_package.bzl new file mode 100644 index 0000000000..8b1b157444 --- /dev/null +++ b/internal/npm_package/npm_package.bzl @@ -0,0 +1,75 @@ +"""The npm_package rule creates a directory containing a publishable npm artifact. + +It also produces two named outputs: +:label.pack +:label.publish + +These can be used with `bazel run` to create a .tgz of the package and to publish +the package to the npm registry, respectively. +""" + +load("//internal:node.bzl", "sources_aspect") + +def create_package(ctx, devmode_sources): + """Creates an action that produces the npm package. + + It copies srcs and deps into the artifact and produces the .pack and .publish + scripts. + + Args: + ctx: the skylark rule context + devmode_sources: the .js files which belong in the package + + Returns: + The tree artifact which is the publishable directory. + """ + + package_dir = ctx.actions.declare_directory(ctx.label.name) + + args = ctx.actions.args() + args.add(package_dir.path) + args.add([s.path for s in ctx.files.srcs], join_with=",") + args.add(ctx.bin_dir.path) + args.add([s.path for s in devmode_sources], join_with=",") + args.add([ctx.outputs.pack.path, ctx.outputs.publish.path]) + + ctx.action( + executable = ctx.executable._packager, + inputs = ctx.files.srcs + devmode_sources + [ctx.file._run_npm_template], + outputs = [package_dir, ctx.outputs.pack, ctx.outputs.publish], + arguments = [args], + ) + return package_dir + +def _npm_package(ctx): + files = depset() + for d in ctx.attr.deps: + files = depset(transitive = [files, d.files, d.node_sources]) + + package_dir = create_package(ctx, files.to_list()) + + return [DefaultInfo( + files = depset([package_dir]), + )] + +NPM_PACKAGE_ATTRS = { + "srcs": attr.label_list(allow_files = True), + "deps": attr.label_list(aspects = [sources_aspect]), + "_packager": attr.label( + default = Label("//internal/npm_package:packager"), + cfg = "host", executable = True), + "_run_npm_template": attr.label( + default = Label("@nodejs//:run_npm.sh.template"), + allow_single_file = True), +} + +NPM_PACKAGE_OUTPUTS = { + "pack": "%{name}.pack", + "publish": "%{name}.publish", +} + +npm_package = rule( + implementation = _npm_package, + attrs = NPM_PACKAGE_ATTRS, + outputs = NPM_PACKAGE_OUTPUTS, +) diff --git a/internal/npm_package/packager.js b/internal/npm_package/packager.js new file mode 100644 index 0000000000..76a5d2dd7c --- /dev/null +++ b/internal/npm_package/packager.js @@ -0,0 +1,61 @@ +/** + * @license + * Copyright 2018 The Bazel Authors. All rights reserved. + * + * Licensed 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. + */ +const fs = require('fs'); +const path = require('path'); + +function mkdirp(p) { + if (!fs.existsSync(p)) { + mkdirp(path.dirname(p)); + fs.mkdirSync(p); + } +} + +function write(p, content) { + mkdirp(path.dirname(p)); + fs.writeFileSync(p, content); +} + +// TODO(alexeagle): add support for version stamping, we might want to replace +// the version number in some of the files (eg. take the latest git tag and +// overwrite the version in package.json) + +function main(args) { + const [outDir, srcsArg, binDir, depsArg, packPath, publishPath] = args; + + // src like my/path is just copied to outDir/my/path + for (src of srcsArg.split(',').filter(s => !!s)) { + const content = fs.readFileSync(src, {encoding: 'utf-8'}); + const outPath = path.join(outDir, src); + write(outPath, content); + } + + // deps like bazel-bin/my/path is copied to outDir/my/path + for (dep of depsArg.split(',').filter(s => !!s)) { + const content = fs.readFileSync(dep, {encoding: 'utf-8'}) + const outPath = path.join(outDir, path.relative(binDir, dep)); + write(outPath, content); + } + + const npmTemplate = + fs.readFileSync(require.resolve('nodejs/run_npm.sh.template'), {encoding: 'utf-8'}); + fs.writeFileSync(packPath, npmTemplate.replace('TMPL_args', `pack ${outDir}`)); + fs.writeFileSync(publishPath, npmTemplate.replace('TMPL_args', `publish ${outDir}`)); +} + +if (require.main === module) { + process.exitCode = main(process.argv.slice(2)); +}