Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WX-1566 Special Docker build for debugging #7417

Merged
merged 10 commits into from
May 2, 2024
23 changes: 8 additions & 15 deletions docs/developers/Building.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,6 @@ features or fixes, the following are required to build Cromwell from source:
* [AdoptOpenJDK 11 HotSpot](https://adoptopenjdk.net/)
* [Git](https://git-scm.com/)

You can also use the [development image](https://github.com/broadinstitute/cromwell/tree/develop/scripts/docker-develop), and build a development container to work inside:

```bash
$ docker build -t cromwell-dev .
$ docker run -it cromwell-dev bash
```

Comment on lines -11 to -17
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have not heard of anyone using this in many years

First start by cloning the Cromwell repository from GitHub:

```bash
Expand All @@ -35,15 +28,15 @@ Finally build the Cromwell jar:
$ sbt assembly
```

NOTE: This command will run for a long time the first time.
NOTE: Compiling will not succeed on directories encrypted with ecryptfs (ubuntu encrypted home dirs for example), due to long file paths.
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This encryption package has been removed from modern Ubuntu


`sbt assembly` will build the runnable Cromwell JAR in `server/target/scala-2.13/` with a name like `cromwell-<VERSION>.jar`. It will also build a runnable Womtool JAR in `womtool/target/scala-2.13/` with a name like `womtool-<VERSION>.jar`.

To build a [Docker](https://www.docker.com/) image, run:
## Docker

```bash
$ sbt server/docker
```
The following Docker build configurations are supported. Most users will want Snapshot, resulting in an image like `broadinstitute/cromwell:<VERSION>-SNAP`.

This will build and tag a Docker image with a name like `broadinstitute/cromwell:<VERSION>-SNAP`.
| Command | Build Type | Debug Tools | Description |
|------------------------------------------------|------------|-------------|--------------------------------------|
| `sbt server/docker` | Snapshot | No | Most common local build |
| `sbt -Dproject.isDebug=true server/docker` | Debug | Yes | Local build with debugging/profiling |
| `sbt -Dproject.isSnapshot=false server/docker` | Standard | No | Reserved for CI: commit on `develop` |
| `sbt -Dproject.isRelease=true server/docker` | Release | No | Reserved for CI: numbered release |
75 changes: 63 additions & 12 deletions project/Publishing.scala
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import Version.cromwellVersion
import Version.{Debug, Release, Snapshot, Standard, cromwellVersion}
import org.apache.ivy.Ivy
import org.apache.ivy.core.IvyPatternHelper
import org.apache.ivy.core.module.descriptor.{DefaultModuleDescriptor, MDArtifact}
Expand Down Expand Up @@ -36,19 +36,27 @@ object Publishing {
ArrayBuffer(broadinstitute/cromwell:dev, broadinstitute/cromwell:develop)
*/
dockerTags := {
val versionsCsv = if (Version.isSnapshot) {
// Tag looks like `85-443a6fc-SNAP`
version.value
} else {
if (Version.isRelease) {
// Tags look like `85`, `85-443a6fc`
s"$cromwellVersion,${version.value}"
} else {
// Tag looks like `85-443a6fc`, `latest`
s"${version.value},latest"
}
val tags = Version.buildType match {
case Snapshot =>
// Ordinary local build
// Looks like `85-443a6fc-SNAP`
Seq(version.value)
case Release =>
// Looks like `85`, `85-443a6fc`
Seq(cromwellVersion, version.value)
case Debug =>
// Ordinary local build with debug stuff
// Looks like `85-443a6fc-DEBUG`
Seq(version.value)
case Standard =>
// Merge to `develop`
// Looks like `85-443a6fc`, `latest`
// TODO: once we automate releases, `latest` should move to `Release`
Seq(version.value, "latest", "develop")
}

val versionsCsv = tags.mkString(",")

// Travis applies (as of 10/22) the `dev` and `develop` tags on merge to `develop`
sys.env.getOrElse("CROMWELL_SBT_DOCKER_TAGS", versionsCsv).split(",")
},
Expand All @@ -68,6 +76,11 @@ object Publishing {
add(artifact, artifactTargetPath)
runRaw(s"ln -s $artifactTargetPath /app/$projectName.jar")

// Extra tools in debug mode only
if (Version.buildType == Debug) {
addInstruction(installDebugFacilities)
}

/*
If you use the 'exec' form for an entry point, shell processing is not performed and
environment variable substitution does not occur. Thus we have to /bin/bash here
Expand Down Expand Up @@ -116,6 +129,44 @@ object Publishing {
)
)

/**
* Install packages needed for debugging when shelled in to a running image.
*
* This includes:
* - The JDK, which includes tools like `jstack` not present in the JRE
* - The YourKit Java Profiler
* - Various Linux system & development utilities
*
* @return Instruction to run in the build
*/
def installDebugFacilities: Instruction = {
import sbtdocker.Instructions

// It is optimal to use a single `Run` instruction to minimize the number of layers in the image.
// Do not be tempted to install the default in the repositories, it's from Oracle.
//
// Documentation:
// - https://www.yourkit.com/docs/java-profiler/2024.3/help/docker_broker.jsp#setup
// - https://adoptium.net/installation/linux/#_deb_installation_on_debian_or_ubuntu
Instructions.Run(
"""apt-get update -qq && \
|apt-get install -qq --no-install-recommends file gpg htop less nload unzip vim && \
|wget -qO - https://packages.adoptium.net/artifactory/api/gpg/key/public | gpg --dearmor | \
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe also helpful to have curl and jq installed to test web connections?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

curl is already present, will add jq 👍

| tee /etc/apt/trusted.gpg.d/adoptium.gpg > /dev/null && \
|echo "deb https://packages.adoptium.net/artifactory/deb $(awk -F= '/^VERSION_CODENAME/{print$2}' /etc/os-release) main" | \
| tee /etc/apt/sources.list.d/adoptium.list && \
|apt-get update -qq && \
|apt-get install -qq temurin-11-jdk && \
|rm -rf /var/lib/apt/lists/* && \
|wget -q https://www.yourkit.com/download/docker/YourKit-JavaProfiler-2024.3-docker.zip -P /tmp/docker-build-cache/ && \
|unzip /tmp/docker-build-cache/YourKit-JavaProfiler-2024.3-docker.zip -d /tmp/docker-build-cache && \
|mkdir -p /usr/local/YourKit-JavaProfiler-2024.3/bin/ && \
|cp -R /tmp/docker-build-cache/YourKit-JavaProfiler-2024.3/bin/linux-x86-64/ /usr/local/YourKit-JavaProfiler-2024.3/bin/linux-x86-64/ && \
|rm -rf /tmp/docker-build-cache
|""".stripMargin
)
}

def dockerPushSettings(pushEnabled: Boolean): Seq[Setting[_]] =
if (pushEnabled) {
List(
Expand Down
41 changes: 33 additions & 8 deletions project/Version.scala
Original file line number Diff line number Diff line change
Expand Up @@ -8,24 +8,44 @@ object Version {
// Upcoming release, or current if we're on a master / hotfix branch
val cromwellVersion = "87"

sealed trait BuildType
case object Snapshot extends BuildType
case object Debug extends BuildType
case object Release extends BuildType
case object Standard extends BuildType

def buildType: BuildType = {
if (isDebug) Debug
else if (isRelease) Release
else if (isSnapshot) Snapshot
else Standard
}

/**
* Returns true if this project should be considered a snapshot.
*
* The value is read in directly from the system property `project.isSnapshot` as there were confusing issues with
* the multi-project and sbt.Keys#isSnapshot().
*
* Default `true`.
* This is the default if no arguments are provided.
*/
val isSnapshot: Boolean = sys.props.get("project.isSnapshot").forall(_.toBoolean)
private lazy val isSnapshot: Boolean = getPropOrDefault("project.isSnapshot", default = true)

/**
* Returns `true` if this project should tag a release like `85` in addition to a hash like `85-443a6fc`.
*
* Has no effect when `isSnapshot` is `true`.
* Returns true if this project should be built in the debugging configuration.
*
* Default `true`.
* Note that this image is much larger than the default build!
*/
val isRelease: Boolean = sys.props.get("project.isRelease").forall(_.toBoolean)
private lazy val isDebug: Boolean = getPropOrDefault("project.isDebug")

/**
* Returns `true` if this project should tag a release like `85` in addition to a hash like `85-443a6fc`.
*/
private lazy val isRelease: Boolean = getPropOrDefault("project.isRelease")

private def getPropOrDefault(prop: String, default: Boolean = false): Boolean = {
sys.props.get(prop).map(_.toBoolean).getOrElse(default)
}

// Adapted from SbtGit.versionWithGit
def cromwellVersionWithGit: Seq[Setting[_]] =
Expand Down Expand Up @@ -90,6 +110,11 @@ object Version {
val version = overrideVersion orElse commitVersion getOrElse unknownVersion

// For now, obfuscate SNAPSHOTs from sbt's developers: https://github.com/sbt/sbt/issues/2687#issuecomment-236586241
if (isSnapshot) s"$version-SNAP" else version
// (by calling it `SNAP` instead of `SNAPSHOT`)
buildType match {
case Snapshot => s"$version-SNAP"
case Debug => s"$version-DEBUG"
case _ => version
}
}
}
Loading