Skip to content

Commit

Permalink
Use regular expressions to match JS filenames in kt_js_import. (bazel…
Browse files Browse the repository at this point in the history
…build#223)

* Add a more nuanced approach to pulling kt_js_import files out of the jar.

* PR Change Request Edits

* Add simple helloworld node example, using coroutines to test kt_js_import.
* Use that helloworld node example in src/test/kotlin/io/bazel/kotlin
* Use inline `foo if case else bar` format for srcjar resolution in `kotlin/internal/js/impl.bzl`

* Add presubmit test for node/coroutines-helloworld

* Remove KotlinJsCoroutinesHelloworldExampleTest.kt

* Use shutil.move instead of os.rename

* Add express to the build targets as well.
  • Loading branch information
jongerrish authored and cgruber committed Oct 28, 2019
1 parent d9e08ba commit fe48312
Show file tree
Hide file tree
Showing 12 changed files with 219 additions and 29 deletions.
10 changes: 10 additions & 0 deletions .bazelci/presubmit.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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/...
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@
**/.project
**/.ijwb
**/.aswb
**/.vscode
41 changes: 41 additions & 0 deletions WORKSPACE
Original file line number Diff line number Diff line change
Expand Up @@ -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",
],
)

23 changes: 23 additions & 0 deletions examples/node/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -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__"],
)
24 changes: 24 additions & 0 deletions examples/node/WORKSPACE
Original file line number Diff line number Diff line change
Expand Up @@ -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",
],
)
32 changes: 32 additions & 0 deletions examples/node/coroutines-helloworld/BUILD
Original file line number Diff line number Diff line change
@@ -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"],
)
14 changes: 14 additions & 0 deletions examples/node/coroutines-helloworld/Main.kt
Original file line number Diff line number Diff line change
@@ -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()
}
13 changes: 12 additions & 1 deletion examples/node/express/App.kt
Original file line number Diff line number Diff line change
@@ -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<String>) {
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")
Expand Down
11 changes: 9 additions & 2 deletions examples/node/express/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
15 changes: 13 additions & 2 deletions examples/node/express/Routes.kt
Original file line number Diff line number Diff line change
@@ -1,16 +1,27 @@
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<Int> {
val scope = CoroutineScope(Dispatchers.Default)
val channel = Channel<Int>()
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 {
res.type("text/plain")
res.send("hello world")
}
}

return channel
}
14 changes: 6 additions & 8 deletions kotlin/internal/js/impl.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand All @@ -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,
),
]
50 changes: 34 additions & 16 deletions kotlin/internal/js/importer.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,10 @@
# limitations under the License.
import argparse
import os
import re
import zipfile
import tempfile
import shutil


def _is_jar(jar):
Expand All @@ -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)

0 comments on commit fe48312

Please sign in to comment.