From 9008791a54e56d87bf04b396775fbe293e066bc2 Mon Sep 17 00:00:00 2001 From: Alexey Andreev Date: Tue, 29 Aug 2017 14:28:15 +0300 Subject: [PATCH] JS: generate paths in source maps relative to .map file location See KT-19818 --- .../jetbrains/kotlin/cli/js/K2JSCompiler.java | 40 ++++++++------ compiler/testData/cli/js/sourceMap.args | 2 + compiler/testData/cli/js/sourceMap.test | 2 +- .../cli/js/sourceMapEmbedSources.args | 2 + .../cli/js/sourceMapEmbedSources.test | 2 +- .../cli/js/sourceMapRelativeRoot.args | 14 +++++ .../testData/cli/js/sourceMapRelativeRoot.out | 1 + .../cli/js/sourceMapRelativeRoot.test | 5 ++ .../cli/js/sourceMapRelativeRoot/.gitignore | 1 + .../js/sourceMapRelativeRoot/lib/src/lib.kt | 4 ++ .../js/sourceMapRelativeRoot/main/src/main.kt | 1 + .../testData/cli/js/sourceMapRootAuto.args | 2 + .../testData/cli/js/sourceMapRootAuto.test | 4 +- .../jetbrains/kotlin/cli/AbstractCliTest.java | 52 +++++++++++++------ .../kotlin/cli/CliTestGenerated.java | 6 +++ .../jetbrains/kotlin/utils/JsLibraryUtils.kt | 6 +-- .../kotlin/jps/build/KotlinJpsBuildTest.kt | 2 +- .../kotlin/jps/build/KotlinBuilder.kt | 6 ++- .../kotlin/js/dce/DeadCodeElimination.kt | 3 +- .../kotlin/js/config/JSConfigurationKeys.java | 4 ++ .../jetbrains/kotlin/js/config/JsConfig.java | 6 ++- .../kotlin/js/inline/FunctionReader.kt | 34 ++++++++++-- .../js/inline/util/RelativePathCalculator.kt | 41 +++++++++++++++ .../sourcemaps/SourceMapLocationRemapper.kt | 4 +- .../jetbrains/kotlin/js/test/BasicBoxTest.kt | 2 +- .../kotlin/js/facade/K2JSTranslator.java | 4 +- .../kotlin/js/facade/TranslationResult.kt | 3 +- .../js/sourceMap/SourceFilePathResolver.java | 31 ++++++++++- .../kotlin/gradle/Kotlin2JsGradlePluginIT.kt | 6 +-- .../build.gradle | 1 + .../jetbrains/kotlin/gradle/tasks/Tasks.kt | 8 ++- 31 files changed, 236 insertions(+), 63 deletions(-) create mode 100644 compiler/testData/cli/js/sourceMapRelativeRoot.args create mode 100644 compiler/testData/cli/js/sourceMapRelativeRoot.out create mode 100644 compiler/testData/cli/js/sourceMapRelativeRoot.test create mode 100644 compiler/testData/cli/js/sourceMapRelativeRoot/.gitignore create mode 100644 compiler/testData/cli/js/sourceMapRelativeRoot/lib/src/lib.kt create mode 100644 compiler/testData/cli/js/sourceMapRelativeRoot/main/src/main.kt create mode 100644 js/js.inliner/src/org/jetbrains/kotlin/js/inline/util/RelativePathCalculator.kt diff --git a/compiler/cli/src/org/jetbrains/kotlin/cli/js/K2JSCompiler.java b/compiler/cli/src/org/jetbrains/kotlin/cli/js/K2JSCompiler.java index 24b6088880d..9291c343feb 100644 --- a/compiler/cli/src/org/jetbrains/kotlin/cli/js/K2JSCompiler.java +++ b/compiler/cli/src/org/jetbrains/kotlin/cli/js/K2JSCompiler.java @@ -251,8 +251,20 @@ public class K2JSCompiler extends CLICompiler { } } + File outputDir = outputFile.getParentFile(); + if (outputDir == null) { + outputDir = outputFile.getAbsoluteFile().getParentFile(); + } + try { + config.getConfiguration().put(JSConfigurationKeys.OUTPUT_DIR, outputDir.getCanonicalFile()); + } + catch (IOException e) { + messageCollector.report(ERROR, "Could not resolve output directory", null); + return ExitCode.COMPILATION_ERROR; + } + if (config.getConfiguration().getBoolean(JSConfigurationKeys.SOURCE_MAP)) { - checkDuplicateSourceFileNames(messageCollector, sourcesFiles, config.getSourceMapRoots()); + checkDuplicateSourceFileNames(messageCollector, sourcesFiles, config); } MainCallParameters mainCallParameters = createMainCallParameters(arguments.getMain()); @@ -280,11 +292,6 @@ public class K2JSCompiler extends CLICompiler { return ExitCode.COMPILATION_ERROR; } - File outputDir = outputFile.getParentFile(); - if (outputDir == null) { - outputDir = outputFile.getAbsoluteFile().getParentFile(); - } - ProgressIndicatorAndCompilationCanceledStatus.checkCanceled(); OutputUtilsKt.writeAll(outputFiles, outputDir, messageCollector, @@ -296,12 +303,11 @@ public class K2JSCompiler extends CLICompiler { private static void checkDuplicateSourceFileNames( @NotNull MessageCollector log, @NotNull List sourceFiles, - @NotNull List sourceRoots + @NotNull JsConfig config ) { - if (sourceRoots.isEmpty()) return; + if (config.getSourceMapRoots().isEmpty()) return; - List sourceRootFiles = sourceRoots.stream().map(File::new).collect(Collectors.toList()); - SourceFilePathResolver pathResolver = new SourceFilePathResolver(sourceRootFiles); + SourceFilePathResolver pathResolver = SourceFilePathResolver.create(config); Map pathMap = new HashMap<>(); Set duplicatePaths = new HashSet<>(); @@ -357,11 +363,15 @@ public class K2JSCompiler extends CLICompiler { configuration.put(JSConfigurationKeys.SOURCE_MAP_PREFIX, arguments.getSourceMapPrefix()); } - String sourceMapSourceRoots = arguments.getSourceMapBaseDirs() != null ? - arguments.getSourceMapBaseDirs() : - calculateSourceMapSourceRoot(messageCollector, arguments); - List sourceMapSourceRootList = StringUtil.split(sourceMapSourceRoots, File.pathSeparator); - configuration.put(JSConfigurationKeys.SOURCE_MAP_SOURCE_ROOTS, sourceMapSourceRootList); + String sourceMapSourceRoots = arguments.getSourceMapBaseDirs(); + if (sourceMapSourceRoots == null && StringUtil.isNotEmpty(arguments.getSourceMapPrefix())) { + sourceMapSourceRoots = calculateSourceMapSourceRoot(messageCollector, arguments); + } + + if (sourceMapSourceRoots != null) { + List sourceMapSourceRootList = StringUtil.split(sourceMapSourceRoots, File.pathSeparator); + configuration.put(JSConfigurationKeys.SOURCE_MAP_SOURCE_ROOTS, sourceMapSourceRootList); + } } else { if (arguments.getSourceMapPrefix() != null) { diff --git a/compiler/testData/cli/js/sourceMap.args b/compiler/testData/cli/js/sourceMap.args index ba955d97b1f..ec0bfee4154 100644 --- a/compiler/testData/cli/js/sourceMap.args +++ b/compiler/testData/cli/js/sourceMap.args @@ -1,5 +1,7 @@ $TESTDATA_DIR$/sourceMap.kt -no-stdlib -source-map +-source-map-prefix +./ -output $TEMP_DIR$/out.js diff --git a/compiler/testData/cli/js/sourceMap.test b/compiler/testData/cli/js/sourceMap.test index 9a569dde5b4..3e377c7c555 100644 --- a/compiler/testData/cli/js/sourceMap.test +++ b/compiler/testData/cli/js/sourceMap.test @@ -1,2 +1,2 @@ // EXISTS: out.js -// CONTAINS: out.js.map, "sourceMap.kt" +// CONTAINS: out.js.map, "./sourceMap.kt" diff --git a/compiler/testData/cli/js/sourceMapEmbedSources.args b/compiler/testData/cli/js/sourceMapEmbedSources.args index 595760bdea0..fd3c5812464 100644 --- a/compiler/testData/cli/js/sourceMapEmbedSources.args +++ b/compiler/testData/cli/js/sourceMapEmbedSources.args @@ -1,6 +1,8 @@ $TESTDATA_DIR$/sourceMap.kt -no-stdlib -source-map +-source-map-prefix +./ -source-map-embed-sources always -output diff --git a/compiler/testData/cli/js/sourceMapEmbedSources.test b/compiler/testData/cli/js/sourceMapEmbedSources.test index abad696f62b..d879fb77ec6 100644 --- a/compiler/testData/cli/js/sourceMapEmbedSources.test +++ b/compiler/testData/cli/js/sourceMapEmbedSources.test @@ -1,3 +1,3 @@ // EXISTS: out.js -// CONTAINS: out.js.map, "sourceMap.kt" +// CONTAINS: out.js.map, "./sourceMap.kt" // CONTAINS: out.js.map, "var log = \"\"\n\nfun foo(x: String) { diff --git a/compiler/testData/cli/js/sourceMapRelativeRoot.args b/compiler/testData/cli/js/sourceMapRelativeRoot.args new file mode 100644 index 00000000000..5c6cbb9f720 --- /dev/null +++ b/compiler/testData/cli/js/sourceMapRelativeRoot.args @@ -0,0 +1,14 @@ +$TESTDATA_DIR$/sourceMapRelativeRoot/lib/src/lib.kt +-no-stdlib +-source-map +-meta-info +-output +$TESTDATA_DIR$/sourceMapRelativeRoot/lib/out/lib.js +--- +$TESTDATA_DIR$/sourceMapRelativeRoot/main/src/main.kt +-no-stdlib +-libraries +$TESTDATA_DIR$/sourceMapRelativeRoot/lib/out/ +-source-map +-output +$TESTDATA_DIR$/sourceMapRelativeRoot/main/out/main.js \ No newline at end of file diff --git a/compiler/testData/cli/js/sourceMapRelativeRoot.out b/compiler/testData/cli/js/sourceMapRelativeRoot.out new file mode 100644 index 00000000000..d86bac9de59 --- /dev/null +++ b/compiler/testData/cli/js/sourceMapRelativeRoot.out @@ -0,0 +1 @@ +OK diff --git a/compiler/testData/cli/js/sourceMapRelativeRoot.test b/compiler/testData/cli/js/sourceMapRelativeRoot.test new file mode 100644 index 00000000000..e7fd01e82f1 --- /dev/null +++ b/compiler/testData/cli/js/sourceMapRelativeRoot.test @@ -0,0 +1,5 @@ +// EXISTS: $TESTDATA_DIR$/sourceMapRelativeRoot/lib/out/lib.js +// EXISTS: $TESTDATA_DIR$/sourceMapRelativeRoot/main/out/main.js +// CONTAINS: $TESTDATA_DIR$/sourceMapRelativeRoot/lib/out/lib.js.map, "../src/lib.kt" +// CONTAINS: $TESTDATA_DIR$/sourceMapRelativeRoot/main/out/main.js.map, "../src/main.kt" +// CONTAINS: $TESTDATA_DIR$/sourceMapRelativeRoot/main/out/main.js.map, "../../lib/src/lib.kt" diff --git a/compiler/testData/cli/js/sourceMapRelativeRoot/.gitignore b/compiler/testData/cli/js/sourceMapRelativeRoot/.gitignore new file mode 100644 index 00000000000..c585e19389d --- /dev/null +++ b/compiler/testData/cli/js/sourceMapRelativeRoot/.gitignore @@ -0,0 +1 @@ +out \ No newline at end of file diff --git a/compiler/testData/cli/js/sourceMapRelativeRoot/lib/src/lib.kt b/compiler/testData/cli/js/sourceMapRelativeRoot/lib/src/lib.kt new file mode 100644 index 00000000000..1efbd69fe53 --- /dev/null +++ b/compiler/testData/cli/js/sourceMapRelativeRoot/lib/src/lib.kt @@ -0,0 +1,4 @@ +@Suppress("NOTHING_TO_INLINE") +inline fun foo() = 23 + +fun bar() = 42 \ No newline at end of file diff --git a/compiler/testData/cli/js/sourceMapRelativeRoot/main/src/main.kt b/compiler/testData/cli/js/sourceMapRelativeRoot/main/src/main.kt new file mode 100644 index 00000000000..2c0ee76670d --- /dev/null +++ b/compiler/testData/cli/js/sourceMapRelativeRoot/main/src/main.kt @@ -0,0 +1 @@ +fun box() = foo() + bar() \ No newline at end of file diff --git a/compiler/testData/cli/js/sourceMapRootAuto.args b/compiler/testData/cli/js/sourceMapRootAuto.args index 4e1cdb93599..75c8893ced4 100644 --- a/compiler/testData/cli/js/sourceMapRootAuto.args +++ b/compiler/testData/cli/js/sourceMapRootAuto.args @@ -2,5 +2,7 @@ $TESTDATA_DIR$/sourceMapRoot/foo/file1.kt $TESTDATA_DIR$/sourceMapRoot/bar/file2.kt -no-stdlib -source-map +-source-map-prefix +./ -output $TEMP_DIR$/out.js diff --git a/compiler/testData/cli/js/sourceMapRootAuto.test b/compiler/testData/cli/js/sourceMapRootAuto.test index a0340f0e762..72135eb65ec 100644 --- a/compiler/testData/cli/js/sourceMapRootAuto.test +++ b/compiler/testData/cli/js/sourceMapRootAuto.test @@ -1,3 +1,3 @@ // EXISTS: out.js -// CONTAINS: out.js.map, "foo/file1.kt" -// CONTAINS: out.js.map, "bar/file2.kt" +// CONTAINS: out.js.map, "./foo/file1.kt" +// CONTAINS: out.js.map, "./bar/file2.kt" diff --git a/compiler/tests/org/jetbrains/kotlin/cli/AbstractCliTest.java b/compiler/tests/org/jetbrains/kotlin/cli/AbstractCliTest.java index fd9074cee11..e10063282d3 100644 --- a/compiler/tests/org/jetbrains/kotlin/cli/AbstractCliTest.java +++ b/compiler/tests/org/jetbrains/kotlin/cli/AbstractCliTest.java @@ -43,19 +43,29 @@ import org.junit.Assert; import java.io.ByteArrayOutputStream; import java.io.File; -import java.io.IOException; import java.io.PrintStream; import java.util.ArrayList; import java.util.List; public abstract class AbstractCliTest extends TestCaseWithTmpdir { - @NotNull + private static final String TESTDATA_DIR = "$TESTDATA_DIR$"; + public static Pair executeCompilerGrabOutput(@NotNull CLITool compiler, @NotNull List args) { ByteArrayOutputStream bytes = new ByteArrayOutputStream(); PrintStream origErr = System.err; try { System.setErr(new PrintStream(bytes)); - ExitCode exitCode = CLITool.doMainNoExit(compiler, ArrayUtil.toStringArray(args)); + ExitCode exitCode; + int index = 0; + do { + int next = args.subList(index, args.size()).indexOf("---"); + if (next == -1) { + next = args.size(); + } + exitCode = CLITool.doMainNoExit(compiler, ArrayUtil.toStringArray(args.subList(index, next))); + if (exitCode != ExitCode.OK) break; + index = next + 1; + } while (index < args.size()); return new Pair<>(bytes.toString("utf-8"), exitCode); } catch (Exception e) { @@ -70,8 +80,8 @@ public abstract class AbstractCliTest extends TestCaseWithTmpdir { public static String getNormalizedCompilerOutput(@NotNull String pureOutput, @NotNull ExitCode exitCode, @NotNull String testDataDir) { String testDataAbsoluteDir = new File(testDataDir).getAbsolutePath(); String normalizedOutputWithoutExitCode = StringUtil.convertLineSeparators(pureOutput) - .replace(testDataAbsoluteDir, "$TESTDATA_DIR$") - .replace(FileUtil.toSystemIndependentName(testDataAbsoluteDir), "$TESTDATA_DIR$") + .replace(testDataAbsoluteDir, TESTDATA_DIR) + .replace(FileUtil.toSystemIndependentName(testDataAbsoluteDir), TESTDATA_DIR) .replace(PathUtil.getKotlinPathsForDistDirectory().getHomePath().getAbsolutePath(), "$PROJECT_DIR$") .replace("expected version is " + JvmMetadataVersion.INSTANCE, "expected version is $ABI_VERSION$") .replace("expected version is " + JsMetadataVersion.INSTANCE, "expected version is $ABI_VERSION$") @@ -81,7 +91,7 @@ public abstract class AbstractCliTest extends TestCaseWithTmpdir { return normalizedOutputWithoutExitCode + exitCode + "\n"; } - private void doTest(@NotNull String fileName, @NotNull CLITool compiler) throws Exception { + private void doTest(@NotNull String fileName, @NotNull CLITool compiler) { System.setProperty("java.awt.headless", "true"); Pair outputAndExitCode = executeCompilerGrabOutput(compiler, readArgs(fileName, tmpdir.getPath())); String actual = getNormalizedCompilerOutput( @@ -93,17 +103,17 @@ public abstract class AbstractCliTest extends TestCaseWithTmpdir { File additionalTestConfig = new File(fileName.replaceFirst("\\.args$", ".test")); if (additionalTestConfig.exists()) { - doTestAdditionalChecks(additionalTestConfig); + doTestAdditionalChecks(additionalTestConfig, fileName); } } - private void doTestAdditionalChecks(@NotNull File testConfigFile) throws IOException { + private void doTestAdditionalChecks(@NotNull File testConfigFile, @NotNull String argsFilePath) { List diagnostics = new ArrayList<>(0); String content = FilesKt.readText(testConfigFile, Charsets.UTF_8); List existsList = InTextDirectivesUtils.findListWithPrefixes(content, "// EXISTS: "); for (String fileName : existsList) { - File file = new File(tmpdir, fileName); + File file = checkedPathToFile(fileName, argsFilePath); if (!file.exists()) { diagnostics.add("File does not exist, but should: " + fileName); } @@ -114,7 +124,7 @@ public abstract class AbstractCliTest extends TestCaseWithTmpdir { List absentList = InTextDirectivesUtils.findListWithPrefixes(content, "// ABSENT: "); for (String fileName : absentList) { - File file = new File(tmpdir, fileName); + File file = checkedPathToFile(fileName, argsFilePath); if (file.exists() && file.isFile()) { diagnostics.add("File exists, but shouldn't: " + fileName); } @@ -125,7 +135,7 @@ public abstract class AbstractCliTest extends TestCaseWithTmpdir { String[] parts = containsSpec.split(",", 2); String fileName = parts[0].trim(); String contentToSearch = parts[1].trim(); - File file = new File(tmpdir, fileName); + File file = checkedPathToFile(fileName, argsFilePath); if (!file.exists()) { diagnostics.add("File does not exist: " + fileName); } @@ -147,7 +157,17 @@ public abstract class AbstractCliTest extends TestCaseWithTmpdir { } @NotNull - private static List readArgs(@NotNull String argsFilePath, @NotNull String tempDir) throws IOException { + private File checkedPathToFile(@NotNull String path, @NotNull String argsFilePath) { + if (path.startsWith(TESTDATA_DIR + "/")) { + return new File(new File(argsFilePath).getParent(), path.substring(TESTDATA_DIR.length() + 1)); + } + else { + return new File(tmpdir, path); + } + } + + @NotNull + private static List readArgs(@NotNull String argsFilePath, @NotNull String tempDir) { List lines = FilesKt.readLines(new File(argsFilePath), Charsets.UTF_8); return CollectionsKt.mapNotNull(lines, arg -> { @@ -163,7 +183,7 @@ public abstract class AbstractCliTest extends TestCaseWithTmpdir { return argsWithColonsReplaced .replace("$TEMP_DIR$", tempDir) - .replace("$TESTDATA_DIR$", new File(argsFilePath).getParent()) + .replace(TESTDATA_DIR, new File(argsFilePath).getParent()) .replace( "$FOREIGN_ANNOTATIONS_DIR$", new File(AbstractForeignAnnotationsTestKt.getFOREIGN_ANNOTATIONS_SOURCES_PATH()).getPath() @@ -171,15 +191,15 @@ public abstract class AbstractCliTest extends TestCaseWithTmpdir { }); } - protected void doJvmTest(@NotNull String fileName) throws Exception { + protected void doJvmTest(@NotNull String fileName) { doTest(fileName, new K2JVMCompiler()); } - protected void doJsTest(@NotNull String fileName) throws Exception { + protected void doJsTest(@NotNull String fileName) { doTest(fileName, new K2JSCompiler()); } - protected void doJsDceTest(@NotNull String fileName) throws Exception { + protected void doJsDceTest(@NotNull String fileName) { doTest(fileName, new K2JSDce()); } diff --git a/compiler/tests/org/jetbrains/kotlin/cli/CliTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/cli/CliTestGenerated.java index f767e14809f..d9800ce5f9e 100644 --- a/compiler/tests/org/jetbrains/kotlin/cli/CliTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/cli/CliTestGenerated.java @@ -665,6 +665,12 @@ public class CliTestGenerated extends AbstractCliTest { doJsTest(fileName); } + @TestMetadata("sourceMapRelativeRoot.args") + public void testSourceMapRelativeRoot() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/cli/js/sourceMapRelativeRoot.args"); + doJsTest(fileName); + } + @TestMetadata("sourceMapRootAuto.args") public void testSourceMapRootAuto() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/cli/js/sourceMapRootAuto.args"); diff --git a/compiler/util/src/org/jetbrains/kotlin/utils/JsLibraryUtils.kt b/compiler/util/src/org/jetbrains/kotlin/utils/JsLibraryUtils.kt index 1025ac320b8..813943a1fc6 100644 --- a/compiler/util/src/org/jetbrains/kotlin/utils/JsLibraryUtils.kt +++ b/compiler/util/src/org/jetbrains/kotlin/utils/JsLibraryUtils.kt @@ -64,7 +64,7 @@ object JsLibraryUtils { private fun File.runIfFileExists(relativePath: String, action: (JsLibrary) -> Unit) { if (isFile) { - action(JsLibrary(readText(), relativePath, correspondingSourceMapFile().contentIfExists())) + action(JsLibrary(readText(), relativePath, correspondingSourceMapFile().contentIfExists(), this)) } } @@ -126,7 +126,7 @@ object JsLibraryUtils { val stream = zipFile.getInputStream(entry) val content = FileUtil.loadTextAndClose(stream) - librariesWithoutSourceMaps += JsLibrary(content, relativePath, null) + librariesWithoutSourceMaps += JsLibrary(content, relativePath, null, null) } else if (entryName.endsWith(KotlinJavascriptMetadataUtils.JS_MAP_EXT)) { val correspondingJsPath = entryName.removeSuffix(KotlinJavascriptMetadataUtils.JS_MAP_EXT) + @@ -171,4 +171,4 @@ object JsLibraryUtils { } } -data class JsLibrary(val content: String, val path: String, val sourceMapContent: String?) +data class JsLibrary(val content: String, val path: String, val sourceMapContent: String?, val file: File?) diff --git a/jps-plugin/jps-tests/test/org/jetbrains/kotlin/jps/build/KotlinJpsBuildTest.kt b/jps-plugin/jps-tests/test/org/jetbrains/kotlin/jps/build/KotlinJpsBuildTest.kt index 77c3968c5d7..82385cdf7f5 100644 --- a/jps-plugin/jps-tests/test/org/jetbrains/kotlin/jps/build/KotlinJpsBuildTest.kt +++ b/jps-plugin/jps-tests/test/org/jetbrains/kotlin/jps/build/KotlinJpsBuildTest.kt @@ -359,7 +359,7 @@ open class KotlinJpsBuildTest : AbstractKotlinJpsBuildTestCase() { buildAllModules().assertSuccessful() val sourceMapContent = File(getOutputDir(PROJECT_NAME), "$PROJECT_NAME.js.map").readText() - val expectedPath = "prefix-dir/pkg/test1.kt" + val expectedPath = "prefix-dir/src/pkg/test1.kt" assertTrue("Source map file should contain relative path ($expectedPath)", sourceMapContent.contains("\"$expectedPath\"")) val librarySourceMapFile = File(getOutputDir(PROJECT_NAME), "lib/kotlin.js.map") diff --git a/jps-plugin/src/org/jetbrains/kotlin/jps/build/KotlinBuilder.kt b/jps-plugin/src/org/jetbrains/kotlin/jps/build/KotlinBuilder.kt index 73780a77220..6b696ab58ac 100644 --- a/jps-plugin/src/org/jetbrains/kotlin/jps/build/KotlinBuilder.kt +++ b/jps-plugin/src/org/jetbrains/kotlin/jps/build/KotlinBuilder.kt @@ -67,6 +67,7 @@ import org.jetbrains.kotlin.progress.CompilationCanceledStatus import org.jetbrains.kotlin.utils.* import org.jetbrains.org.objectweb.asm.ClassReader import java.io.File +import java.net.URI import java.util.* class KotlinBuilder : ModuleLevelBuilder(BuilderCategory.SOURCE_PROCESSOR) { @@ -647,7 +648,10 @@ class KotlinBuilder : ModuleLevelBuilder(BuilderCategory.SOURCE_PROCESSOR) { val compilerSettings = JpsKotlinCompilerSettings.getCompilerSettings(representativeModule) val k2JsArguments = JpsKotlinCompilerSettings.getK2JsCompilerArguments(representativeModule) - val sourceRoots = KotlinSourceFileCollector.getRelevantSourceRoots(representativeTarget).map { it.file } + val sourceRoots = representativeModule.contentRootsList.urls + .map { URI.create(it) } + .filter { it.scheme == "file" } + .map { File(it) } val friendPaths = KotlinBuilderModuleScriptGenerator.getProductionModulesWhichInternalsAreVisible(representativeTarget).mapNotNull { val file = getOutputMetaFile(it, false) diff --git a/js/js.dce/src/org/jetbrains/kotlin/js/dce/DeadCodeElimination.kt b/js/js.dce/src/org/jetbrains/kotlin/js/dce/DeadCodeElimination.kt index d3bb0af729a..67c20f732db 100644 --- a/js/js.dce/src/org/jetbrains/kotlin/js/dce/DeadCodeElimination.kt +++ b/js/js.dce/src/org/jetbrains/kotlin/js/dce/DeadCodeElimination.kt @@ -119,7 +119,8 @@ class DeadCodeElimination(private val logConsumer: (DCELogLevel, String) -> Unit val sourceMapFile = File(file.outputPath + ".map") val textOutput = TextOutputImpl() val sourceMapBuilder = SourceMap3Builder(File(file.outputPath), textOutput, "") - val consumer = SourceMapBuilderConsumer(sourceMapBuilder, SourceFilePathResolver(mutableListOf()), true, true) + val sourcePathResolver = SourceFilePathResolver(mutableListOf(), File(file.outputPath).parentFile) + val consumer = SourceMapBuilderConsumer(sourceMapBuilder, sourcePathResolver, true, true) block.accept(JsToStringGenerationVisitor(textOutput, consumer)) val sourceMapContent = sourceMapBuilder.build() sourceMapBuilder.addLink() diff --git a/js/js.frontend/src/org/jetbrains/kotlin/js/config/JSConfigurationKeys.java b/js/js.frontend/src/org/jetbrains/kotlin/js/config/JSConfigurationKeys.java index f2c83d67db4..2f51c0335de 100644 --- a/js/js.frontend/src/org/jetbrains/kotlin/js/config/JSConfigurationKeys.java +++ b/js/js.frontend/src/org/jetbrains/kotlin/js/config/JSConfigurationKeys.java @@ -21,6 +21,7 @@ import org.jetbrains.kotlin.incremental.js.IncrementalDataProvider; import org.jetbrains.kotlin.incremental.js.IncrementalResultsConsumer; import org.jetbrains.kotlin.serialization.js.ModuleKind; +import java.io.File; import java.util.List; public class JSConfigurationKeys { @@ -30,6 +31,9 @@ public class JSConfigurationKeys { public static final CompilerConfigurationKey SOURCE_MAP = CompilerConfigurationKey.create("generate source map"); + public static final CompilerConfigurationKey OUTPUT_DIR = + CompilerConfigurationKey.create("output directory"); + public static final CompilerConfigurationKey SOURCE_MAP_PREFIX = CompilerConfigurationKey.create("prefix to add to paths in source map"); diff --git a/js/js.frontend/src/org/jetbrains/kotlin/js/config/JsConfig.java b/js/js.frontend/src/org/jetbrains/kotlin/js/config/JsConfig.java index 77ce9f7cfa5..a7a87c061f6 100644 --- a/js/js.frontend/src/org/jetbrains/kotlin/js/config/JsConfig.java +++ b/js/js.frontend/src/org/jetbrains/kotlin/js/config/JsConfig.java @@ -125,7 +125,11 @@ public class JsConfig { @NotNull public List getSourceMapRoots() { - return configuration.get(JSConfigurationKeys.SOURCE_MAP_SOURCE_ROOTS, Collections.singletonList(".")); + return configuration.get(JSConfigurationKeys.SOURCE_MAP_SOURCE_ROOTS, Collections.emptyList()); + } + + public boolean shouldGenerateRelativePathsInSourceMap() { + return getSourceMapPrefix().isEmpty() && getSourceMapRoots().isEmpty(); } @NotNull diff --git a/js/js.inliner/src/org/jetbrains/kotlin/js/inline/FunctionReader.kt b/js/js.inliner/src/org/jetbrains/kotlin/js/inline/FunctionReader.kt index aa1a6f34813..941a55f41f3 100644 --- a/js/js.inliner/src/org/jetbrains/kotlin/js/inline/FunctionReader.kt +++ b/js/js.inliner/src/org/jetbrains/kotlin/js/inline/FunctionReader.kt @@ -23,6 +23,7 @@ import org.jetbrains.kotlin.builtins.isFunctionTypeOrSubtype import org.jetbrains.kotlin.descriptors.CallableDescriptor import org.jetbrains.kotlin.js.backend.ast.* import org.jetbrains.kotlin.js.backend.ast.metadata.* +import org.jetbrains.kotlin.js.config.JSConfigurationKeys import org.jetbrains.kotlin.js.config.JsConfig import org.jetbrains.kotlin.js.inline.util.* import org.jetbrains.kotlin.js.parser.OffsetToSourceMapping @@ -77,7 +78,8 @@ class FunctionReader( val kotlinVariable: String, val specialFunctions: Map, offsetToSourceMappingProvider: () -> OffsetToSourceMapping, - val sourceMap: SourceMap? + val sourceMap: SourceMap?, + val outputDir: File? ) { val offsetToSourceMapping by lazy(offsetToSourceMappingProvider) @@ -89,7 +91,7 @@ class FunctionReader( private val moduleNameToInfo by lazy { val result = HashMultimap.create() - JsLibraryUtils.traverseJsLibraries(config.libraries.map(::File)) { (content, path, sourceMapContent) -> + JsLibraryUtils.traverseJsLibraries(config.libraries.map(::File)) { (content, path, sourceMapContent, file) -> var current = 0 while (true) { @@ -131,7 +133,8 @@ class FunctionReader( kotlinVariable = kotlinVariable, specialFunctions = specialFunctions, offsetToSourceMappingProvider = { OffsetToSourceMapping(content) }, - sourceMap = sourceMap + sourceMap = sourceMap, + outputDir = file?.parentFile ) result.put(moduleName, moduleInfo) @@ -142,6 +145,8 @@ class FunctionReader( } private val moduleNameMap: Map + private val shouldRemapPathToRelativeForm = config.shouldGenerateRelativePathsInSourceMap() + private val relativePathCalculator = config.configuration[JSConfigurationKeys.OUTPUT_DIR]?.let { RelativePathCalculator(it) } init { moduleNameMap = buildModuleNameMap(fragments) @@ -242,7 +247,9 @@ class FunctionReader( val sourceMap = info.sourceMap if (sourceMap != null) { - val remapper = SourceMapLocationRemapper(sourceMap) + val remapper = SourceMapLocationRemapper(sourceMap) { + remapPath(removeRedundantPathPrefix(it), info) + } remapper.remap(function) wrapperStatements?.forEach { remapper.remap(it) } } @@ -301,6 +308,25 @@ class FunctionReader( param.hasDefaultValue = true } } + + private fun removeRedundantPathPrefix(path: String): String { + var index = 0 + while (index + 2 <= path.length && path.substring(index, index + 2) == "./") { + index += 2 + while (index < path.length && path[index] == '/') { + ++index + } + } + + return path.substring(index) + } + + private fun remapPath(path: String, info: ModuleInfo): String { + if (!shouldRemapPathToRelativeForm) return path + val outputDir = info.outputDir ?: return path + val calculator = relativePathCalculator ?: return path + return calculator.calculateRelativePathTo(File(outputDir, path)) ?: path + } } private val Char.isWhitespaceOrComma: Boolean diff --git a/js/js.inliner/src/org/jetbrains/kotlin/js/inline/util/RelativePathCalculator.kt b/js/js.inliner/src/org/jetbrains/kotlin/js/inline/util/RelativePathCalculator.kt new file mode 100644 index 00000000000..00365482762 --- /dev/null +++ b/js/js.inliner/src/org/jetbrains/kotlin/js/inline/util/RelativePathCalculator.kt @@ -0,0 +1,41 @@ +/* + * Copyright 2010-2017 JetBrains s.r.o. + * + * 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. + */ + +package org.jetbrains.kotlin.js.inline.util + +import java.io.File + +class RelativePathCalculator(baseDir: File) { + private val baseDirPath = generateSequence(baseDir.canonicalFile) { it.parentFile }.toList().asReversed() + + fun calculateRelativePathTo(file: File): String? { + val path = generateSequence(file.canonicalFile) { it.parentFile }.toList().asReversed() + if (baseDirPath[0] != path[0]) return null + + val commonLength = baseDirPath.zip(path).takeWhile { (first, second) -> first == second }.size + + val sb = StringBuilder() + for (i in commonLength until baseDirPath.size) { + sb.append("../") + } + for (i in commonLength until path.size) { + sb.append(path[i].name).append('/') + } + sb.setLength(sb.lastIndex) + + return sb.toString() + } +} \ No newline at end of file diff --git a/js/js.parser/src/org/jetbrains/kotlin/js/parser/sourcemaps/SourceMapLocationRemapper.kt b/js/js.parser/src/org/jetbrains/kotlin/js/parser/sourcemaps/SourceMapLocationRemapper.kt index 077eca152d9..249ca696965 100644 --- a/js/js.parser/src/org/jetbrains/kotlin/js/parser/sourcemaps/SourceMapLocationRemapper.kt +++ b/js/js.parser/src/org/jetbrains/kotlin/js/parser/sourcemaps/SourceMapLocationRemapper.kt @@ -18,7 +18,7 @@ package org.jetbrains.kotlin.js.parser.sourcemaps import org.jetbrains.kotlin.js.backend.ast.* -class SourceMapLocationRemapper(private val sourceMap: SourceMap) { +class SourceMapLocationRemapper(private val sourceMap: SourceMap, private val sourceMapPathMapper: (String) -> String = { it }) { fun remap(node: JsNode) { val listCollector = JsNodeFlatListCollector() node.accept(listCollector) @@ -65,7 +65,7 @@ class SourceMapLocationRemapper(private val sourceMap: SourceMap) { val segment = findCorrespondingSegment(node) val sourceFileName = segment?.sourceFileName node.source = if (sourceFileName != null) { - val location = JsLocation(segment.sourceFileName, segment.sourceLineNumber, segment.sourceColumnNumber) + val location = JsLocation(sourceMapPathMapper(sourceFileName), segment.sourceLineNumber, segment.sourceColumnNumber) JsLocationWithEmbeddedSource(location, null) { sourceMap.sourceContentResolver(segment.sourceFileName) } } else { diff --git a/js/js.tests/test/org/jetbrains/kotlin/js/test/BasicBoxTest.kt b/js/js.tests/test/org/jetbrains/kotlin/js/test/BasicBoxTest.kt index 7e626bcb86e..acd595db58f 100644 --- a/js/js.tests/test/org/jetbrains/kotlin/js/test/BasicBoxTest.kt +++ b/js/js.tests/test/org/jetbrains/kotlin/js/test/BasicBoxTest.kt @@ -439,7 +439,7 @@ abstract class BasicBoxTest( generatedProgram.accept(AmbiguousAstSourcePropagation()) val output = TextOutputImpl() - val pathResolver = SourceFilePathResolver(mutableListOf(File("."))) + val pathResolver = SourceFilePathResolver(mutableListOf(File(".")), null) val sourceMapBuilder = SourceMap3Builder(outputFile, output, "") generatedProgram.accept(JsToStringGenerationVisitor(output, SourceMapBuilderConsumer(sourceMapBuilder, pathResolver, false, false))) val code = output.toString() diff --git a/js/js.translator/src/org/jetbrains/kotlin/js/facade/K2JSTranslator.java b/js/js.translator/src/org/jetbrains/kotlin/js/facade/K2JSTranslator.java index 7f6edf31e10..1be3b42b001 100644 --- a/js/js.translator/src/org/jetbrains/kotlin/js/facade/K2JSTranslator.java +++ b/js/js.translator/src/org/jetbrains/kotlin/js/facade/K2JSTranslator.java @@ -128,8 +128,7 @@ public final class K2JSTranslator { ModuleDescriptor moduleDescriptor = analysisResult.getModuleDescriptor(); Diagnostics diagnostics = bindingTrace.getBindingContext().getDiagnostics(); - List sourceRoots = config.getSourceMapRoots().stream().map(File::new).collect(Collectors.toList()); - SourceFilePathResolver pathResolver = new SourceFilePathResolver(sourceRoots); + SourceFilePathResolver pathResolver = SourceFilePathResolver.create(config); AstGenerationResult translationResult = Translation.generateAst( bindingTrace, units, mainCallParameters, moduleDescriptor, config, pathResolver); @@ -155,7 +154,6 @@ public final class K2JSTranslator { ExpandIsCallsKt.expandIsCalls(newFragments); ProgressIndicatorAndCompilationCanceledStatus.checkCanceled(); - JsAstSerializer serializer = new JsAstSerializer(file -> { try { return pathResolver.getPathRelativeToSourceRoots(file); diff --git a/js/js.translator/src/org/jetbrains/kotlin/js/facade/TranslationResult.kt b/js/js.translator/src/org/jetbrains/kotlin/js/facade/TranslationResult.kt index f0fb50b74c9..b5e4b861dfc 100644 --- a/js/js.translator/src/org/jetbrains/kotlin/js/facade/TranslationResult.kt +++ b/js/js.translator/src/org/jetbrains/kotlin/js/facade/TranslationResult.kt @@ -65,9 +65,8 @@ abstract class TranslationResult protected constructor(val diagnostics: Diagnost val sourceMapBuilder = SourceMap3Builder(outputFile, output, config.sourceMapPrefix) val sourceMapBuilderConsumer = if (config.configuration.getBoolean(JSConfigurationKeys.SOURCE_MAP)) { - val sourceRoots = config.sourceMapRoots.map { File(it) } val sourceMapContentEmbedding = config.sourceMapContentEmbedding - val pathResolver = SourceFilePathResolver(sourceRoots) + val pathResolver = SourceFilePathResolver.create(config) SourceMapBuilderConsumer( sourceMapBuilder, pathResolver, diff --git a/js/js.translator/src/org/jetbrains/kotlin/js/sourceMap/SourceFilePathResolver.java b/js/js.translator/src/org/jetbrains/kotlin/js/sourceMap/SourceFilePathResolver.java index eeee0bbdc82..62de6bc3d2f 100644 --- a/js/js.translator/src/org/jetbrains/kotlin/js/sourceMap/SourceFilePathResolver.java +++ b/js/js.translator/src/org/jetbrains/kotlin/js/sourceMap/SourceFilePathResolver.java @@ -18,23 +18,33 @@ package org.jetbrains.kotlin.js.sourceMap; import com.intellij.openapi.util.text.StringUtil; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.kotlin.js.config.JSConfigurationKeys; +import org.jetbrains.kotlin.js.config.JsConfig; +import org.jetbrains.kotlin.js.inline.util.RelativePathCalculator; import java.io.File; import java.io.IOException; import java.util.*; +import java.util.stream.Collectors; public class SourceFilePathResolver { @NotNull private final Set sourceRoots; + @Nullable + private final RelativePathCalculator outputDirPathResolver; + @NotNull private final Map cache = new HashMap<>(); - public SourceFilePathResolver(@NotNull List sourceRoots) { + public SourceFilePathResolver(@NotNull List sourceRoots, @Nullable File outputDir) { this.sourceRoots = new HashSet<>(); for (File sourceRoot : sourceRoots) { this.sourceRoots.add(sourceRoot.getAbsoluteFile()); } + + outputDirPathResolver = outputDir != null ? new RelativePathCalculator(outputDir) : null; } @NotNull @@ -47,7 +57,11 @@ public class SourceFilePathResolver { return path; } + @NotNull private String calculatePathRelativeToSourceRoots(@NotNull File file) throws IOException { + String pathRelativeToOutput = calculatePathRelativeToOutput(file); + if (pathRelativeToOutput != null) return pathRelativeToOutput; + List parts = new ArrayList<>(); File currentFile = file.getCanonicalFile(); @@ -64,4 +78,19 @@ public class SourceFilePathResolver { } return file.getName(); } + + @Nullable + private String calculatePathRelativeToOutput(@NotNull File file) { + return outputDirPathResolver != null ? outputDirPathResolver.calculateRelativePathTo(file) : null; + } + + @NotNull + public static SourceFilePathResolver create(@NotNull JsConfig config) { + List sourceRoots = config.getSourceMapRoots().stream().map(File::new).collect(Collectors.toList()); + File outputDir = null; + if (config.shouldGenerateRelativePathsInSourceMap()) { + outputDir = config.getConfiguration().get(JSConfigurationKeys.OUTPUT_DIR); + } + return new SourceFilePathResolver(sourceRoots, outputDir); + } } diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/Kotlin2JsGradlePluginIT.kt b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/Kotlin2JsGradlePluginIT.kt index c5805c7a44e..32e66a38753 100644 --- a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/Kotlin2JsGradlePluginIT.kt +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/kotlin/org/jetbrains/kotlin/gradle/Kotlin2JsGradlePluginIT.kt @@ -195,7 +195,7 @@ class Kotlin2JsGradlePluginIT : BaseGradleIT() { assertFileExists(mapFilePath) val map = fileInWorkingDir(mapFilePath).readText() - val sourceFilePath = "prefixprefix/example/Dummy.kt" + val sourceFilePath = "prefixprefix/src/main/kotlin/example/Dummy.kt" assertTrue("Source map should contain reference to $sourceFilePath") { map.contains("\"$sourceFilePath\"") } } } @@ -211,8 +211,8 @@ class Kotlin2JsGradlePluginIT : BaseGradleIT() { assertFileExists(mapFilePath) val map = fileInWorkingDir(mapFilePath).readText() - assertTrue("Source map should contain reference to main.kt") { map.contains("\"main.kt\"") } - assertTrue("Source map should contain reference to foo.kt") { map.contains("\"foo.kt\"") } + assertTrue("Source map should contain reference to main.kt") { map.contains("\"./src/main/kotlin/main.kt\"") } + assertTrue("Source map should contain reference to foo.kt") { map.contains("\"./src/main/kotlin/foo.kt\"") } assertTrue("Source map should contain source of main.kt") { map.contains("\"fun main(args: Array) {\\n") } assertTrue("Source map should contain source of foo.kt") { map.contains("\"inline fun foo(): String {\\n") } } diff --git a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/kotlin2JsProjectWithSourceMapInline/build.gradle b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/kotlin2JsProjectWithSourceMapInline/build.gradle index de1fd957ef6..68acb9258b8 100644 --- a/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/kotlin2JsProjectWithSourceMapInline/build.gradle +++ b/libraries/tools/kotlin-gradle-plugin-integration-tests/src/test/resources/testProject/kotlin2JsProjectWithSourceMapInline/build.gradle @@ -24,6 +24,7 @@ allprojects { compileKotlin2Js { kotlinOptions.freeCompilerArgs = [ "-Xskip-metadata-version-check" ] kotlinOptions.sourceMap = true + kotlinOptions.sourceMapPrefix = "./" kotlinOptions.sourceMapEmbedSources = "always" } } diff --git a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/tasks/Tasks.kt b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/tasks/Tasks.kt index 2ce171c38eb..60360893574 100644 --- a/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/tasks/Tasks.kt +++ b/libraries/tools/kotlin-gradle-plugin/src/main/kotlin/org/jetbrains/kotlin/gradle/tasks/Tasks.kt @@ -461,11 +461,9 @@ open class Kotlin2JsCompile() : AbstractKotlinCompile(), args.friendModules = friendDependency - args.sourceMapBaseDirs = source.orEmpty() - .asSequence() - .filterIsInstance() - .flatMap { it.srcDirs.asSequence() } - .joinToString(File.pathSeparator) { it.absolutePath } + if (args.sourceMapBaseDirs == null && !args.sourceMapPrefix.isNullOrEmpty()) { + args.sourceMapBaseDirs = project.projectDir.absolutePath + } logger.kotlinDebug("compiling with args ${ArgumentUtils.convertArgumentsToStringList(args)}")