diff --git a/.bazelci/presubmit.yml b/.bazelci/presubmit.yml index 66a5c314e..75b289cb4 100644 --- a/.bazelci/presubmit.yml +++ b/.bazelci/presubmit.yml @@ -32,3 +32,13 @@ tasks: - test build_targets: - //... + examples-nodejs: + name: Example - Node + platform: ubuntu1804 + working_directory: examples/node + include_json_profile: + - build + - test + build_targets: + - //coroutines-helloworld/... + - //express/... diff --git a/.gitignore b/.gitignore index 6ce3d5b6a..4d524fcf8 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,4 @@ **/.project **/.ijwb **/.aswb +**/.vscode diff --git a/WORKSPACE b/WORKSPACE index 9a64eadac..edc104e19 100644 --- a/WORKSPACE +++ b/WORKSPACE @@ -69,3 +69,44 @@ load("//kotlin:kotlin.bzl", "kotlin_repositories", "kt_register_toolchains") kotlin_repositories() kt_register_toolchains() + +# The following are to support building and running nodejs examples from src/test + +http_archive( + name = "build_bazel_rules_nodejs", + sha256 = "3356c6b767403392bab018ce91625f6d15ff8f11c6d772dc84bc9cada01c669a", + urls = ["https://github.com/bazelbuild/rules_nodejs/releases/download/0.36.1/rules_nodejs-0.36.1.tar.gz"], +) + +load("@build_bazel_rules_nodejs//:defs.bzl", "yarn_install") + +yarn_install( + name = "node_ws", + package_json = "@node_example//:package.json", + yarn_lock = "@node_example//:yarn.lock", +) + +RULES_JVM_EXTERNAL_TAG = "2.7" + +RULES_JVM_EXTERNAL_SHA = "f04b1466a00a2845106801e0c5cec96841f49ea4e7d1df88dc8e4bf31523df74" + +http_archive( + name = "rules_jvm_external", + sha256 = RULES_JVM_EXTERNAL_SHA, + strip_prefix = "rules_jvm_external-%s" % RULES_JVM_EXTERNAL_TAG, + url = "https://github.com/bazelbuild/rules_jvm_external/archive/%s.zip" % RULES_JVM_EXTERNAL_TAG, +) + +load("@rules_jvm_external//:defs.bzl", "maven_install") + +maven_install( + artifacts = [ + "org.jetbrains.kotlinx:atomicfu-js:0.13.1", + "org.jetbrains.kotlinx:kotlinx-coroutines-core-js:1.3.2", + ], + repositories = [ + "https://maven-central.storage.googleapis.com/repos/central/data/", + "https://repo1.maven.org/maven2", + ], +) + diff --git a/examples/node/BUILD b/examples/node/BUILD index 9c0f139ef..e5e7a6b38 100644 --- a/examples/node/BUILD +++ b/examples/node/BUILD @@ -11,3 +11,26 @@ # 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. +load("@io_bazel_rules_kotlin//kotlin:kotlin.bzl", "kt_js_import") + +# Create kt_js_imports for KotlinX's Coroutines and Atomicfu libraries. +# +# These could be imported using package.json and NPM, but they are here as an example of how to +# use KotlinJS libraries hosted on Maven directly. +# +# Note: It's important that the `name`s correspond to the base name of the library only (ie. not-ending with -js/_js) +kt_js_import( + name = "kotlinx-coroutines-core", + jars = [ + "@maven//:org_jetbrains_kotlinx_kotlinx_coroutines_core_js" + ], + visibility = ["//:__subpackages__"], +) + +kt_js_import( + name = "kotlinx-atomicfu", + jars = [ + "@maven//:org_jetbrains_kotlinx_atomicfu_js" + ], + visibility = ["//:__subpackages__"], +) diff --git a/examples/node/WORKSPACE b/examples/node/WORKSPACE index dd4cb89a4..2e614d43f 100644 --- a/examples/node/WORKSPACE +++ b/examples/node/WORKSPACE @@ -23,3 +23,27 @@ yarn_install( package_json = "//:package.json", yarn_lock = "//:yarn.lock", ) + +RULES_JVM_EXTERNAL_TAG = "2.7" + +RULES_JVM_EXTERNAL_SHA = "f04b1466a00a2845106801e0c5cec96841f49ea4e7d1df88dc8e4bf31523df74" + +http_archive( + name = "rules_jvm_external", + sha256 = RULES_JVM_EXTERNAL_SHA, + strip_prefix = "rules_jvm_external-%s" % RULES_JVM_EXTERNAL_TAG, + url = "https://github.com/bazelbuild/rules_jvm_external/archive/%s.zip" % RULES_JVM_EXTERNAL_TAG, +) + +load("@rules_jvm_external//:defs.bzl", "maven_install") + +maven_install( + artifacts = [ + "org.jetbrains.kotlinx:atomicfu-js:0.13.1", + "org.jetbrains.kotlinx:kotlinx-coroutines-core-js:1.3.2", + ], + repositories = [ + "https://maven-central.storage.googleapis.com/repos/central/data/", + "https://repo1.maven.org/maven2", + ], +) diff --git a/examples/node/coroutines-helloworld/BUILD b/examples/node/coroutines-helloworld/BUILD new file mode 100644 index 000000000..c299d47f5 --- /dev/null +++ b/examples/node/coroutines-helloworld/BUILD @@ -0,0 +1,32 @@ +# 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. +load("@io_bazel_rules_kotlin//kotlin:kotlin.bzl", "kt_js_library") +load("@build_bazel_rules_nodejs//:defs.bzl", "nodejs_binary") + +kt_js_library( + name = "app", + srcs = ["Main.kt"], + deps = [ + "//:kotlinx-coroutines-core", + ], + visibility = ["//visibility:public"], +) + +nodejs_binary( + name = "coroutines-helloworld", + node_modules = "@node_ws//:node_modules", + data = [":app"], + entry_point = ":app.js", + visibility = ["//visibility:public"], +) diff --git a/examples/node/coroutines-helloworld/Main.kt b/examples/node/coroutines-helloworld/Main.kt new file mode 100644 index 000000000..940c1972e --- /dev/null +++ b/examples/node/coroutines-helloworld/Main.kt @@ -0,0 +1,14 @@ +package trivial + +import kotlinx.coroutines.* + +val scope = CoroutineScope(Dispatchers.Default) + +suspend fun main(vararg args: String) { + val job = scope.launch { + delay(1000) + println("Hello world!") + } + + job.join() +} diff --git a/examples/node/express/App.kt b/examples/node/express/App.kt index 4cdc18111..36947ffcd 100644 --- a/examples/node/express/App.kt +++ b/examples/node/express/App.kt @@ -1,13 +1,24 @@ package express +import kotlinx.coroutines.* +import kotlinx.coroutines.channels.* + @JsModule("express") external fun express(): dynamic val app = express() +@ExperimentalCoroutinesApi fun main(args: Array) { + val scope = CoroutineScope(Dispatchers.Default) + // register the routes. - routes(app) + val hitCountChannel = routes(app) + scope.launch { + hitCountChannel.consumeEach { + println("Hits so far: $it") + } + } app.listen(3000, { println("Listening on port 3000") diff --git a/examples/node/express/BUILD b/examples/node/express/BUILD index 8b4822769..864cabe2a 100644 --- a/examples/node/express/BUILD +++ b/examples/node/express/BUILD @@ -19,13 +19,20 @@ load("@build_bazel_rules_nodejs//:defs.bzl", "nodejs_binary") kt_js_library( name = "acme-routes", srcs = [":Routes.kt"], - deps = ["//express/auth:acme-auth"], + deps = [ + "//express/auth:acme-auth", + "//:kotlinx-atomicfu", + "//:kotlinx-coroutines-core", + ], ) kt_js_library( name ="app", srcs = [":App.kt"], - deps = [":acme-routes"] + deps = [ + ":acme-routes", + "//:kotlinx-coroutines-core", + ], ) # The binary demonstrates an express app composed of three modules. The modules can co-exist in the same directory. diff --git a/examples/node/express/Routes.kt b/examples/node/express/Routes.kt index 3f079d901..44d15d21a 100644 --- a/examples/node/express/Routes.kt +++ b/examples/node/express/Routes.kt @@ -1,11 +1,20 @@ package express import express.auth.isAuthenticated +import kotlinx.atomicfu.* +import kotlinx.coroutines.* +import kotlinx.coroutines.channels.* -//import express.auth.isAuthenticated +fun routes(app: dynamic): Channel { + val scope = CoroutineScope(Dispatchers.Default) + val channel = Channel() + val hitCounter = atomic(0) -fun routes(app: dynamic) { app.get("/") { req, res -> + scope.launch { + val hitsSoFar = hitCounter.updateAndGet { it + 1 } + channel.send(hitsSoFar) + } if(!isAuthenticated("bob")) { res.send(401, "you sir, are not authorized !") } else { @@ -13,4 +22,6 @@ fun routes(app: dynamic) { res.send("hello world") } } + + return channel } \ No newline at end of file diff --git a/kotlin/internal/js/impl.bzl b/kotlin/internal/js/impl.bzl index 7f84fe625..92f3861ff 100644 --- a/kotlin/internal/js/impl.bzl +++ b/kotlin/internal/js/impl.bzl @@ -122,16 +122,14 @@ def kt_js_import_impl(ctx): fail("a single jar should be supplied, multiple jars not supported") jar_file = ctx.files.jars[0] - # Lock the jar name to the label name -- only make an exception for the compiler repo. - if not (ctx.label.workspace_root.startswith("external/") and ctx.label.workspace_root.endswith(_KT_COMPILER_REPO)): - expected_basename = "%s.jar" % ctx.label.name - if _strip_version(jar_file.basename) != expected_basename: - fail("label name %s is not the same as the jar name %s" % (jar_file.basename, expected_basename)) + srcjar = ctx.files.srcjar[0] if len(ctx.files.srcjar) == 1 else None args = ctx.actions.args() args.add("--jar", jar_file) - args.add("--out", ctx.outputs.js) - args.add("--aux", ctx.outputs.js_map) + args.add("--import_pattern", "^[^/.]+\\.js$") + args.add("--import_out", ctx.outputs.js) + args.add("--aux_pattern", "^[^/]+\\.js\\.map$") + args.add("--aux_out", ctx.outputs.js_map) tools, _, input_manifest = ctx.resolve_command(tools = [ctx.attr._importer]) ctx.actions.run( @@ -154,6 +152,6 @@ def kt_js_import_impl(ctx): js = ctx.outputs.js, js_map = ctx.outputs.js_map, jar = jar_file, - srcjar = ctx.files.srcjar[0], + srcjar = srcjar, ), ] diff --git a/kotlin/internal/js/importer.py b/kotlin/internal/js/importer.py index 7e54d5089..87bfb571e 100644 --- a/kotlin/internal/js/importer.py +++ b/kotlin/internal/js/importer.py @@ -13,7 +13,10 @@ # limitations under the License. import argparse import os +import re import zipfile +import tempfile +import shutil def _is_jar(jar): @@ -23,42 +26,57 @@ def _is_jar(jar): return zipfile.ZipFile(jar) -def _extract_root_entry(jar, entry_path, touch=False): +def _extract_root_entry(jar, filename_pattern, output_path, touch=False): """ Extracts a root entry from a jar. and write it to a path. - entry_path is absolute and the basename is used to extract the entry from the jar. + output_path is absolute and the basename is used to extract the entry from the jar. :param jar: The jar from which to make the extraction. - :param entry_path: An absolute file path to where the entry should be written. + :param filename_pattern: Regular expression to match when searching for the file in the jar. + :param output_path: An absolute file path to where the entry should be written. :param touch: Should the file be touched if it was not found in the jar. """ - basename = os.path.basename(entry_path) - try: - jar.read(basename) - except Exception as ex: + target = None + for filename in jar.namelist(): + if filename_pattern.match(filename): + target = filename + break + + if not target: if touch: - f = open(entry_path, 'a') + f = open(output_path, 'a') f.close() return else: - raise ex - jar.extract(basename, path=os.path.dirname(entry_path)) + raise FileNotFoundError("No file matching {0} was found in jar".format(filename_pattern)) + + # Extract the target file to a temporary location. + temp_dir = tempfile.gettempdir() + temp_file = os.path.join(temp_dir, os.path.basename(target)) + jar.extract(target, path=temp_dir) + + # Move the temp file into the final output location. + shutil.move(temp_file, output_path) def _main(p): args = p.parse_args() - _extract_root_entry(args.jar, args.out) - for e in args.aux: - _extract_root_entry(args.jar, e, touch=True) + _extract_root_entry(args.jar, args.import_pattern, args.import_out) + for (p, e) in zip(args.aux_pattern, args.aux_out): + _extract_root_entry(args.jar, p, e, touch=True) parser = argparse.ArgumentParser() parser.add_argument("--jar", type=_is_jar, required=True) -parser.add_argument("--out", required=True, help="mandatory paths to files that should be extracted from the root") +parser.add_argument("--import_pattern", required=True, type=re.compile, help="regular expression to match when searching the jar for the KotlinJS file") +parser.add_argument("--import_out", required=True, help="path where the extracted KotlinJS import should be stored") +parser.add_argument( + "--aux_pattern", nargs="*", type=re.compile, + help="""regular expressions to match when searching the jar for additional files""") parser.add_argument( - "--aux", nargs="*", - help="""paths to files that should be extracted from the root, if the files do not exist they are touched.""") + "--aux_out", nargs="*", + help="""paths where additonal files from the jar should be stored, if the files do not exist these paths are touched.""") _main(parser)