build(bazel): add `data` attr to ng_package (#22919)

PR Close #22919
This commit is contained in:
Jeremy Elbourn 2018-03-21 16:18:06 -07:00 committed by Matias Niemelä
parent bcaa07b0ac
commit d9a0a8ff3e
5 changed files with 114 additions and 34 deletions

View File

@ -172,6 +172,7 @@ def _ng_package_impl(ctx):
packager_inputs = (
ctx.files.srcs +
ctx.files.data +
esm5_sources.to_list() +
depset(transitive = [d.typescript.transitive_declarations
for d in ctx.attr.deps
@ -186,6 +187,7 @@ def _ng_package_impl(ctx):
packager_args.add(npm_package_directory.path)
packager_args.add(ctx.label.package)
packager_args.add([ctx.bin_dir.path, ctx.label.package], join_with="/")
packager_args.add([ctx.genfiles_dir.path, ctx.label.package], join_with="/")
# Marshal the metadata into a JSON string so we can parse the data structure
# in the TypeScript program easily.
@ -213,6 +215,9 @@ def _ng_package_impl(ctx):
packager_args.add(_flatten_paths(bundles), join_with=",")
packager_args.add([s.path for s in ctx.files.srcs], join_with=",")
# TODO: figure out a better way to gather runfiles providers from the transitive closure.
packager_args.add([d.path for d in ctx.files.data], join_with=",")
if ctx.file.license_banner:
packager_inputs.append(ctx.file.license_banner)
packager_args.add(ctx.file.license_banner.path)
@ -246,6 +251,10 @@ NG_PACKAGE_ATTRS = dict(NPM_PACKAGE_ATTRS, **dict(ROLLUP_ATTRS, **{
esm5_outputs_aspect,
sources_aspect,
]),
"data": attr.label_list(
doc = "Additional, non-Angular files to be added to the package, e.g. global CSS assets.",
allow_files = True,
),
"include_devmode_srcs": attr.bool(default = False),
"readme_md": attr.label(allow_single_file = FileType([".md"])),
"globals": attr.string_dict(default={}),

View File

@ -14,6 +14,9 @@ function main(args: string[]): number {
// Exit immediately when encountering an error.
shx.set('-e');
// Keep track of whether an error has occured so that we can return an appropriate exit code.
let errorHasOccured = false;
// This utility expects all of its arguments to be specified in a params file generated by
// bazel (see https://docs.bazel.build/versions/master/skylark/lib/Args.html#use_param_file).
const paramFilePath = args[0];
@ -35,6 +38,9 @@ function main(args: string[]): number {
// This is the intended output location for package artifacts.
binDir,
// The bazel-genfiles dir joined with the srcDir (e.g. 'bazel-bin/package.common').
genfilesDir,
// JSON data mapping each entry point to the generated bundle index and
// flat module metadata, for example
// {"@angular/core": {
@ -67,6 +73,9 @@ function main(args: string[]): number {
// List of all files in the ng_package rule's srcs.
srcsArg,
// List of all files in the ng_package rule's data.
dataArg,
// Path to the package's LICENSE.
licenseFile,
] = params;
@ -77,6 +86,7 @@ function main(args: string[]): number {
const esm5 = esm5Arg.split(',').filter(s => !!s);
const bundles = bundlesArg.split(',').filter(s => !!s);
const srcs = srcsArg.split(',').filter(s => !!s);
const dataFiles: string[] = dataArg.split(',').filter(s => !!s);
const modulesManifest = JSON.parse(modulesManifestArg);
if (readmeMd) {
@ -84,31 +94,39 @@ function main(args: string[]): number {
}
/**
* Relativize the path where the file is written.
* @param f a path relative to the srcDir, typically from a file in the srcs[]
* @param c content of the file
* Writes a file into the package based on its input path, relativizing to the package path.
* @param inputPath Path to the file in the input tree.
* @param fileContent Content of the file.
*/
function writeSrcFile(f: string, c: string) {
mkDirWriteFile(path.join(out, path.relative(srcDir, f)), c);
function writeFileFromInputPath(inputPath: string, fileContent: string) {
// We want the relative path from the given file to its ancestor "root" directory.
// This root depends on whether the file lives in the source tree (srcDir) as a basic file
// input to ng_package, the bin output tree (binDir) as the output of another rule, or
// the genfiles output tree (genfilesDir) as the output of a genrule.
let rootDir: string;
if (inputPath.includes(binDir)) {
rootDir = binDir;
} else if (inputPath.includes(genfilesDir)) {
rootDir = genfilesDir;
} else {
rootDir = srcDir;
}
const outputPath = path.join(out, path.relative(rootDir, inputPath));
// Always ensure that the target directory exists.
shx.mkdir('-p', path.dirname(outputPath));
fs.writeFileSync(outputPath, fileContent, 'utf-8');
}
/**
* Relativize the path where the file is written.
* @param f a path relative to the binDir, typically from a file in the deps[]
* @param c content of the file
* Copies a file into the package based on its input path, relativizing to the package path.
* @param inputPath a path relative to the binDir, typically from a file in the deps[]
*/
function writeBinFile(f: string, c: string) {
const outputPath = path.join(out, path.relative(binDir, f));
mkDirWriteFile(outputPath, c);
return outputPath;
function copyFileFromInputPath(inputPath: string) {
writeFileFromInputPath(inputPath, fs.readFileSync(inputPath, 'utf-8'));
}
/**
* Copy the file, relativizing the path.
* @param f a path relative to the binDir, typically from a file in the deps[]
*/
function copyBinFile(f: string) { writeBinFile(f, fs.readFileSync(f, 'utf-8')); }
/**
* Relativize the path where a file is written.
* @param file a path containing a re-rooted segment like .esm5 or .es6
@ -135,9 +153,13 @@ function main(args: string[]): number {
const content = fs.readFileSync(f, 'utf-8')
// Strip the named AMD module for compatibility with non-bazel users
.replace(/^\/\/\/ <amd-module name=.*\/>\n/, '');
const outputPath = writeBinFile(f, content);
writeFileFromInputPath(f, content);
});
// Copy all `data` files into the package. These are files that aren't built by the ng_package
// rule, but instead are just straight copied into the package, e.g. global CSS assets.
dataFiles.forEach(f => copyFileFromInputPath(f));
// Iterate through the entry point modules
// We do this first because we also record new paths for the esm5 and esm2015 copies
// of the index JS file, which we need to amend the package.json.
@ -149,11 +171,11 @@ function main(args: string[]): number {
moduleFiles['esm5_index'] = path.join(binDir, 'esm5', relative);
moduleFiles['esm2015_index'] = path.join(binDir, 'esm2015', relative);
writeBinFile(moduleFiles['esm5_index'], indexContent);
writeBinFile(moduleFiles['esm2015_index'], indexContent);
writeFileFromInputPath(moduleFiles['esm5_index'], indexContent);
writeFileFromInputPath(moduleFiles['esm2015_index'], indexContent);
copyBinFile(moduleFiles['typings']);
copyBinFile(moduleFiles['metadata']);
copyFileFromInputPath(moduleFiles['typings']);
copyFileFromInputPath(moduleFiles['metadata']);
});
// Root package name (e.g. '@angular/common'), captures as we iterate through sources below.
@ -161,6 +183,13 @@ function main(args: string[]): number {
const packagesWithExistingPackageJson = new Set<string>();
for (const src of srcs) {
if (src.includes(binDir) || src.includes(genfilesDir)) {
errorHasOccured = true;
console.error(
'The "srcs" for ng_package should not include output of other rules. Found:\n' +
` ${src}`);
}
let content = fs.readFileSync(src, 'utf-8');
// Modify package.json files as necessary for publishing
if (path.basename(src) === 'package.json') {
@ -177,7 +206,7 @@ function main(args: string[]): number {
rootPackageName = packageJson['name'];
}
}
writeSrcFile(src, content);
writeFileFromInputPath(src, content);
}
const licenseBanner = licenseFile ? fs.readFileSync(licenseFile, 'utf-8') : '';
@ -196,7 +225,7 @@ function main(args: string[]): number {
}
});
return 0;
return errorHasOccured ? 1 : 0;
/**
* Convert a binDir-relative path to srcDir-relative
@ -216,11 +245,6 @@ function main(args: string[]): number {
!f.endsWith(`.ngsummary${ext}`);
}
function mkDirWriteFile(p: string, content: string) {
shx.mkdir('-p', path.dirname(p));
fs.writeFileSync(p, content, 'utf-8');
}
function copyFile(file: string, baseDir: string, relative = '.') {
const dir = path.join(baseDir, relative);
shx.mkdir('-p', dir);
@ -240,6 +264,7 @@ function main(args: string[]): number {
* Inserts or edits properties into the package.json file(s) in the package so that
* they point to all the right generated artifacts.
*
* @param packageJson The path to the package.json file.
* @param parsedPackage Parsed package.json content
*/
function amendPackageJson(packageJson: string, parsedPackage: {[key: string]: string}) {
@ -298,13 +323,13 @@ function main(args: string[]): number {
/** Creates metadata re-export file for a secondary entry-point. */
function createMetadataReexportFile(entryPointName: string, metadataFile: string) {
const inputPath = path.join(srcDir, `${entryPointName}.metadata.json`);
writeSrcFile(inputPath, JSON.stringify({
writeFileFromInputPath(inputPath, JSON.stringify({
'__symbolic': 'module',
'version': 3,
'metadata': {},
'exports':
[{'from': `${srcDirRelative(inputPath, metadataFile.replace(/.metadata.json$/, ''))}`}],
'flatModuleIndexRedirect': true
'flatModuleIndexRedirect': true,
}) + '\n');
}
@ -317,18 +342,19 @@ function main(args: string[]): number {
const content = `${license}
export * from '${srcDirRelative(inputPath, typingsFile.replace(/\.d\.tsx?$/, ''))}';
`;
writeSrcFile(inputPath, content);
writeFileFromInputPath(inputPath, content);
}
/**
* Creates a package.json for a secondary entry-point.
* @param dir The directory under which the package.json should be written.
* @param entryPointPackageName The full package name for the entry point,
* e.g. '@angular/common/http'.
*/
function createEntryPointPackageJson(dir: string, entryPointPackageName: string) {
const pkgJson = path.join(srcDir, dir, 'package.json');
const content = amendPackageJson(pkgJson, {name: entryPointPackageName});
writeSrcFile(pkgJson, content);
writeFileFromInputPath(pkgJson, content);
}
}

View File

@ -15,9 +15,31 @@ ng_package(
"package.json",
"some-file.txt",
],
data = [
":arbitrary_bin_file",
":arbitrary_genfiles_file",
":extra-styles.css",
],
entry_point = "packages/bazel/test/ng_package/example/index.js",
deps = [
":example",
"//packages/bazel/test/ng_package/example/secondary",
],
)
# Use a genrule to create a file in bazel-genfiles to ensure that the genfiles output of
# a rule can be passed through to the `data` of ng_package.
genrule(
name = "arbitrary_genfiles_file",
outs = ["arbitrary_genfiles.txt"],
cmd = "echo Hello > $@",
)
# Use a genrule to create a file in bazel-bin to ensure that the bin output of
# a rule can be passed through to the `data` of ng_package.
genrule(
name = "arbitrary_bin_file",
outs = ["arbitrary_bin.txt"],
cmd = "echo World > $@",
output_to_bindir = True,
)

View File

@ -0,0 +1,3 @@
.special {
color: goldenrod;
}

View File

@ -1,3 +1,5 @@
arbitrary_bin.txt
arbitrary_genfiles.txt
bundles
bundles/example-secondary.umd.js
bundles/example-secondary.umd.js.map
@ -43,6 +45,7 @@ esm5
esm5/secondary/secondarymodule.ngsummary.js
example_public_index.d.ts
example_public_index.metadata.json
extra-styles.css
fesm2015
fesm2015/example.js
fesm2015/example.js.map
@ -65,6 +68,16 @@ secondary
secondary.d.ts
secondary.metadata.json
some-file.txt
--- arbitrary_bin.txt ---
World
--- arbitrary_genfiles.txt ---
Hello
--- bundles/example-secondary.umd.js ---
(function (global, factory) {
@ -661,6 +674,13 @@ export * from './index';
{"__symbolic":"module","version":4,"metadata":{"MyModule":{"__symbolic":"class","decorators":[{"__symbolic":"call","expression":{"__symbolic":"reference","module":"@angular/core","name":"NgModule","line":11,"character":1},"arguments":[{}]}],"members":{}}},"origins":{"MyModule":"./mymodule"},"importAs":"example"}
--- extra-styles.css ---
.special {
color: goldenrod;
}
--- fesm2015/example.js ---
import { NgModule } from '@angular/core';