diff --git a/WORKSPACE b/WORKSPACE
index ba15d08389..57803226a9 100644
--- a/WORKSPACE
+++ b/WORKSPACE
@@ -3,7 +3,7 @@ load("@bazel_tools//tools/build_defs/repo:git.bzl", "git_repository")
git_repository(
name = "build_bazel_rules_typescript",
remote = "https://github.com/bazelbuild/rules_typescript.git",
- tag = "0.0.5",
+ tag = "0.0.6",
)
load("@build_bazel_rules_typescript//:defs.bzl", "node_repositories")
diff --git a/build.sh b/build.sh
index 293bcf729e..b587140875 100755
--- a/build.sh
+++ b/build.sh
@@ -327,6 +327,16 @@ mapSources() {
fi
}
+updateVersionReferences() {
+ NPM_DIR="$1"
+ (
+ echo "====== VERSION: Updating version references in ${NPM_DIR}"
+ cd ${NPM_DIR}
+ echo "====== EXECUTE: perl -p -i -e \"s/0\.0\.0\-PLACEHOLDER/${VERSION}/g\" $""(grep -ril 0\.0\.0\-PLACEHOLDER .)"
+ perl -p -i -e "s/0\.0\.0\-PLACEHOLDER/${VERSION}/g" $(grep -ril 0\.0\.0\-PLACEHOLDER .) < /dev/null 2> /dev/null
+ )
+}
+
VERSION="${VERSION_PREFIX}${VERSION_SUFFIX}"
echo "====== BUILDING: Version ${VERSION}"
@@ -419,11 +429,15 @@ if [[ ${BUILD_TOOLS} == true || ${BUILD_ALL} == true ]]; then
$(npm bin)/tsc -p packages/tsc-wrapped/tsconfig-build.json
cp ./packages/tsc-wrapped/package.json ./dist/packages-dist/tsc-wrapped
cp ./packages/tsc-wrapped/README.md ./dist/packages-dist/tsc-wrapped
- (
- cd dist/packages-dist/tsc-wrapped
- echo "====== EXECUTE: perl -p -i -e \"s/0\.0\.0\-PLACEHOLDER/${VERSION}/g\" $""(grep -ril 0\.0\.0\-PLACEHOLDER .)"
- perl -p -i -e "s/0\.0\.0\-PLACEHOLDER/${VERSION}/g" $(grep -ril 0\.0\.0\-PLACEHOLDER .) < /dev/null 2> /dev/null
- )
+ updateVersionReferences dist/packages-dist/tsc-wrapped
+
+ rsync -a packages/bazel/ ./dist/packages-dist/bazel
+ # Re-write nodejs import paths
+ perl -p -i -e "s#__main__/packages/bazel#angular#g" $(grep -ril __main__ dist/packages-dist/bazel) < /dev/null 2> /dev/null
+ # Remove BEGIN-INTERNAL...END-INTERAL blocks
+ # https://stackoverflow.com/questions/24175271/how-can-i-match-multi-line-patterns-in-the-command-line-with-perl-style-regex
+ perl -0777 -n -i -e "s/(?m)^.*BEGIN-INTERNAL[\w\W]*END-INTERNAL.*\n//g; print" $(grep -ril BEGIN-INTERNAL dist/packages-dist/bazel) < /dev/null 2> /dev/null
+ updateVersionReferences dist/packages-dist/bazel
fi
for PACKAGE in ${PACKAGES[@]}
@@ -489,12 +503,7 @@ do
if [[ -d ${NPM_DIR} ]]; then
- (
- echo "====== VERSION: Updating version references"
- cd ${NPM_DIR}
- echo "====== EXECUTE: perl -p -i -e \"s/0\.0\.0\-PLACEHOLDER/${VERSION}/g\" $""(grep -ril 0\.0\.0\-PLACEHOLDER .)"
- perl -p -i -e "s/0\.0\.0\-PLACEHOLDER/${VERSION}/g" $(grep -ril 0\.0\.0\-PLACEHOLDER .) < /dev/null 2> /dev/null
- )
+ updateVersionReferences ${NPM_DIR}
fi
travisFoldEnd "build package: ${PACKAGE}"
diff --git a/integration/bazel/BUILD.bazel b/integration/bazel/BUILD.bazel
new file mode 100644
index 0000000000..629c7aae5a
--- /dev/null
+++ b/integration/bazel/BUILD.bazel
@@ -0,0 +1,10 @@
+package(default_visibility = ["//visibility:public"])
+
+filegroup(
+ name = "node_modules",
+ srcs = glob([
+ "node_modules/**/*.js",
+ "node_modules/**/*.d.ts",
+ "node_modules/**/*.json",
+ ])
+)
diff --git a/integration/bazel/WORKSPACE b/integration/bazel/WORKSPACE
new file mode 100644
index 0000000000..57e7580e73
--- /dev/null
+++ b/integration/bazel/WORKSPACE
@@ -0,0 +1,24 @@
+load("@bazel_tools//tools/build_defs/repo:git.bzl", "git_repository")
+
+git_repository(
+ name = "build_bazel_rules_typescript",
+ remote = "https://github.com/bazelbuild/rules_typescript.git",
+ tag = "0.0.6",
+)
+load("@build_bazel_rules_typescript//:defs.bzl", "node_repositories")
+node_repositories(package_json = "//:package.json")
+
+local_repository(
+ name = "angular",
+ path = "node_modules/@angular/bazel"
+)
+
+git_repository(
+ name = "io_bazel_rules_sass",
+ remote = "https://github.com/bazelbuild/rules_sass.git",
+ tag = "0.0.2",
+)
+
+load("@io_bazel_rules_sass//sass:sass.bzl", "sass_repositories")
+
+sass_repositories()
\ No newline at end of file
diff --git a/integration/bazel/angular.tsconfig.json b/integration/bazel/angular.tsconfig.json
new file mode 100644
index 0000000000..b080935ebf
--- /dev/null
+++ b/integration/bazel/angular.tsconfig.json
@@ -0,0 +1,21 @@
+// WORKAROUND https://github.com/angular/angular/issues/18810
+// This file is required to run ngc on angular libraries, to write files like
+// node_modules/@angular/core/core.ngsummary.json
+{
+ "compilerOptions": {
+ "lib": [
+ "dom",
+ "es2015"
+ ],
+ "experimentalDecorators": true,
+ "types": []
+ },
+ "include": [
+ "node_modules/@angular/**/*"
+ ],
+ "exclude": [
+ "node_modules/@angular/bazel/**",
+ "node_modules/@angular/compiler-cli/**",
+ "node_modules/@angular/tsc-wrapped/**"
+ ]
+}
diff --git a/integration/bazel/package.json b/integration/bazel/package.json
new file mode 100644
index 0000000000..716f073811
--- /dev/null
+++ b/integration/bazel/package.json
@@ -0,0 +1,26 @@
+{
+ "name": "angular-bazel",
+ "description": "example and integration test for building Angular apps with Bazel",
+ "version": "0.0.0",
+ "license": "MIT",
+ "dependencies": {
+ "@angular/animations": "file:../../dist/packages-dist/animations",
+ "@angular/common": "file:../../dist/packages-dist/common",
+ "@angular/compiler": "file:../../dist/packages-dist/compiler",
+ "@angular/core": "file:../../dist/packages-dist/core",
+ "@angular/platform-browser": "file:../../dist/packages-dist/platform-browser",
+ "rxjs": "5.3.1",
+ "zone.js": "0.8.6"
+ },
+ "devDependencies": {
+ "@angular/bazel": "file:../../dist/packages-dist/bazel",
+ "@angular/compiler-cli": "file:../../dist/packages-dist/compiler-cli",
+ "@types/node": "^7.0.18",
+ "protobufjs": "5.0.0",
+ "typescript": "~2.3.1"
+ },
+ "scripts": {
+ "postinstall": "ngc -p angular.tsconfig.json",
+ "test": "bazel build ..."
+ }
+}
\ No newline at end of file
diff --git a/integration/bazel/src/BUILD.bazel b/integration/bazel/src/BUILD.bazel
new file mode 100644
index 0000000000..3451c3ba90
--- /dev/null
+++ b/integration/bazel/src/BUILD.bazel
@@ -0,0 +1,11 @@
+load("@angular//:index.bzl", "ng_module")
+
+# Allow targets under sub-packages to reference the tsconfig.json file
+exports_files(["tsconfig.json"])
+
+ng_module(
+ name = "app",
+ srcs = ["app.module.ts"],
+ deps = ["//src/hello-world"],
+ tsconfig = ":tsconfig.json",
+)
\ No newline at end of file
diff --git a/integration/bazel/src/app.module.ts b/integration/bazel/src/app.module.ts
new file mode 100644
index 0000000000..14e2760d75
--- /dev/null
+++ b/integration/bazel/src/app.module.ts
@@ -0,0 +1,9 @@
+import {HelloWorldModule} from './hello-world/hello-world.module';
+
+import {NgModule} from '@angular/core';
+import {BrowserModule} from '@angular/platform-browser';
+
+@NgModule({
+ imports: [BrowserModule, HelloWorldModule]
+})
+export class AppModule {}
diff --git a/integration/bazel/src/hello-world/BUILD.bazel b/integration/bazel/src/hello-world/BUILD.bazel
new file mode 100644
index 0000000000..e0cb4a41ab
--- /dev/null
+++ b/integration/bazel/src/hello-world/BUILD.bazel
@@ -0,0 +1,19 @@
+package(default_visibility = ["//visibility:public"])
+load("@angular//:index.bzl", "ng_module")
+load("@io_bazel_rules_sass//sass:sass.bzl", "sass_binary")
+
+sass_binary(
+ name = "styles",
+ src = "hello-world.component.scss",
+ deps = [
+ "//src/shared:colors",
+ "//src/shared:fonts",
+ ],
+)
+
+ng_module(
+ name = "hello-world",
+ srcs = glob(["*.ts"]),
+ tsconfig = "//src:tsconfig.json",
+ assets = [":styles"],
+)
diff --git a/integration/bazel/src/hello-world/hello-world.component.scss b/integration/bazel/src/hello-world/hello-world.component.scss
new file mode 100644
index 0000000000..2db21ad42e
--- /dev/null
+++ b/integration/bazel/src/hello-world/hello-world.component.scss
@@ -0,0 +1,12 @@
+@import "src/shared/fonts";
+@import "src/shared/colors";
+
+html {
+ body {
+ font-family: $default-font-stack;
+ h1 {
+ font-family: $modern-font-stack;
+ color: $example-red;
+ }
+ }
+}
diff --git a/integration/bazel/src/hello-world/hello-world.component.ts b/integration/bazel/src/hello-world/hello-world.component.ts
new file mode 100644
index 0000000000..75eb306a32
--- /dev/null
+++ b/integration/bazel/src/hello-world/hello-world.component.ts
@@ -0,0 +1,15 @@
+
+import {Component, NgModule} from '@angular/core';
+
+@Component({
+ selector: 'hello-world-app',
+ template: `
+
Hello {{ name }}!
+
+ `,
+ // TODO: might be better to point to .scss so this looks valid at design-time
+ styleUrls: ['./styles.css']
+})
+export class HelloWorldComponent {
+ name: string = 'world';
+}
diff --git a/integration/bazel/src/hello-world/hello-world.module.ts b/integration/bazel/src/hello-world/hello-world.module.ts
new file mode 100644
index 0000000000..ceb62649a4
--- /dev/null
+++ b/integration/bazel/src/hello-world/hello-world.module.ts
@@ -0,0 +1,8 @@
+import {HelloWorldComponent} from './hello-world.component';
+import {NgModule} from '@angular/core';
+
+@NgModule({
+ declarations: [HelloWorldComponent],
+ bootstrap: [HelloWorldComponent],
+})
+export class HelloWorldModule {}
diff --git a/integration/bazel/src/shared/BUILD.bazel b/integration/bazel/src/shared/BUILD.bazel
new file mode 100644
index 0000000000..488025b1ab
--- /dev/null
+++ b/integration/bazel/src/shared/BUILD.bazel
@@ -0,0 +1,13 @@
+package(default_visibility = ["//visibility:public"])
+
+load("@io_bazel_rules_sass//sass:sass.bzl", "sass_library")
+
+sass_library(
+ name = "colors",
+ srcs = ["_colors.scss"],
+)
+
+sass_library(
+ name = "fonts",
+ srcs = ["_fonts.scss"],
+)
diff --git a/integration/bazel/src/shared/_colors.scss b/integration/bazel/src/shared/_colors.scss
new file mode 100644
index 0000000000..7584a9b844
--- /dev/null
+++ b/integration/bazel/src/shared/_colors.scss
@@ -0,0 +1,2 @@
+$example-blue: #0000ff;
+$example-red: #ff0000;
diff --git a/integration/bazel/src/shared/_fonts.scss b/integration/bazel/src/shared/_fonts.scss
new file mode 100644
index 0000000000..d17c15dc1d
--- /dev/null
+++ b/integration/bazel/src/shared/_fonts.scss
@@ -0,0 +1,2 @@
+$default-font-stack: Cambria, "Hoefler Text", Utopia, "Liberation Serif", "Nimbus Roman No9 L Regular", Times, "Times New Roman", serif;
+$modern-font-stack: Constantia, "Lucida Bright", Lucidabright, "Lucida Serif", Lucida, "DejaVu Serif", "Bitstream Vera Serif", "Liberation Serif", Georgia, serif;
diff --git a/integration/bazel/src/tsconfig.json b/integration/bazel/src/tsconfig.json
new file mode 100644
index 0000000000..068cd10fcc
--- /dev/null
+++ b/integration/bazel/src/tsconfig.json
@@ -0,0 +1,12 @@
+{
+ "compilerOptions": {
+ "experimentalDecorators": true,
+ "lib": [
+ "dom",
+ "es5",
+ "es2015.collection",
+ "es2015.iterable",
+ "es2015.promise"
+ ]
+ }
+}
\ No newline at end of file
diff --git a/packages/bazel/WORKSPACE b/packages/bazel/WORKSPACE
index c0c1418247..873e7584e7 100644
--- a/packages/bazel/WORKSPACE
+++ b/packages/bazel/WORKSPACE
@@ -13,7 +13,7 @@ load("@bazel_tools//tools/build_defs/repo:git.bzl", "git_repository")
git_repository(
name = "build_bazel_rules_typescript",
remote = "https://github.com/bazelbuild/rules_typescript.git",
- tag = "0.0.5",
+ tag = "0.0.6",
)
load("@build_bazel_rules_typescript//:defs.bzl", "node_repositories")
diff --git a/packages/bazel/src/ng_module.bzl b/packages/bazel/src/ng_module.bzl
index 16d66c3a74..13629865b3 100644
--- a/packages/bazel/src/ng_module.bzl
+++ b/packages/bazel/src/ng_module.bzl
@@ -3,72 +3,113 @@
# Use of this source code is governed by an MIT-style license that can be
# found in the LICENSE file at https://angular.io/license
-load("@build_bazel_rules_typescript//internal:build_defs.bzl", "tsc_wrapped_tsconfig")
-
-load(
- "@build_bazel_rules_typescript//internal:common/compilation.bzl",
- "COMMON_ATTRIBUTES", "compile_ts", "ts_providers_dict_to_struct"
+load(":rules_typescript.bzl",
+ "tsc_wrapped_tsconfig",
+ "COMMON_ATTRIBUTES",
+ "compile_ts",
+ "DEPS_ASPECTS",
+ "ts_providers_dict_to_struct",
+ "json_marshal",
)
-load("@build_bazel_rules_typescript//internal:common/json_marshal.bzl", "json_marshal")
-
# Calculate the expected output of the template compiler for every source in
# in the library. Most of these will be produced as empty files but it is
# unknown, without parsing, which will be empty.
-def _expected_outs(ctx):
- result = []
+def _expected_outs(ctx, label):
+ devmode_js_files = []
+ closure_js_files = []
+ declaration_files = []
+ summary_files = []
+
+ codegen_inputs = ctx.files.srcs
+
+ for src in ctx.files.srcs + ctx.files.assets:
+ if src.short_path.endswith(".ts") and not src.short_path.endswith(".d.ts"):
+ basename = src.short_path[len(ctx.label.package) + 1:-len(".ts")]
+ devmode_js = [
+ ".ngfactory.js",
+ ".ngsummary.js",
+ ".js",
+ ]
+ summaries = [".ngsummary.json"]
- for src in ctx.files.srcs:
- if src.short_path.endswith(".ts"):
- basename = src.short_path[len(ctx.label.package) + 1:-3]
- result += [ctx.new_file(ctx.bin_dir, basename + ext) for ext in [
- ".ngfactory.js",
- ".ngfactory.d.ts",
- ".ngsummary.js",
- ".ngsummary.d.ts",
- ".ngsummary.json",
- ]]
elif src.short_path.endswith(".css"):
- basename = src.short_path[len(ctx.label.package) + 1:-4]
- result += [ctx.new_file(ctx.bin_dir, basename + ext) for ext in [
- ".css.shim.ngstyle.js",
- ".css.shim.ngstyle.d.ts",
- ".css.ngstyle.js",
- ".css.ngstyle.d.ts",
- ]]
- return result
+ basename = src.short_path[len(ctx.label.package) + 1:-len(".css")]
+ devmode_js = [
+ ".css.shim.ngstyle.js",
+ ".css.ngstyle.js",
+ ]
+ summaries = []
+
+ closure_js = [f.replace(".js", ".closure.js") for f in devmode_js]
+ declarations = [f.replace(".js", ".d.ts") for f in devmode_js]
+
+ devmode_js_files += [ctx.new_file(ctx.bin_dir, basename + ext) for ext in devmode_js]
+ closure_js_files += [ctx.new_file(ctx.bin_dir, basename + ext) for ext in closure_js]
+ declaration_files += [ctx.new_file(ctx.bin_dir, basename + ext) for ext in declarations]
+ summary_files += [ctx.new_file(ctx.bin_dir, basename + ext) for ext in summaries]
+
+ return struct(
+ closure_js = closure_js_files,
+ devmode_js = devmode_js_files,
+ declarations = declaration_files,
+ summaries = summary_files,
+ )
def _ngc_tsconfig(ctx, files, srcs, **kwargs):
+ outs = _expected_outs(ctx, ctx.label)
+ if "devmode_manifest" in kwargs:
+ expected_outs = outs.devmode_js + outs.declarations + outs.summaries
+ else:
+ expected_outs = outs.closure_js
+
return dict(tsc_wrapped_tsconfig(ctx, files, srcs, **kwargs), **{
"angularCompilerOptions": {
- "expectedOut": [o.path for o in _expected_outs(ctx)],
+ "generateCodeForLibraries": False,
+ # FIXME: wrong place to de-dupe
+ "expectedOut": depset([o.path for o in expected_outs]).to_list()
}
})
+def _collect_summaries_aspect_impl(target, ctx):
+ results = target.angular.summaries if hasattr(target, "angular") else depset()
+
+ # If we are visiting empty-srcs ts_library, this is a re-export
+ srcs = target.srcs if hasattr(target, "srcs") else []
+
+ # "re-export" rules should expose all the files of their deps
+ if not srcs:
+ for dep in ctx.rule.attr.deps:
+ if (hasattr(dep, "angular")):
+ results += dep.angular.summaries
+
+ return struct(collect_summaries_aspect_result = results)
+
+_collect_summaries_aspect = aspect(
+ implementation = _collect_summaries_aspect_impl,
+ attr_aspects = ["deps"],
+)
+
def _compile_action(ctx, inputs, outputs, config_file_path):
- externs_files = []
- non_externs_files = []
- for output in outputs:
- if output.basename.endswith(".es5.MF"):
- ctx.file_action(output, content="")
- else:
- non_externs_files.append(output)
+ summaries = depset()
+ for dep in ctx.attr.deps:
+ if hasattr(dep, "collect_summaries_aspect_result"):
+ summaries += dep.collect_summaries_aspect_result
- # TODO(alexeagle): For now we mock creation of externs files
- for externs_file in externs_files:
- ctx.file_action(output=externs_file, content="")
+ action_inputs = inputs + summaries.to_list() + ctx.files.assets
+ # print("ASSETS", [a.path for a in ctx.files.assets])
+ # print("INPUTS", ctx.label, [o.path for o in summaries if o.path.find("core/src") > 0])
- action_inputs = inputs
if hasattr(ctx.attr, "node_modules"):
action_inputs += [f for f in ctx.files.node_modules
if f.path.endswith(".ts") or f.path.endswith(".json")]
- if ctx.file.tsconfig:
+ if hasattr(ctx.attr, "tsconfig") and ctx.file.tsconfig:
action_inputs += [ctx.file.tsconfig]
# One at-sign makes this a params-file, enabling the worker strategy.
# Two at-signs escapes the argument so it's passed through to ngc
# rather than the contents getting expanded.
- if ctx.attr.supports_workers:
+ if ctx.attr._supports_workers:
arguments = ["@@" + config_file_path]
else:
arguments = ["-p", config_file_path]
@@ -77,79 +118,75 @@ def _compile_action(ctx, inputs, outputs, config_file_path):
progress_message = "Compiling Angular templates (ngc) %s" % ctx.label,
mnemonic = "AngularTemplateCompile",
inputs = action_inputs,
- outputs = non_externs_files,
+ outputs = outputs,
arguments = arguments,
executable = ctx.executable.compiler,
execution_requirements = {
- "supports-workers": str(int(ctx.attr.supports_workers)),
+ "supports-workers": str(int(ctx.attr._supports_workers)),
},
)
+def _prodmode_compile_action(ctx, inputs, outputs, config_file_path):
+ outs = _expected_outs(ctx, ctx.label)
+ _compile_action(ctx, inputs, outputs + outs.closure_js, config_file_path)
+
def _devmode_compile_action(ctx, inputs, outputs, config_file_path):
- # TODO(alexeagle): compile for feeding to Closure Compiler
- _compile_action(ctx, inputs, outputs + _expected_outs(ctx), config_file_path)
+ outs = _expected_outs(ctx, ctx.label)
+ _compile_action(ctx, inputs, outputs + outs.devmode_js + outs.declarations + outs.summaries, config_file_path)
-def _compile_ng(ctx):
- declarations = []
- for dep in ctx.attr.deps:
- if hasattr(dep, "typescript"):
- declarations += dep.typescript.transitive_declarations
+def ng_module_impl(ctx, ts_compile_actions):
+ providers = ts_compile_actions(
+ ctx, is_library=True, compile_action=_prodmode_compile_action,
+ devmode_compile_action=_devmode_compile_action,
+ tsc_wrapped_tsconfig=_ngc_tsconfig,
+ outputs = _expected_outs)
- tsconfig_json = ctx.new_file(ctx.label.name + "_tsconfig.json")
- ctx.file_action(output=tsconfig_json, content=json_marshal(
- _ngc_tsconfig(ctx, ctx.files.srcs + declarations, ctx.files.srcs)))
-
- _devmode_compile_action(ctx, ctx.files.srcs + declarations + [tsconfig_json], [], tsconfig_json.path)
-
- return {
- "files": depset(_expected_outs(ctx)),
- "typescript": {
- # FIXME: expose the right outputs so this looks like a ts_library
- "declarations": [],
- "transitive_declarations": [],
- "type_blacklisted_declarations": [],
- },
+ #addl_declarations = [_expected_outs(ctx)]
+ #providers["typescript"]["declarations"] += addl_declarations
+ #providers["typescript"]["transitive_declarations"] += addl_declarations
+ providers["angular"] = {
+ "summaries": _expected_outs(ctx, ctx.label).summaries
}
+ return providers
+
def _ng_module_impl(ctx):
- if ctx.attr.write_ng_outputs_only:
- ts_providers = _compile_ng(ctx)
- else:
- ts_providers = compile_ts(ctx, is_library=True,
- compile_action=_compile_action,
- devmode_compile_action=_devmode_compile_action,
- tsc_wrapped_tsconfig=_ngc_tsconfig)
+ return ts_providers_dict_to_struct(ng_module_impl(ctx, compile_ts))
- addl_declarations = [o for o in _expected_outs(ctx) if o.path.endswith(".d.ts")]
- ts_providers["typescript"]["declarations"] += addl_declarations
- ts_providers["typescript"]["transitive_declarations"] += addl_declarations
+NG_MODULE_ATTRIBUTES = {
+ "srcs": attr.label_list(allow_files = [".ts"]),
- return ts_providers_dict_to_struct(ts_providers)
+ "deps": attr.label_list(aspects = DEPS_ASPECTS + [_collect_summaries_aspect]),
+ "assets": attr.label_list(allow_files = [
+ ".css",
+ # TODO(alexeagle): change this to ".ng.html" when usages updated
+ ".html",
+ ]),
+
+ # TODO(alexeagle): wire up when we have i18n in bazel
+ "no_i18n": attr.bool(default = False),
+
+ "compiler": attr.label(
+ default = Label("//src/ngc-wrapped"),
+ executable = True,
+ cfg = "host",
+ ),
+
+ # TODO(alexeagle): enable workers for ngc
+ "_supports_workers": attr.bool(default = False),
+}
ng_module = rule(
implementation = _ng_module_impl,
- attrs = dict(COMMON_ATTRIBUTES, **{
- "srcs": attr.label_list(allow_files = True),
-
- # To be used only to bootstrap @angular/core compilation,
- # since we want to compile @angular/core with ngc, but ngc depends on
- # @angular/core typescript output.
- "write_ng_outputs_only": attr.bool(default = False),
+ attrs = COMMON_ATTRIBUTES + NG_MODULE_ATTRIBUTES + {
"tsconfig": attr.label(allow_files = True, single_file = True),
- "no_i18n": attr.bool(default = False),
- # TODO(alexeagle): enable workers for ngc
- "supports_workers": attr.bool(default = False),
- "compiler": attr.label(
- default = Label("//internal/ngc"),
- executable = True,
- cfg = "host",
- ),
+
# @// is special syntax for the "main" repository
# The default assumes the user specified a target "node_modules" in their
# root BUILD file.
"node_modules": attr.label(
default = Label("@//:node_modules")
),
- }),
+ },
)
\ No newline at end of file
diff --git a/packages/bazel/src/ngc-wrapped/BUILD.bazel b/packages/bazel/src/ngc-wrapped/BUILD.bazel
index 00dc24e7e2..b5f1368870 100644
--- a/packages/bazel/src/ngc-wrapped/BUILD.bazel
+++ b/packages/bazel/src/ngc-wrapped/BUILD.bazel
@@ -6,9 +6,14 @@ ts_library(
name = "ngc_lib",
srcs = ["index.ts"],
deps = [
+ # BEGIN-INTERNAL
+ # Only needed when compiling within the Angular repo.
+ # Users will get this dependency from node_modules.
"//packages/compiler-cli",
+ # END-INTERNAL
"@build_bazel_rules_typescript//internal/tsc_wrapped"
],
+ tsconfig = ":tsconfig.json",
)
nodejs_binary(
diff --git a/packages/bazel/src/ngc-wrapped/index.ts b/packages/bazel/src/ngc-wrapped/index.ts
index 9fc36702a9..a5d26bfeda 100644
--- a/packages/bazel/src/ngc-wrapped/index.ts
+++ b/packages/bazel/src/ngc-wrapped/index.ts
@@ -5,28 +5,185 @@
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
-
-// TODO(chuckj): Remove the requirement for a fake 'reflect` implementation from
+// TODO(chuckj): Remove the requirment for a fake 'reflect` implementation from
// the compiler
-import 'reflect-metadata';
+import 'reflect-metadata'; // from //third_party/javascript/node_modules/reflect_decorators:ts
-import {calcProjectFileAndBasePath, createNgCompilerOptions, formatDiagnostics, performCompilation} from '@angular/compiler-cli';
+import * as ng from '@angular/compiler-cli';
import * as fs from 'fs';
import * as path from 'path';
-// Note, the tsc_wrapped module comes from rules_typescript, not from @angular/tsc-wrapped
-import {parseTsconfig} from 'tsc_wrapped';
+// Note, the tsc_wrapped module comes from rules_typescript, not from npm
+import {CompilerHost, UncachedFileLoader, parseTsconfig} from 'tsc_wrapped';
+import * as tsickle from 'tsickle';
import * as ts from 'typescript';
-function main(args: string[]) {
+const EXT = /(\.ts|\.d\.ts|\.js|\.jsx|\.tsx)$/;
+// FIXME: we should be able to add the assets to the tsconfig so FileLoader
+// knows about them
+const NGC_NON_TS_INPUTS =
+ /(\.(ngsummary|ngstyle|ngfactory)(\.d)?\.ts|\.ngsummary\.json|\.css|\.html)$/;
+// FIXME should need only summary, css, html
+
+function topologicalSort(
+ result: tsickle.FileMap, current: string, modulesManifest: tsickle.ModulesManifest,
+ visiting: tsickle.FileMap) {
+ const referencedModules = modulesManifest.getReferencedModules(current);
+ if (!referencedModules) return; // not in the local set of sources.
+ for (const referencedModule of referencedModules) {
+ const referencedFileName = modulesManifest.getFileNameFromModule(referencedModule);
+ if (!referencedFileName) continue; // Ambient modules.
+ if (!result[referencedFileName]) {
+ if (visiting[referencedFileName]) {
+ const path = current + ' -> ' + Object.keys(visiting).join(' -> ');
+ throw new Error('Cyclical dependency between files:\n' + path);
+ }
+ visiting[referencedFileName] = true;
+ topologicalSort(result, referencedFileName, modulesManifest, visiting);
+ delete visiting[referencedFileName];
+ }
+ }
+ result[current] = true;
+}
+// TODO(alexeagle): move to tsc-wrapped in third_party so it's shared
+export function constructManifest(
+ modulesManifest: tsickle.ModulesManifest,
+ host: {flattenOutDir: (f: string) => string}): string {
+ const result: tsickle.FileMap = {};
+ for (const file of modulesManifest.fileNames) {
+ topologicalSort(result, file, modulesManifest, {});
+ }
+
+ // NB: The object literal maintains insertion order.
+ return Object.keys(result).map(fn => host.flattenOutDir(fn)).join('\n') + '\n';
+}
+
+export function main(args) {
const project = args[1];
const [{options: tsOptions, bazelOpts, files, config}] = parseTsconfig(project);
- const {basePath} = calcProjectFileAndBasePath(project);
- const ngOptions = createNgCompilerOptions(basePath, config, tsOptions);
- const {diagnostics} = performCompilation({rootNames: files, options: ngOptions});
- if (diagnostics.length) {
- console.error(formatDiagnostics(ngOptions, diagnostics));
+ const {basePath} = ng.calcProjectFileAndBasePath(project);
+ const ngOptions = ng.createNgCompilerOptions(basePath, config, tsOptions);
+ if (!bazelOpts.es5Mode) {
+ ngOptions.annotateForClosureCompiler = true;
+ ngOptions.annotationsAs = 'static fields';
}
+
+ function relativeToRootDir(filePath: string): string {
+ if (tsOptions.rootDir) {
+ const rel = path.relative(tsOptions.rootDir, filePath);
+ if (rel.indexOf('.') != 0) return rel;
+ }
+ return filePath;
+ }
+ const expectedOuts = [...config['angularCompilerOptions']['expectedOut']];
+ const tsHost = ts.createCompilerHost(tsOptions, true);
+
+ const originalWriteFile = tsHost.writeFile.bind(tsHost);
+ tsHost.writeFile =
+ (fileName: string, content: string, writeByteOrderMark: boolean,
+ onError?: (message: string) => void, sourceFiles?: ts.SourceFile[]) => {
+ const relative = relativeToRootDir(fileName);
+ const expectedIdx = expectedOuts.findIndex(o => o === relative);
+ if (expectedIdx >= 0) {
+ expectedOuts.splice(expectedIdx, 1);
+ originalWriteFile(fileName, content, writeByteOrderMark, onError, sourceFiles);
+ }
+ };
+
+
+ // Patch fileExists when resolving modules, so that ngc can ask TypeScript to
+ // resolve non-existing generated files that don't exist on disk, but are
+ // synthetic and added to the `programWithStubs` based on real inputs.
+ const generatedFileModuleResolverHost = Object.create(tsHost);
+ generatedFileModuleResolverHost.fileExists = (fileName: string) => {
+ const match = /^(.*?)\.(ngfactory|ngsummary|ngstyle|shim\.ngstyle)(.*)$/.exec(fileName);
+ if (match) {
+ const [, file, suffix, ext] = match;
+ // Performance: skip looking for files other than .d.ts or .ts
+ if (ext !== '.ts' && ext !== '.d.ts') return false;
+ if (suffix.indexOf('ngstyle') >= 0) {
+ // Look for foo.css on disk
+ fileName = file;
+ } else {
+ // Look for foo.d.ts or foo.ts on disk
+ fileName = file + (ext || '');
+ }
+ }
+ return tsHost.fileExists(fileName);
+ };
+
+ function generatedFileModuleResolver(
+ moduleName: string, containingFile: string,
+ compilerOptions: ts.CompilerOptions): ts.ResolvedModuleWithFailedLookupLocations {
+ return ts.resolveModuleName(
+ moduleName, containingFile, compilerOptions, generatedFileModuleResolverHost);
+ }
+
+ const bazelHost = new CompilerHost(
+ files, tsOptions, bazelOpts, tsHost, new UncachedFileLoader(), generatedFileModuleResolver);
+ bazelHost.allowNonHermeticRead = (filePath: string) =>
+ NGC_NON_TS_INPUTS.test(filePath) || filePath.split(path.sep).indexOf('node_modules') != -1;
+ bazelHost.shouldSkipTsickleProcessing = (fileName: string): boolean =>
+ bazelOpts.compilationTargetSrc.indexOf(fileName) === -1 && !NGC_NON_TS_INPUTS.test(fileName);
+
+ const ngHost = ng.createCompilerHost({options: ngOptions, tsHost: bazelHost});
+
+ ngHost.fileNameToModuleName = (importedFilePath: string, containingFilePath: string) =>
+ relativeToRootDir(importedFilePath).replace(EXT, '');
+ ngHost.toSummaryFileName = (fileName: string, referringSrcFileName: string) =>
+ ngHost.fileNameToModuleName(fileName, referringSrcFileName);
+
+ const tsickleOpts = {
+ googmodule: bazelOpts.googmodule,
+ es5Mode: bazelOpts.es5Mode,
+ prelude: bazelOpts.prelude,
+ untyped: bazelOpts.untyped,
+ typeBlackListPaths: new Set(bazelOpts.typeBlackListPaths),
+ transformDecorators: bazelOpts.tsickle,
+ transformTypesToClosure: bazelOpts.tsickle,
+ };
+ const emitCallback: ng.TsEmitCallback = ({
+ program,
+ targetSourceFile,
+ writeFile,
+ cancellationToken,
+ emitOnlyDtsFiles,
+ customTransformers = {},
+ }) =>
+ tsickle.emitWithTsickle(
+ program, bazelHost, tsickleOpts, bazelHost, ngOptions, targetSourceFile, writeFile,
+ cancellationToken, emitOnlyDtsFiles, {
+ beforeTs: customTransformers.before,
+ afterTs: customTransformers.after,
+ });
+
+ const {diagnostics, emitResult} =
+ ng.performCompilation({rootNames: files, options: ngOptions, host: ngHost, emitCallback});
+ const tsickleEmitResult = emitResult as tsickle.EmitResult;
+ let externs = '/** @externs */\n';
+ if (diagnostics.length) {
+ console.error(ng.formatDiagnostics(ngOptions, diagnostics));
+ } else {
+ if (bazelOpts.tsickleGenerateExterns) {
+ externs += tsickle.getGeneratedExterns(tsickleEmitResult.externs);
+ }
+ if (bazelOpts.manifest) {
+ const manifest = constructManifest(tsickleEmitResult.modulesManifest, bazelHost);
+ fs.writeFileSync(bazelOpts.manifest, manifest);
+ }
+ }
+
+ if (bazelOpts.tsickleExternsPath) {
+ // Note: when tsickleExternsPath is provided, we always write a file as a
+ // marker that compilation succeeded, even if it's empty (just containing an
+ // @externs).
+ fs.writeFileSync(bazelOpts.tsickleExternsPath, externs);
+ }
+
+ for (const missing of expectedOuts) {
+ originalWriteFile(missing, '', false);
+ }
+
return diagnostics.some(d => d.category === ts.DiagnosticCategory.Error) ? 1 : 0;
}
diff --git a/packages/bazel/src/ngc-wrapped/tsconfig.json b/packages/bazel/src/ngc-wrapped/tsconfig.json
new file mode 100644
index 0000000000..fd217e560a
--- /dev/null
+++ b/packages/bazel/src/ngc-wrapped/tsconfig.json
@@ -0,0 +1,5 @@
+{
+ "compilerOptions": {
+ "lib": ["es5", "es2015.collection", "es2015.core"]
+ }
+}
diff --git a/packages/bazel/src/rules_typescript.bzl b/packages/bazel/src/rules_typescript.bzl
new file mode 100644
index 0000000000..77f37b9abb
--- /dev/null
+++ b/packages/bazel/src/rules_typescript.bzl
@@ -0,0 +1,9 @@
+# Allows different paths for these imports in google3
+load("@build_bazel_rules_typescript//internal:build_defs.bzl", "tsc_wrapped_tsconfig")
+
+load(
+ "@build_bazel_rules_typescript//internal:common/compilation.bzl",
+ "COMMON_ATTRIBUTES", "compile_ts", "DEPS_ASPECTS", "ts_providers_dict_to_struct"
+)
+
+load("@build_bazel_rules_typescript//internal:common/json_marshal.bzl", "json_marshal")
diff --git a/packages/compiler-cli/src/transformers/api.ts b/packages/compiler-cli/src/transformers/api.ts
index cc8860968c..ebd51c931d 100644
--- a/packages/compiler-cli/src/transformers/api.ts
+++ b/packages/compiler-cli/src/transformers/api.ts
@@ -91,11 +91,6 @@ export interface CompilerOptions extends ts.CompilerOptions {
// position.
disableExpressionLowering?: boolean;
- // The list of expected files, when provided:
- // - extra files are filtered out,
- // - missing files are created empty.
- expectedOut?: string[];
-
// Locale of the application
i18nOutLocale?: string;
// Export format (xlf, xlf2 or xmb)
diff --git a/packages/compiler-cli/src/transformers/program.ts b/packages/compiler-cli/src/transformers/program.ts
index ee81730f98..acb76edda3 100644
--- a/packages/compiler-cli/src/transformers/program.ts
+++ b/packages/compiler-cli/src/transformers/program.ts
@@ -143,22 +143,13 @@ class AngularCompilerProgram implements Program {
}): ts.EmitResult {
const emitMap = new Map();
- const expectedOut = this.options.expectedOut ?
- this.options.expectedOut.map(f => path.resolve(process.cwd(), f)) :
- undefined;
-
- // Ensure that expected output files exist.
- for (const out of expectedOut || []) {
- this.host.writeFile(out, '', false);
- }
-
const emitResult = emitCallback({
program: this.programWithStubs,
host: this.host,
options: this.options,
targetSourceFile: undefined,
- writeFile:
- createWriteFileCallback(emitFlags, this.host, this.metadataCache, emitMap, expectedOut),
+ writeFile: createWriteFileCallback(
+ emitFlags, this.host, this.metadataCache, emitMap, this.generatedFiles),
cancellationToken,
emitOnlyDtsFiles: (emitFlags & (EmitFlags.DTS | EmitFlags.JS)) == EmitFlags.DTS,
customTransformers: this.calculateTransforms(customTransformers)
@@ -399,7 +390,9 @@ function writeMetadata(
function createWriteFileCallback(
emitFlags: EmitFlags, host: ts.CompilerHost, metadataCache: LowerMetadataCache,
- emitMap: Map, expectedOut?: string[]) {
+ emitMap: Map, generatedFiles: GeneratedFile[]) {
+ const genFileToSrcFile = new Map();
+ generatedFiles.forEach(f => genFileToSrcFile.set(f.genFileUrl, f.srcFileUrl));
return (fileName: string, data: string, writeByteOrderMark: boolean,
onError?: (message: string) => void, sourceFiles?: ts.SourceFile[]) => {
@@ -407,14 +400,15 @@ function createWriteFileCallback(
if (sourceFiles && sourceFiles.length == 1) {
srcFile = sourceFiles[0];
- emitMap.set(srcFile.fileName, fileName);
+ const originalSrcFile = genFileToSrcFile.get(srcFile.fileName) || srcFile.fileName;
+ emitMap.set(originalSrcFile, fileName);
}
const absFile = path.resolve(process.cwd(), fileName);
const generatedFile = GENERATED_FILES.test(fileName);
- // Don't emit unexpected files nor empty generated files
- if ((!expectedOut || expectedOut.indexOf(absFile) > -1) && (!generatedFile || data)) {
+ // Don't emit empty generated files
+ if (!generatedFile || data) {
host.writeFile(fileName, data, writeByteOrderMark, onError, sourceFiles);
if (srcFile && !generatedFile && (emitFlags & EmitFlags.Metadata) != 0) {
diff --git a/scripts/ci/install.sh b/scripts/ci/install.sh
index 45dce806e2..784f021ca1 100755
--- a/scripts/ci/install.sh
+++ b/scripts/ci/install.sh
@@ -61,7 +61,7 @@ if [[ ${TRAVIS} && (${CI_MODE} == "aio" || ${CI_MODE} == "aio_e2e" || ${CI_MODE}
fi
# Install bazel
-if [[ ${TRAVIS} && ${CI_MODE} == "bazel" ]]; then
+if [[ ${TRAVIS} && (${CI_MODE} == "bazel" || ${CI_MODE} == "e2e_2") ]]; then
travisFoldStart "bazel-install"
(
mkdir tmp