//| mill-version: 1.0.0 //| mill-jvm-version: 11 package build // see mill-build/build.mill import millbuild.MillBuildCommonDependencies import mill._ import mill.api.{BuildCtx,Result} import mill.scalalib._ import mill.scalalib.publish._ import mill.util.{JarManifest,Jvm} import scala.annotation.tailrec /* * This build supports the environment variable SOURCE_DATE_EPOCH for deterministic builds. * See https://reproducible-builds.org/docs/source-date-epoch/ */ object Dependency { val MchangeCommonsJavaVersion = MillBuildCommonDependencies.McjVersion val MchangeCommonsJava = mvn"com.mchange:mchange-commons-java:${MchangeCommonsJavaVersion}" val JUnit = mvn"org.junit.vintage:junit-vintage-engine:5.10.2" val PgJdbc = mvn"org.postgresql:postgresql:42.6.0" val Hsqldb = mvn"org.hsqldb:hsqldb:2.7.4" } object `package` extends JavaModule with SonatypeCentralPublishModule { val organization = "com.mchange" override def artifactName = Task {"c3p0"} override def publishVersion = Task {"0.11.3-SNAPSHOT"} override def sonatypeCentralShouldRelease: Task.Simple[Boolean] = Task { false } // we are currently building in Java 11, but releasing Java 7 compatible class files // for users of smaller JDBC subsets val JvmCompatVersion = "7" // we don't use the newer --release flag, we intentionally compile against newer API, so newer API is supported // but old JVMs that hit it wil generate NoSuchMethodError or similar at runtime override def javacOptions = Seq("-source",JvmCompatVersion,"-target",JvmCompatVersion) trait Gen extends JavaModule { // cpPassingJarPath handling is modified from https://github.com/com-lihaoyi/mill/blob/06170d57a211430b63c383d9e627ffe4088649d6/libs/javalib/src/mill/javalib/RunModule.scala def runIf(conditionAndArgs: Task[Args]): Command[Unit] = Task.Command { val caa = conditionAndArgs().value val condition = caa.head // println(s"!!!!! $condition") val args = caa.tail val truthyCondition = "true".equalsIgnoreCase(condition) || "t".equalsIgnoreCase(condition) if (truthyCondition) { try Result.Success( Jvm.callProcess( mainClass = finalMainClass(), classPath = runClasspath().map(_.path), jvmArgs = forkArgs(), env = forkEnv(), mainArgs = args, cwd = forkWorkingDir(), cpPassingJarPath = if runUseArgsFile() then Some(os.temp(prefix = "run-", suffix = ".jar", deleteOnExit = false)) else None ) ) catch { case e: Exception => Result.Failure("subprocess failed") } } else { Result.Success( () ) } } override def mvnDeps = Task { super.mvnDeps() ++ Seq(Dependency.MchangeCommonsJava) } override def forkArgs = Task {Seq("-Dcom.mchange.v2.log.MLog=com.mchange.v2.log.FallbackMLog")} } object bean extends Gen {} object proxy extends Gen { override def sources = Task { BuildCtx.withFilesystemCheckerDisabled { super.sources() :+ PathRef( BuildCtx.workspaceRoot / "src-proxy-interface" ) } } override def mainClass = Task { Some("com.mchange.v2.c3p0.codegen.JdbcProxyGenerator") } } val Debug = true val Trace = 10 // 10 is TRACE_MAX, 5 for less tracing // Still very little evidence that there's any meaningful performance // gain statically dropping Debug code. Better to keep the often-very-useful // debug-level logging. // // val Debug = false // val Trace = 0 override def sources = Task { BuildCtx.withFilesystemCheckerDisabled { super.sources() :+ PathRef( BuildCtx.workspaceRoot / "src-proxy-interface" ) } } override def mvnDeps = Task { super.mvnDeps() ++ Seq(Dependency.MchangeCommonsJava) } private val WritePermissions : os.PermSet = "-w--w--w-" def readOnly( perms : os.PermSet ) : os.PermSet = perms -- WritePermissions // i intended to make generated source dir contents read-only, but this prevents the build from // overwriting when necessary. so this is not currently in use. // // i'd have to add logic to restore writability during rebuilds, cleans, // etc which seems maybe too complicated. def makeDirContentsReadOnly( dir : os.Path ) = os.walk(dir).foreach( p => os.perms.set(p, readOnly(os.perms(p))) ) def buildModTime : Task[Long] = Task.Input { val out = math.max( os.mtime( BuildCtx.workspaceRoot / "build.mill" ), os.mtime( BuildCtx.workspaceRoot / "mill-build" / "build.mill" ) ) //println( s"buildModTime: $out" ) out } private def recursiveDirModTime( dir : os.Path ) : Long = { if (!os.exists(dir)) { -1L } else { val mtimes = os.walk.attrs(dir, includeTarget = true).map( _._2.mtime.toMillis ) if (mtimes.size < 2) -1L else mtimes.max // if there's only one mtime, it's the empty target } } def genDebugSources = Task { // mill correctly caches these with no work on our part com.mchange.v2.debug.DebugGen.main(Array("--packages=com.mchange",s"--codebase=${BuildCtx.workspaceRoot}/src",s"--outputbase=${Task.dest}","--recursive",s"--debug=${Debug}",s"--trace=${Trace}")) PathRef(Task.dest) } def genC3P0SubstitutionsSource = Task(persistent=true) { val version = publishVersion() val bmt = buildModTime() val dmt = recursiveDirModTime( Task.dest ) if ( bmt > dmt ) { import java.time.Instant val timestamp = sys.env.get("SOURCE_DATE_EPOCH").map( sde => Instant.ofEpochSecond( sde.toLong ) ).getOrElse( Instant.now ) val text = s"""|package com.mchange.v2.c3p0.subst; | |public final class C3P0Substitutions |{ | public final static String VERSION = "${version}"; | public final static String DEBUG = "${Debug}"; | public final static String TRACE = "${Trace}"; | public final static String TIMESTAMP = "${timestamp}"; | | private C3P0Substitutions() | {} |} |""".stripMargin val path = Task.dest / "com" / "mchange" / "v2" / "c3p0" / "subst" / "C3P0Substitutions.java" System.err.println("Regenerating C3P0Substitutions.java") os.write.over( path, data = text, createFolders = true ) } PathRef(Task.dest) } def beangenGeneratedSourceDir = Task(persistent=true) { Task.dest } def beangen = Task { BuildCtx.withFilesystemCheckerDisabled { val realDest = beangenGeneratedSourceDir() bean.runIf( Task.Anon( Args( (Seq(buildModTime(),recursiveDirModTime(BuildCtx.workspaceRoot/"bean"/"beangen"),recursiveDirModTime(BuildCtx.workspaceRoot/"bean"/"src")).max > recursiveDirModTime(beangenGeneratedSourceDir())).toString, BuildCtx.workspaceRoot/"bean/beangen/com/mchange/v2/c3p0/impl", (beangenGeneratedSourceDir()/"com"/"mchange"/"v2"/"c3p0"/"impl").toString() ) ) )() PathRef(realDest) } } def proxygenGeneratedSourceDir = Task(persistent=true) { Task.dest } def proxygen = Task { BuildCtx.withFilesystemCheckerDisabled { val realDest = proxygenGeneratedSourceDir() proxy.runIf( Task.Anon( Args( (math.max(buildModTime(),recursiveDirModTime(BuildCtx.workspaceRoot/"proxy"/"src")) > recursiveDirModTime(proxygenGeneratedSourceDir())).toString, proxygenGeneratedSourceDir().toString ) ) )() PathRef(realDest) } } override def generatedSources = Task { super.generatedSources() ++ Seq(genDebugSources(),genC3P0SubstitutionsSource(),beangen(),proxygen()) } override def manifest = Task { val mainTups = JarManifest.MillDefault.main + Tuple2("Automatic-Module-Name","com.mchange.v2.c3p0") JarManifest( main = mainTups ) } def allLicenses : Task[Seq[PathRef]] = Task { BuildCtx.withFilesystemCheckerDisabled { (BuildCtx.workspaceRoot / "LICENSE" :: BuildCtx.workspaceRoot / "LICENSE-LGPL" :: BuildCtx.workspaceRoot / "LICENSE-EPL" :: Nil).map( PathRef(_) ) } } // is the same manifest good? // some users want licenses in the source jars... https://github.com/swaldman/c3p0/issues/167 def sourceJar: Task.Simple[PathRef] = Task { val out = Task.dest / "source.jar" Jvm.createJar( jar = out, inputPaths = (allSources() ++ resources() ++ compileResources() ++ allLicenses()).map(_.path).filter(os.exists), manifest = manifest() ) PathRef(out) } object doc extends JavaModule { val StagingDir = os.Path("/Users/swaldman/development/gitproj/www.mchange.com/projects/c3p0-versions") def replacements : Task[Map[String,String]] = Task { import java.time.ZonedDateTime Map( "@c3p0.version@" -> build.publishVersion(), "@mchange-commons-java.version@" -> Dependency.MchangeCommonsJavaVersion, "@c3p0.copyright.year@" -> ZonedDateTime.now().getYear.toString ); } @tailrec def replaceAll( replacements : List[(String,String)], template : String ) : String = { replacements match { case head :: tail => replaceAll(tail, template.replace(head._1, head._2)) case Nil => template } } def docsrc : Task[PathRef] = Task.Source { moduleDir / "docsrc" } def docroot : Task[PathRef] = Task { val javadocRoot = build.docJar().path / os.up / "javadoc" val replaceMap = replacements() val docsrcDir = docsrc().path val sourcePaths = os.walk(docsrcDir) def destPath( sourcePath : os.Path ) = Task.dest / sourcePath.relativeTo(docsrcDir) sourcePaths.foreach { sp => if (sp.toString.endsWith(".html")) { val replaced = replaceAll( replaceMap.toList, os.read(sp) ) os.write(destPath(sp), replaced) } else os.copy.over( from=sp, to=destPath(sp) ) } os.copy.over( from = javadocRoot, to = Task.dest / "apidocs" ) PathRef(Task.dest) } def stage : Task[Unit] = Task { os.copy.over( from = docroot().path, to = StagingDir / ("c3p0-" + build.publishVersion()) ) } } // for now at least, I don't mean to publish tests as a public library, // but I do want to publish them locally so I have them when working on related // libraries object test extends JavaModule with TestModule.Junit5 with PublishModule { override def moduleDeps = Seq(build) override def artifactName = Task {"c3p0-test"} override def publishVersion = Task { build.publishVersion() } // when you want to generate tests you can try under old JVMs override def javacOptions = Seq("-source",JvmCompatVersion,"-target",JvmCompatVersion) /** * A place for resources we want seen by local (test) runs, but * should not be published (even locally) into the c3p0-test jar, * so that when we run tests from elsewhere, they are not polluted * by settings in these resources */ def localResources : Task[PathRef] = Task.Source { moduleDir / "resources-local" } def localResourcesRough : Task[PathRef] = Task.Source { moduleDir / "resources-local-rough" } override def runClasspath : Task.Simple[Seq[PathRef]] = Task { super.runClasspath() :+ localResources() // super.runClasspath() :+ localResourcesRough() } def externalClassPath : Task[String] = Task { val sep = java.io.File.pathSeparator runClasspath().map( _.path ).mkString( sep ) } def externalForkArgs : Task[String] = Task { forkArgs().mkString(" ") } def externalCommandBase : Task[String] = Task[String] { List("java",externalForkArgs(),"-cp",externalClassPath()).mkString(" ") } def printExternalCommandBase : Task[Unit] = Task { println(externalCommandBase()) } override def mvnDeps = Task { super.mvnDeps() ++ Seq( Dependency.JUnit, Dependency.PgJdbc, //Dependency.Hsqldb ) } override def forkArgs = Task { "-Dc3p0.jdbcUrl=jdbc:postgresql://localhost:5432/c3p0" :: //"-Dc3p0.jdbcUrl=jdbc:hsqldb:hsql://localhost/mydb;hsqldb.sqllog=3" :: // hsqldb //"-Dc3p0.user=SA" :: // hsqldb //"-Dc3p0.password=" :: // hsqldb //"-Dc3p0.maxStatements=100" :: //"-Dc3p0.maxStatementsPerConnection=5" :: //"-Dc3p0.cancelAutomaticallyClosedStatements=true" :: //"-Dcom.sun.management.jmxremote.port=38383" :: //"-Dcom.sun.management.jmxremote.authenticate=false" :: //"-Dcom.sun.management.jmxremote.ssl=false" :: //"-server" :: //"-Xrunhprof:cpu=times,file=/tmp/java.hprof,doe=y,format=a" :: //"-Xprof" :: //"-Xrunhprof:file=/tmp/java.hprof,doe=y,format=b" :: //"-verbose:class" //"-ea" :: "-Djava.rmi.server.hostname=localhost" :: s"""-Djava.util.logging.config.file=${moduleDir / "conf-logging" / "logging.properties"}""" :: Nil } def c3p0Benchmark = Task { this.runMain("com.mchange.v2.c3p0.test.C3P0BenchmarkApp")() } def c3p0Stats = Task { this.runMain("com.mchange.v2.c3p0.test.StatsTest")() } def c3p0Proxywrapper = Task { this.runMain("com.mchange.v2.c3p0.test.ProxyWrappersTest")() } def c3p0RawConnectionOp = Task { this.runMain("com.mchange.v2.c3p0.test.RawConnectionOpTest")() } def c3p0Load = Task { this.runMain("com.mchange.v2.c3p0.test.LoadPoolBackedDataSource")() } def c3p0PSLoad = Task { this.runMain("com.mchange.v2.c3p0.test.PSLoadPoolBackedDataSource")() } def c3p0InterruptedBatch = Task { this.runMain("com.mchange.v2.c3p0.test.InterruptedBatchTest")() } def c3p0Dispersion = Task { this.runMain("com.mchange.v2.c3p0.test.ConnectionDispersionTest")() } def c3p0OneThreadRepeat = Task { this.runMain("com.mchange.v2.c3p0.test.OneThreadRepeatedInsertOrQueryTest")() } def c3p0RefSer : Task.Simple[Unit] = Task { this.runMain("com.mchange.v2.c3p0.test.TestRefSerStuff") } def c3p0JavaBeanRef : Task.Simple[Unit] = Task { this.runMain("com.mchange.v2.c3p0.test.JavaBeanRefTest") } def c3p0DynamicPreparedStatement : Task.Simple[Unit] = Task { this.runMain("com.mchange.v2.c3p0.test.DynamicPreparedStatementTest") } override def pomSettings = Task { PomSettings( description = "Tests of c3p0, a mature JDBC3+ Connection pooling library", organization = organization, url = "https://www.mchange.com/projects/c3p0", licenses = Seq(License.`LGPL-2.1-or-later`,License.`EPL-1.0`), versionControl = VersionControl.github("swaldman", "c3p0"), developers = Seq( Developer("swaldman", "Steve Waldman", "https://github.com/swaldman") ) ) } // just to give ourselves the command `$ mill -i test.scalamod.console` object scalamod extends ScalaModule { override def moduleDeps = Seq(build.test) override def scalaVersion = "3.3.4" override def runClasspath = Task { build.test.runClasspath() } override def forkArgs = Task { build.test.forkArgs() } } } override def pomSettings = Task { PomSettings( description = "A mature JDBC3+ Connection pooling library", organization = organization, url = "https://www.mchange.com/projects/c3p0", licenses = Seq(License.`LGPL-2.1-or-later`,License.`EPL-1.0`), versionControl = VersionControl.github("swaldman", "c3p0"), developers = Seq( Developer("swaldman", "Steve Waldman", "https://github.com/swaldman") ) ) } // intended to publish development snapshots, not for stable releases val MchangeStaging = "/Users/swaldman/development/gitproj/www.mchange.com/repository" def publishMchange() = this.publishM2Local( MchangeStaging ) }