Skip to content

Commit

Permalink
Merge pull request #15 from ohnosequences/pr/15
Browse files Browse the repository at this point in the history
Review setting keys
  • Loading branch information
laughedelic authored Aug 30, 2016
2 parents e2aa4e3 + 7fc93c3 commit 521cc6e
Show file tree
Hide file tree
Showing 5 changed files with 161 additions and 145 deletions.
50 changes: 23 additions & 27 deletions Readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,44 +26,40 @@ addSbtPlugin("ohnosequences" % "sbt-github-release" % "<version>")

### Setting keys

Most of these keys just reflect the [parameters from Github API](http://developer.github.com/v3/repos/releases/#create-a-release):

| Key | Type | Default value |
|----------------:|:-----------------|:---------------------------------------------------|
| `notesDir` | `File` | `notes/` |
| `notesFile` | `File` | `<notesDir>/<version>.markdown` |
| `repo` | `String` | `"<organization>/<normalizedName>"` |
| `tag` | `String` | `"v<version>"` |
| `releaseName` | `String` | `"<name> <tag>"` |
| `commitish` | `String` | `""` (the default repo's branch) |
| `draft` | `Boolean` | `false` |
| `prerelease` | `Boolean` | `false` (`true` if the version has a hyphen) |
| `releaseAssets` | `Seq[File]` | all files from the `packagedArtifacts` setting |
| `mediaTypesMap` | `File => String` | [media types map](#assets) for the asset artifacts |

You can change them in your `build.sbt` for example
Most of these keys just reflect the parameters from the [Github API](http://developer.github.com/v3/repos/releases/#create-a-release):

```scala
GithubRelease.repo := "ohnosequences/sbt-github-release"
| Key | Type | Default value |
|:-------------------------|:--------------------|:---------------------------------------------------|
| `ghreleaseNotes` | `File` | `notes/<version>.markdown` |
| `ghreleaseRepoOrg` | `String` | `<organization>` |
| `ghreleaseRepoName` | `String` | `<name>` |
| `ghreleaseTag` | `String` | `"v<version>"` |
| `ghreleaseTitle` | `String` | `"<name> <tag>"` |
| `ghreleaseCommitish` | `String` | `""` (the default repo's branch) |
| `ghreleaseIsPrerelease` | `String => Boolean` | `true` if the string (tag) contains a hyphen |
| `ghreleaseMediaTypesMap` | `File => String` | [media types map](#assets) for the asset artifacts |

GithubRelease.draft := true
```

#### Assets

You set which files to attach to the release using the `releaseAssets` setting.
You can set which files to attach to the release using the `ghreleaseAssets` task (of `Seq[File]` type). By default it refers to the `packagedArtifacts` task.

Note, that Github requires to set the media ([MIME](https://en.wikipedia.org/wiki/Media_type)) type of each asset. You can customize which media types will be used through the `mediaTypesMap` setting. Github documentation refers to [IANA](https://www.iana.org/assignments/media-types/media-types.xhtml) for the list of accepted types.
Note, that Github requires to set the media ([MIME](https://en.wikipedia.org/wiki/Media_type)) type of each asset. You can customize which media types will be used through the `ghreleaseMediaTypesMap` setting. Github documentation refers to [IANA](https://www.iana.org/assignments/media-types/media-types.xhtml) for the list of accepted types.

By default `mediaTypesMap` is set to the default Java [`MimetypesFileTypeMap`](https://docs.oracle.com/javase/8/docs/api/javax/activation/MimetypesFileTypeMap.html) (with some modifications) which looks for the MIME types files in various places in your system. If you don't have any, you can download [one](http://svn.apache.org/viewvc/httpd/httpd/trunk/docs/conf/mime.types?view=co) and save it as `~/.mime.types`. If you are uploading only `.jar` and `.pom` files, you don't need to do anything.
By default `ghreleaseMediaTypesMap` is set to the default Java [`MimetypesFileTypeMap`](https://docs.oracle.com/javase/8/docs/api/javax/activation/MimetypesFileTypeMap.html) (with some modifications) which looks for the MIME types files in various places in your system. If you don't have any, you can download [one](http://svn.apache.org/viewvc/httpd/httpd/trunk/docs/conf/mime.types?view=co) and save it as `~/.mime.types`. If you are uploading only `.jar` and `.pom` files, you don't need to do anything.

If you don't want to upload any files, just set `GithubRelease.releaseAssets := Seq()`
If you don't want to upload any files, just set `GithubRelease.ghreleaseAssets := Seq()`


### Task keys

* `checkGithubCredentials` — checks Github OAuth token and helps to set it if needed
* `releaseOnGithub` — the main task, which creates a release and publishes the assests
The main task is `githubRelease`, it creates the release and publishes the assests.

There are some other tasks which work as intermediate checks:

* `ghreleaseCheckCredentials` — checks Github OAuth token and helps to set it if needed
* `ghreleaseCheckRepo` — checks that the repository exists and is accessible
* `ghreleaseCheckReleaseBuilder` — checks that Github repo contains the tag and there is no release based on it yet


### Credentials
Expand All @@ -74,7 +70,7 @@ This plugin requires an OAuth token from your Github account. It should be place
oauth = 623454b0sd3645bdfdes541dd1fdg34504a8cXXX
```

But you don't need to create this file manually — when running `releaseOnGithub`, plugin checks it and if there is no valid token, asks you to go and create one and then saves it.
But you don't need to create this file manually — when running `githubRelease`, plugin checks it and if there is no valid token, asks you to go and create one and then saves it.

<!--
### Integration with sbt-release
Expand Down
2 changes: 0 additions & 2 deletions notes/0.3.1.markdown

This file was deleted.

11 changes: 11 additions & 0 deletions notes/changelog.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
* #13: Added `ghreleaseMediaTypesMap` setting for guessing MIME type of the release assets (see also #10, #11)
* #15: Renamed setting/task keys. Now all of them are prefixed with `ghrelease` except the main task, which is now called `githubRelease`:
- `notesFile` is now `ghreleaseNotes`
- `repo` is now split in 2 parts: `ghreleaseRepoOrg` and `ghreleaseRepoName`
- `releaseName` is now `ghreleaseTitle`
- `commitish` is now `ghreleaseCommitish`
- `tag` is now `ghreleaseTag`
- `releaseAssets` task is now `ghreleaseAssets`
- `notesDir` key is removed
- `prerelease` is now `ghreleaseIsPrerelease` of type `String => Boolean`
- `draft` is temporarily removed
108 changes: 108 additions & 0 deletions src/main/scala/GithubRelease.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
package ohnosequences.sbt

import sbt._, Keys._

import org.kohsuke.github._
import scala.collection.JavaConversions._
import scala.util.Try

case object GithubRelease {

case object keys {
lazy val ghreleaseNotes = settingKey[File]("File with the release notes for the current version")
lazy val ghreleaseRepoOrg = settingKey[String]("Github repository organization")
lazy val ghreleaseRepoName = settingKey[String]("Github repository name")
lazy val ghreleaseTag = settingKey[String]("The name of the Git tag")
lazy val ghreleaseTitle = settingKey[String]("The title of the release")
lazy val ghreleaseCommitish = settingKey[String]("Specifies the commitish value that determines where the Git tag is created from")
lazy val ghreleaseMediaTypesMap = settingKey[File => String]("This function will determine media type for the assets")
lazy val ghreleaseIsPrerelease = settingKey[String => Boolean]("A function to determine release as a prerelease based on the tag name")

lazy val ghreleaseAssets = taskKey[Seq[File]]("The artifact files to upload")

// TODO: remove this, make them tasks or parameters for the main task
// lazy val draft = settingKey[Boolean]("true to create a draft (unpublished) release, false to create a published one")

lazy val ghreleaseCheckCredentials = taskKey[GitHub]("Checks authentification and suggests to create a new oauth token if needed")
lazy val ghreleaseCheckRepo = taskKey[GHRepository]("Checks repo existence and returns it if it's fine")
lazy val ghreleaseCheckReleaseBuilder = taskKey[GHReleaseBuilder]("Checks remote tag and returns empty release builder if everything is fine")

lazy val githubRelease = taskKey[GHRelease]("Publishes a release of Github")
}

case object defs {
import keys._

def getReleaseBuilder(tagName: String) = Def.task {
val log = streams.value.log
val repo = ghreleaseCheckRepo.value

val tagNames = repo.listTags.asSet.map(_.getName)
if (! tagNames.contains(tagName)) {
sys.error("Remote repository doesn't have [${tagName}] tag. You need to push it first.")
}

def releaseExists: Boolean =
repo.listReleases.asSet.map(_.getTagName).contains(tagName)

// if (!draft.value && releaseExists) {
if (releaseExists) {
sys.error("There is already a Github release based on [${tagName}] tag. You cannot release it twice.")
}

repo.createRelease(tagName)
}

def githubRelease = Def.taskDyn {
if (isSnapshot.value) {
sys.error(s"Current version is '${version.value}'. You shouldn't publish snapshots, maybe you forgot to set the release version")
}

val log = streams.value.log

val text = IO.read(ghreleaseNotes.value)
val notesPath = ghreleaseNotes.value.relativeTo(baseDirectory.value).getOrElse(ghreleaseNotes.value)
if (text.isEmpty) {
log.error(s"Release notes file [${notesPath}] is empty")
SimpleReader.readLine("Are you sure you want to continue without release notes (y/n)? [n] ") match {
case Some("n" | "N") => sys.error("Aborting release. Write release notes and try again")
case _ => // go on
}
} else log.info(s"Using release notes from the [${notesPath}] file")

val tagName = ghreleaseTag.value
val isPre = ghreleaseIsPrerelease.value(tagName)

Def.task {
val releaseBuilder = {
val rBuilder = getReleaseBuilder(tagName).value
.body(text)
.name(ghreleaseTitle.value)
.prerelease(isPre)
// .draft(draft.value)

if (ghreleaseCommitish.value.isEmpty) rBuilder
else rBuilder.commitish(ghreleaseCommitish.value)
}

val release = Try { releaseBuilder.create } getOrElse {
sys.error("Couldn't create release")
}

val pre = if (isPre) "pre-" else ""
// val pub = if(draft.value) "saved as a draft" else "published"
log.info(s"Github ${pre}release '${release.getName}' is published at\n ${release.getHtmlUrl}")

ghreleaseAssets.value foreach { asset =>
val mediaType = ghreleaseMediaTypesMap.value(asset)
val rel = asset.relativeTo(baseDirectory.value).getOrElse(asset)

release.uploadAsset(asset, mediaType)
log.info(s"Asset [${rel}] is uploaded to Github as ${mediaType}")
}

release
}
}
}
}
135 changes: 19 additions & 116 deletions src/main/scala/SbtGithubReleasePlugin.scala
Original file line number Diff line number Diff line change
Expand Up @@ -6,126 +6,28 @@ import org.kohsuke.github._
import scala.collection.JavaConversions._
import scala.util.Try

case object GithubRelease {

case object keys {
// this object is just as a namespace:
case object GithubRelease {
lazy val notesDir = settingKey[File]("Directory with release notes")
lazy val notesFile = settingKey[File]("File with the release notes for the current version")
lazy val repo = settingKey[String]("org/repo")
lazy val tag = settingKey[String]("The name of the tag: vX.Y.Z")
lazy val releaseName = settingKey[String]("The name of the release")
lazy val commitish = settingKey[String]("Specifies the commitish value that determines where the Git tag is created from")
lazy val draft = settingKey[Boolean]("true to create a draft (unpublished) release, false to create a published one")
lazy val prerelease = settingKey[Boolean]("true to identify the release as a prerelease. false to identify the release as a full release")
lazy val mediaTypesMap = settingKey[File => String]("This function will determine media type for the assets")
lazy val releaseAssets = taskKey[Seq[File]]("The artifact files to upload")
}

lazy val checkGithubCredentials = taskKey[GitHub]("Checks authentification and suggests to create a new oauth token if needed")
lazy val ghreleaseGetRepo = taskKey[GHRepository]("Checks repo existence and returns it if it's fine")
lazy val ghreleaseGetReleaseBuilder = taskKey[GHReleaseBuilder]("Checks remote tag and returns empty release builder if everything is fine")

lazy val releaseOnGithub = taskKey[GHRelease]("Publishes a release of Github")
}

case object defs {
import keys._, keys.GithubRelease._

def getReleaseBuilder(tagName: String) = Def.task {
val log = streams.value.log
val repo = ghreleaseGetRepo.value

val tagNames = repo.listTags.asSet.map(_.getName)
if (! tagNames.contains(tagName)) {
sys.error("Remote repository doesn't have [${tagName}] tag. You need to push it first.")
}

def releaseExists: Boolean =
repo.listReleases.asSet.map(_.getTagName).contains(tagName)

if (!draft.value && releaseExists) {
sys.error("There is already a Github release based on [${tagName}] tag. You cannot release it twice.")
}

repo.createRelease(tagName)
}

def releaseOnGithub = Def.taskDyn {
if (tag.value.endsWith("-SNAPSHOT"))
sys.error(s"Current version is '${version.value}'. You shouldn't publish snapshots, maybe you forgot to set the release version")

val log = streams.value.log

val text = IO.read(notesFile.value)
val notesPath = notesFile.value.relativeTo(baseDirectory.value).getOrElse(notesFile.value)
if (text.isEmpty) {
log.error(s"Release notes file [${notesPath}] is empty")
SimpleReader.readLine("Are you sure you want to continue without release notes (y/n)? [n] ") match {
case Some("n" | "N") => sys.error("Aborting release. Write release notes and try again")
case _ => // go on
}
} else log.info(s"Using release notes from the [${notesPath}] file")

val tagName = tag.value
Def.task {
val releaseBuilder = {
val rBuilder = getReleaseBuilder(tagName).value
.body(text)
.name(releaseName.value)
.draft(draft.value)
.prerelease(prerelease.value)

if (commitish.value.isEmpty) rBuilder
else rBuilder.commitish(commitish.value)
}

val release = Try { releaseBuilder.create } getOrElse {
sys.error("Couldn't create release")
}

val pre = if (prerelease.value) "pre-" else ""
val pub = if(draft.value) "saved as a draft" else "published"
log.info(s"Github ${pre}release '${release.getName}' is ${pub} at\n ${release.getHtmlUrl}")

releaseAssets.value foreach { asset =>
val mediaType = mediaTypesMap.value(asset)
val rel = asset.relativeTo(baseDirectory.value).getOrElse(asset)

release.uploadAsset(asset, mediaType)
log.info(s"Asset [${rel}] is uploaded to Github as ${mediaType}")
}

release
}
}
}
}

object SbtGithubReleasePlugin extends AutoPlugin {
case object SbtGithubReleasePlugin extends AutoPlugin {

// This plugin will load automatically
override def trigger = allRequirements

val autoImport = GithubRelease.keys

import GithubRelease._, keys._, keys.GithubRelease._
import GithubRelease._, keys._

// Default settings
override lazy val projectSettings = Seq[Setting[_]](
notesDir := baseDirectory.value / "notes",
notesFile := notesDir.value / (version.value+".markdown"),
repo := organization.value +"/"+ normalizedName.value,
tag := "v"+version.value,
releaseName := name.value +" "+ tag.value,
commitish := "",
draft := false,
ghreleaseNotes := baseDirectory.value / "notes" / (version.value+".markdown"),
ghreleaseRepoOrg := organization.value,
ghreleaseRepoName := name.value,
ghreleaseTag := "v"+version.value,
ghreleaseTitle := name.value +" "+ ghreleaseTag.value,
ghreleaseCommitish := "",
// According to the Semantic Versioning Specification (rule 9)
// a version containing a hyphen is a pre-release version
prerelease := version.value.matches(""".*-.*"""),
ghreleaseIsPrerelease := { _.matches(""".*-.*""") },

mediaTypesMap := {
ghreleaseMediaTypesMap := {
val typeMap = new javax.activation.MimetypesFileTypeMap()
// NOTE: github doesn't know about application/java-archive type (see https://developer.github.com/v3/repos/releases/#input-2)
typeMap.addMimeTypes("application/zip jar zip")
Expand All @@ -135,9 +37,9 @@ object SbtGithubReleasePlugin extends AutoPlugin {
typeMap.getContentType
},

releaseAssets := packagedArtifacts.value.values.toSeq,
ghreleaseAssets := packagedArtifacts.value.values.toSeq,

checkGithubCredentials := {
ghreleaseCheckCredentials := {
val log = streams.value.log
val conf = file(System.getProperty("user.home")) / ".github"
while (!conf.exists || !GitHub.connect.isCredentialValid) {
Expand All @@ -161,19 +63,20 @@ object SbtGithubReleasePlugin extends AutoPlugin {
GitHub.connect
},

ghreleaseGetRepo := {
ghreleaseCheckRepo := {
val log = streams.value.log
val github = checkGithubCredentials.value
val github = ghreleaseCheckCredentials.value
val repo = s"${ghreleaseRepoOrg.value}/${ghreleaseRepoName.value}"

val repository = Try { github.getRepository(repo.value) } getOrElse {
sys.error(s"Repository ${repo.value} doesn't exist or is not accessible.")
val repository = Try { github.getRepository(repo) } getOrElse {
sys.error(s"Repository ${repo} doesn't exist or is not accessible.")
}
repository
},

// ghreleaseGetReleaseBuilder := getReleaseBuilder.value,
// ghreleaseCheckReleaseBuilder := getReleaseBuilder.value,

releaseOnGithub := defs.releaseOnGithub.value
githubRelease := defs.githubRelease.value
)

}

0 comments on commit 521cc6e

Please sign in to comment.