@@ -94,7 +94,11 @@ lazy val commonSettings = Seq(
9494/**
9595 * Copy source files and translate them to the java.time package
9696 */
97- def copyAndReplace (srcDirs : Seq [File ], destinationDir : File ): Seq [File ] = {
97+ def copyAndReplace (
98+ srcDirs : Seq [File ],
99+ destinationDir : File ,
100+ createNestedDirs : Boolean = false
101+ ): Seq [File ] = {
98102 // Copy a directory and return the list of files
99103 def copyDirectory (
100104 source : File ,
@@ -112,7 +116,12 @@ def copyAndReplace(srcDirs: Seq[File], destinationDir: File): Seq[File] = {
112116 // Copy the source files from the base project, exclude classes on java.util and dirs
113117 val generatedFiles : List [java.io.File ] = onlyScalaDirs
114118 .foldLeft(Set .empty[File ]) { (files, sourceDir) =>
115- files ++ copyDirectory(sourceDir, destinationDir, overwrite = true )
119+ val targetDestinationDir = if (createNestedDirs) {
120+ destinationDir / sourceDir.getName
121+ } else {
122+ destinationDir
123+ }
124+ files ++ copyDirectory(sourceDir, targetDestinationDir, overwrite = true )
116125 }
117126 .filterNot(_.isDirectory)
118127 .filter(_.getName.endsWith(" .scala" ))
@@ -121,6 +130,13 @@ def copyAndReplace(srcDirs: Seq[File], destinationDir: File): Seq[File] = {
121130
122131 // These replacements will in practice rename all the classes from
123132 // org.threeten to java.time
133+ //
134+ // !!! WARNING: AVOID MODIFYING FILE CONTENTS HERE MORE THAN ABSOLUTELY NECESSARY !!!
135+ // - The more significant the change, in terms of changing source code position
136+ // (line number + column), the more broken the Scala.js source maps will be,
137+ // preventing debugging of Scala.js code that uses scala-java-code in the browser.
138+ // - Line-for-line replacements of `package/import x` with `package/import y` are
139+ // mostly ok because they don't affect the remaining code in the file.
124140 def replacements (line : String ): String =
125141 line
126142 .replaceAll(" package org.threeten$" , " package java" )
@@ -141,6 +157,7 @@ def copyAndReplace(srcDirs: Seq[File], destinationDir: File): Seq[File] = {
141157lazy val core = crossProject(JVMPlatform , JSPlatform , NativePlatform )
142158 .crossType(CrossType .Full )
143159 .in(file(" core" ))
160+ .disablePlugins(TypelevelScalaJSGitHubPlugin )
144161 .settings(commonSettings)
145162 .settings(
146163 name := " scala-java-time" ,
@@ -152,10 +169,11 @@ lazy val core = crossProject(JVMPlatform, JSPlatform, NativePlatform)
152169 if (tlIsScala3.value) Seq (" -scalajs-genStaticForwardersForNonTopLevelObjects" )
153170 else Seq (" -P:scalajs:genStaticForwardersForNonTopLevelObjects" )
154171 },
172+ scalaJsGithubSourceMaps(" core/shared" ),
155173 Compile / sourceGenerators += Def .task {
156174 val srcDirs = (Compile / sourceDirectories).value
157175 val destinationDir = (Compile / sourceManaged).value
158- copyAndReplace(srcDirs, destinationDir)
176+ copyAndReplace(srcDirs, destinationDir, createNestedDirs = true )
159177 }.taskValue,
160178 libraryDependencies ++= Seq (
161179 " io.github.cquiroz" %%% " scala-java-locales" % scalajavaLocalesVersion
@@ -265,3 +283,40 @@ lazy val demo = crossProject(JSPlatform, JVMPlatform, NativePlatform)
265283 .nativeSettings(
266284 tzdbPlatform := TzdbPlugin .Platform .Native
267285 )
286+
287+ def scalaJsGithubSourceMaps (projectDir : String ) =
288+ // - Unfortunately we can only specify one `projectDir`.
289+ // So, if our JS project has sources in both `core/js` and `core/shared`,
290+ // we can only pick one of those, and all sources in the other one will have
291+ // broken URLs in source maps.
292+ // - The root of the problem is that in `copyAndReplace` we copy the contents of
293+ // multiple source directories into a single src_managed directory, and so we
294+ // lose information about where our sources originally came from, and even if we
295+ // could capture or recover this information, the syntax of `mapSourceURI` option
296+ // doesn't allow more than one mapping, so we probably wouldn't be able to use it
297+ // to create multiple mappings.
298+ // - Also, for the same reason, we are unable to map sources that are not copied
299+ // to src_managed (stuff under java.util). Those will have invalid file: URLs.
300+ // - In the future, maybe we can somehow adjust the folder structure inside `src_managed`
301+ // to introduce new top level directories matching project names (`core`).
302+ // - The CI env var is set by Github actions automatically.
303+ // We only want this transformation to run when creating & publishing an artifact to Maven,
304+ // as this lets us use the original local file paths for local dev / publishLocal.
305+ // ---
306+ // - How to test changes to this code locally:
307+ // - Make sure the sys.env.get("CI") filter passes, one way or another
308+ // - publishLocal the core project (press enter to skip passphrase, don't need signing)
309+ // - find the resulting jar – see file path in sbt output. Open it (it's a zip archive).
310+ // - find .sjsir files inside the jar, open some of them with plain text editor
311+ // - in the first bytes of the binary, observe the raw.githubusercontent.com URL in plaintext
312+ // - copy-paste that URL into the browser.
313+ // - It should show the contents of this file on github (subject to caveats above)
314+ // - If it doesn't, make sure the version / commit hash you have locally exists on github (or fake it).
315+ scalacOptions ++= sys.env.get(" CI" ).map { _ =>
316+ val localCopiedSourcesPath = (Compile / sourceManaged).value.toURI
317+ val remoteSourcesPath =
318+ s " https://raw.githubusercontent.com/cquiroz/scala-java-time/ ${git.gitHeadCommit.value.get}/ ${projectDir}/src/main/ "
319+ val sourcesOptionName =
320+ if (tlIsScala3.value) " -scalajs-mapSourceURI" else " -P:scalajs:mapSourceURI"
321+ s " ${sourcesOptionName}: $localCopiedSourcesPath-> $remoteSourcesPath"
322+ }
0 commit comments