diff --git a/src/material-examples/BUILD.bazel b/src/material-examples/BUILD.bazel index c7e841ef4986..8e48ad5c9e0c 100644 --- a/src/material-examples/BUILD.bazel +++ b/src/material-examples/BUILD.bazel @@ -1,8 +1,10 @@ package(default_visibility=["//visibility:public"]) -load("//:packages.bzl", "CDK_TARGETS", "MATERIAL_TARGETS", "ROLLUP_GLOBALS") +load("//:packages.bzl", "CDK_TARGETS", "MATERIAL_TARGETS", "ROLLUP_GLOBALS", "MATERIAL_PACKAGES", + "CDK_PACKAGES") load("//tools:defaults.bzl", "ng_module", "ng_package") load("//tools/highlight-files:index.bzl", "highlight_files") +load("//tools/package-docs-content:index.bzl", "package_docs_content") ng_module( name = "examples", @@ -21,17 +23,56 @@ ng_module( tsconfig = ":tsconfig-build.json", ) -highlight_files( - name = "highlighted-files", +filegroup( + name = "example-source-files", srcs = glob(["*/*.html", "*/*.css", "*/*.ts"]) ) +filegroup( + name = "material-overviews", + srcs = ["//src/lib/%s:overview" % name for name in MATERIAL_PACKAGES] +) + +filegroup( + name = "cdk-overviews", + srcs = ["//src/cdk/%s:overview" % name for name in CDK_PACKAGES] +) + +highlight_files( + name = "highlighted-source-files", + srcs = [":example-source-files"] +) + +package_docs_content( + name = "docs-content", + srcs = { + # We want to package the guides in to the docs content. These will be displayed + # in the documentation. + "//guides": "guides", + + # For the live-examples in our docs, we want to package the highlighted files + # into the docs content. These will be used to show the source code for examples. + ":highlighted-source-files": "examples-highlighted", + + # In order to be able to run examples in StackBlitz, we also want to package the + # plain source files into the docs-content. + ":example-source-files": "examples-source", + + # Package the overviews for "@angular/material" and "@angular/cdk" into the docs content + ":material-overviews": "overviews/material", + ":cdk-overviews": "overviews/cdk", + + # TODO(devversion): we need to also package the API html files here + } +) + ng_package( name = "npm_package", srcs = ["package.json"], entry_point = "src/material-examples/public_api.js", globals = ROLLUP_GLOBALS, deps = [":examples"], + data = [":docs-content"], # TODO(devversion): re-enable once we have set up the proper compiler for the ng_package tags = ["manual"], ) diff --git a/tools/package-docs-content/BUILD.bazel b/tools/package-docs-content/BUILD.bazel new file mode 100644 index 000000000000..ab2698f2d019 --- /dev/null +++ b/tools/package-docs-content/BUILD.bazel @@ -0,0 +1,20 @@ +package(default_visibility = ["//visibility:public"]) + +load("@build_bazel_rules_nodejs//:defs.bzl", "nodejs_binary") +load("//tools:defaults.bzl", "ts_library") + +nodejs_binary( + name = "package-docs-content", + entry_point = "angular_material/tools/package-docs-content/package-docs-content.js", + data = [ + "@matdeps//source-map-support", + ":sources", + ], +) + +ts_library( + name = "sources", + srcs = glob(["**/*.ts"]), + deps = ["@matdeps//@types/node"], + tsconfig = ":tsconfig.json", +) diff --git a/tools/package-docs-content/index.bzl b/tools/package-docs-content/index.bzl new file mode 100644 index 000000000000..9472b3e4281b --- /dev/null +++ b/tools/package-docs-content/index.bzl @@ -0,0 +1,82 @@ +""" + Implementation of the "package_docs_content" rule. The implementation runs the + packager executable in order to group all specified files into the given sections. +""" +def _package_docs_content(ctx): + # Directory that will contain all grouped input files. This directory will be created + # relatively to the current target package. (e.g. "bin/src/material-examples/docs-content") + output_dir = ctx.attr.name; + + # Arguments that will be passed to the packager executable. + args = ctx.actions.args() + + # List of outputs that should be generated by the packager action. Bazel will automatically + # throw an error if any output has not been generated properly. + expected_outputs = []; + + # Support passing arguments through a parameter file. This is necessary because on Windows + # there is an argument limit and we need to handle a large amount of input files. Bazel + # switches between parameter file and normal argument passing based on the operating system. + # Read more here: https://docs.bazel.build/versions/master/skylark/lib/Args.html#use_param_file + args.use_param_file(param_file_arg = "--param-file=%s") + + # Walk through each defined input target and the associated section and compute the + # output file which will be added to the executable arguments. + for input_target, section_name in ctx.attr.srcs.items(): + section_files = input_target.files.to_list() + + for input_file in section_files: + # For each input file, we want to create a copy that is stored in the output directory + # within its specified section. e.g. "pkg_bin/docs-content/guides/getting-started.html" + output_file = ctx.actions.declare_file( + "%s/%s/%s" % (output_dir, section_name, input_file.basename)) + + # Add the output file to the expected outputs so that Bazel throws an error if the file + # hasn't been generated properly. + expected_outputs += [output_file] + + # Pass the input file path and the output file path to the packager executable. We need + # to do this for each file because we cannot determine the general path to the output + # directory in a reliable way because Bazel targets cannot just "declare" a directory. + # See: https://docs.bazel.build/versions/master/skylark/lib/actions.html + args.add("%s,%s" % (input_file.path, output_file.path)) + + # Do nothing if there are no input files. Bazel will throw if we schedule an action + # that returns no outputs. + if not ctx.files.srcs: + return None + + # Run the packager executable that groups the specified source files and writes them + # to the given output directory. + ctx.actions.run( + inputs = ctx.files.srcs, + executable = ctx.executable._packager, + outputs = expected_outputs, + arguments = [args], + ) + + return DefaultInfo(files = depset(expected_outputs)) + +""" + Rule definition for the "package_docs_content" rule that can accept arbritary source files + that will be grouped into specified sections. This is being used to package the docs + content into a desired folder structure that can be shared with the docs application. +""" +package_docs_content = rule( + implementation = _package_docs_content, + attrs = { + # This defines the sources for the "package_docs_content" rule. Instead of just + # accepting a list of labels, this rule requires the developer to specify a label + # keyed dictionary. This allows developers to specify where specific targets + # should be grouped into. This helpful when publishing the docs content because + # the docs repository does not about the directory structure of the generated files. + "srcs": attr.label_keyed_string_dict(allow_files = True), + + # Executable for this rule that is responsible for packaging the specified + # targets into the associated sections. + "_packager": attr.label( + default = Label("//tools/package-docs-content"), + executable = True, + cfg = "host" + )}, +) diff --git a/tools/package-docs-content/package-docs-content.ts b/tools/package-docs-content/package-docs-content.ts new file mode 100644 index 000000000000..9354950124d6 --- /dev/null +++ b/tools/package-docs-content/package-docs-content.ts @@ -0,0 +1,38 @@ +/** + * Script that will be dispatched by the "package_docs_content" rule and is responsible for + * copying input files to a new location. The new location will be computed within the Bazel + * rule implementation so that we don't need to compute the output paths with their sections + * multiple times. + */ + +import {readFileSync, writeFileSync} from 'fs'; + +/** + * Determines the command line arguments for the current Bazel action. Since this action can + * have a large set of input files, Bazel may write the arguments into a parameter file. + * This function is responsible for handling normal argument passing or Bazel parameter files. + * Read more here: https://docs.bazel.build/versions/master/skylark/lib/Args.html#use_param_file + */ +function getBazelActionArguments() { + const args = process.argv.slice(2); + + // If Bazel uses a parameter file, we've specified that it passes the file in the following + // format: "arg0 arg1 --param-file={path_to_param_file}" + if (args[0].startsWith('--param-file=')) { + return readFileSync(args[0].split('=')[1], 'utf8').trim().split('\n'); + } + + return args; +} + +if (require.main === module) { + // The script expects the output path as first argument. All remaining arguments will be + // considered as markdown input files that need to be transformed. + getBazelActionArguments().forEach(argument => { + // Each argument that has been passed consists of an input file path and the expected + // output path. e.g. {path_to_input_file},{expected_output_path} + const [inputFilePath, outputPath] = argument.split(',', 2); + + writeFileSync(outputPath, readFileSync(inputFilePath, 'utf8')); + }); +} diff --git a/tools/package-docs-content/tsconfig.json b/tools/package-docs-content/tsconfig.json new file mode 100644 index 000000000000..13dba047bbcf --- /dev/null +++ b/tools/package-docs-content/tsconfig.json @@ -0,0 +1,12 @@ +{ + "compilerOptions": { + "lib": ["es2015"], + "module": "commonjs", + "target": "es5", + "sourceMap": true, + "types": ["node"] + }, + "bazelOptions": { + "suppressTsconfigOverrideWarnings": true + } +}