summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLars Wirzenius <liw@liw.fi>2021-04-22 05:43:18 +0000
committerLars Wirzenius <liw@liw.fi>2021-04-22 05:43:18 +0000
commit4427fe12410f911a9c8f18ea7b3c9643c88f6d5f (patch)
treee05affad9e645047bddee6a93ca69e11445f50fa
parent1600a727446867029451df6f8a181b5a07709efb (diff)
parent24574aef2299925c10eeb9d20a942f36d1d806d4 (diff)
downloadbumper-rs-4427fe12410f911a9c8f18ea7b3c9643c88f6d5f.tar.gz
Merge branch 'tagname-take2' into 'main'
make git tag name be configurable via a template Closes #1 See merge request larswirzenius/bumper!19
-rw-r--r--bumper.md15
-rw-r--r--src/bin/bumper.rs21
-rw-r--r--src/debian.rs18
-rw-r--r--src/errors.rs15
-rw-r--r--src/git.rs4
-rw-r--r--src/lib.rs1
-rw-r--r--src/project.rs8
-rw-r--r--src/python.rs22
-rw-r--r--src/rust.rs4
-rw-r--r--src/tag.rs168
-rw-r--r--subplot/bumper.py4
-rw-r--r--subplot/bumper.yaml3
12 files changed, 273 insertions, 10 deletions
diff --git a/bumper.md b/bumper.md
index 1fc4336..d00c08c 100644
--- a/bumper.md
+++ b/bumper.md
@@ -146,15 +146,24 @@ release for a Python project.
~~~scenario
given an installed Bumper
-given file foo/setup.py from empty
+given file foo/setup.py from setup.py
+given file foo/setup.py is executable
given file foo/lib/version.py from empty
given all files in foo are committed to git
-when I run, in foo, bumper 105.12765.42
+when I run, in foo, bumper --tag=foo-%v 105.12765.42
then all changes in foo are committed
-then in foo, git tag v105.12765.42 is a signed tag
+then in foo, git tag foo-105.12765.42 is a signed tag
then file foo/lib/version.py matches regex /__version__\s*=\s*"105\.12765\.42"/
~~~
+~~~{#setup.py .file .python}
+#!/usr/bin/env python3
+from distutils.core import setup
+setup(
+ name="vmdb2",
+)
+~~~
+
---
title: bumper &ndash; set version number for a project
diff --git a/src/bin/bumper.rs b/src/bin/bumper.rs
index 59443e5..9fbf898 100644
--- a/src/bin/bumper.rs
+++ b/src/bin/bumper.rs
@@ -1,6 +1,7 @@
use bumper::errors::BumperError;
use bumper::git;
use bumper::project::ProjectKind;
+use bumper::tag::Tag;
use log::{debug, error};
use std::path::{Path, PathBuf};
use std::process::exit;
@@ -20,14 +21,25 @@ fn bumper() -> Result<(), BumperError> {
let cwd = abspath(".")?;
println!("Setting version for project in {}", cwd.display());
- for mut kind in ProjectKind::detect(&cwd)? {
+
+ let mut kinds = ProjectKind::detect(&cwd)?;
+ if kinds.is_empty() {
+ return Err(BumperError::UnknownProjectKind(cwd));
+ }
+ let name = kinds[0].name()?;
+
+ for kind in kinds.iter_mut() {
let version = kind.set_version(&opt.version)?;
println!("{} project set to {}", kind.desc(), version);
}
- let msg = format!("Set version to {}", opt.version);
+ let msg = format!("Release version {}", opt.version);
git::commit(".", &msg)?;
- git::tag(".", &opt.version)?;
+
+ let tag = Tag::new(&opt.tag)?;
+ let tag = tag.apply(&name, &opt.version);
+ println!("release tag: {}", tag);
+ git::tag(".", &tag, &msg)?;
debug!("Bumper ends OK");
Ok(())
}
@@ -41,6 +53,9 @@ fn abspath<P: AsRef<Path>>(path: P) -> Result<PathBuf, BumperError> {
#[derive(Debug, StructOpt)]
struct Opt {
+ #[structopt(long, default_value = "v%v")]
+ tag: String,
+
#[structopt(help = "version number of new release")]
version: String,
}
diff --git a/src/debian.rs b/src/debian.rs
index 3f9e01b..15b87ed 100644
--- a/src/debian.rs
+++ b/src/debian.rs
@@ -19,6 +19,24 @@ impl Debian {
Err(BumperError::UnknownProjectKind(dirname.to_path_buf()))
}
+ pub fn name(&self) -> Result<String, BumperError> {
+ let output = Command::new("dpkg-parsechangelog")
+ .arg("-SSource")
+ .current_dir(&self.dirname)
+ .output()
+ .map_err(|err| BumperError::ParseChangelogInvoke(self.dirname.to_path_buf(), err))?;
+ if output.status.success() {
+ let name = String::from_utf8_lossy(&output.stdout).into_owned();
+ Ok(name.trim_end().to_string())
+ } else {
+ let stderr = String::from_utf8_lossy(&output.stderr).into_owned();
+ Err(BumperError::ParseChangelog(
+ self.dirname.to_path_buf(),
+ stderr,
+ ))
+ }
+ }
+
pub fn set_version(&mut self, version: &str) -> Result<String, BumperError> {
let version = format!("{}-1", version);
self.dch(&["-v", &version, ""])?;
diff --git a/src/errors.rs b/src/errors.rs
index 97ed8d8..a296afd 100644
--- a/src/errors.rs
+++ b/src/errors.rs
@@ -2,6 +2,9 @@ use std::path::PathBuf;
#[derive(Debug, thiserror::Error)]
pub enum BumperError {
+ #[error("Tag pattern is not ASCII: {0}")]
+ TagPatternNotAscii(String),
+
#[error("Can't figure out what kind of project: {0}")]
UnknownProjectKind(PathBuf),
@@ -44,6 +47,18 @@ pub enum BumperError {
#[error("dch failed in {0}: {1}")]
Dch(PathBuf, String),
+ #[error("Failed to run dpkg-parsechangelog in {0}: {1}")]
+ ParseChangelogInvoke(PathBuf, #[source] std::io::Error),
+
+ #[error("dpkg-parsechangelog failed in {0}: {1}")]
+ ParseChangelog(PathBuf, String),
+
+ #[error("Failed to run setup.py in {0}: {1}")]
+ Setupnvoke(PathBuf, #[source] std::io::Error),
+
+ #[error("setup.py failed in {0}: {1}")]
+ Setup(PathBuf, String),
+
#[error("Failed to run cargo in {0}: {1}")]
CargoInvoke(PathBuf, #[source] std::io::Error),
diff --git a/src/git.rs b/src/git.rs
index ea640d6..061ed61 100644
--- a/src/git.rs
+++ b/src/git.rs
@@ -3,9 +3,7 @@ use log::debug;
use std::path::Path;
use std::process::Command;
-pub fn tag<P: AsRef<Path>>(dirname: P, version: &str) -> Result<(), BumperError> {
- let msg = format!("release version {}", version);
- let tag_name = format!("v{}", version);
+pub fn tag<P: AsRef<Path>>(dirname: P, tag_name: &str, msg: &str) -> Result<(), BumperError> {
git(dirname.as_ref(), &["tag", "-am", &msg, &tag_name])?;
Ok(())
}
diff --git a/src/lib.rs b/src/lib.rs
index e0c0fca..6285d20 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -4,3 +4,4 @@ pub mod git;
pub mod project;
pub mod python;
pub mod rust;
+pub mod tag;
diff --git a/src/project.rs b/src/project.rs
index 655fb23..9941ef7 100644
--- a/src/project.rs
+++ b/src/project.rs
@@ -48,6 +48,14 @@ impl ProjectKind {
}
}
+ pub fn name(&mut self) -> Result<String, BumperError> {
+ match self {
+ Self::Debian(x) => x.name(),
+ Self::Python(x) => x.name(),
+ Self::Rust(x) => x.name(),
+ }
+ }
+
pub fn set_version(&mut self, version: &str) -> Result<String, BumperError> {
Ok(match self {
Self::Rust(ref mut rust) => rust.set_version(version)?,
diff --git a/src/python.rs b/src/python.rs
index 4cc043e..28c0aec 100644
--- a/src/python.rs
+++ b/src/python.rs
@@ -2,8 +2,10 @@ use crate::errors::BumperError;
use glob::glob;
use log::{debug, info};
use std::path::{Path, PathBuf};
+use std::process::Command;
pub struct Python {
+ dirname: PathBuf,
version_pys: Vec<PathBuf>,
}
@@ -17,13 +19,31 @@ impl Python {
debug!("no version.py files in {}", dirname.display());
Err(BumperError::NoVersionPy(dirname.to_path_buf()))
} else {
- Ok(Self { version_pys: files })
+ Ok(Self {
+ dirname: dirname.to_path_buf(),
+ version_pys: files,
+ })
}
} else {
Err(BumperError::UnknownProjectKind(dirname.to_path_buf()))
}
}
+ pub fn name(&mut self) -> Result<String, BumperError> {
+ let output = Command::new("./setup.py")
+ .arg("--name")
+ .current_dir(&self.dirname)
+ .output()
+ .map_err(|err| BumperError::Setupnvoke(self.dirname.to_path_buf(), err))?;
+ if output.status.success() {
+ let name = String::from_utf8_lossy(&output.stdout).into_owned();
+ Ok(name.trim_end().to_string())
+ } else {
+ let stderr = String::from_utf8_lossy(&output.stderr).into_owned();
+ Err(BumperError::Setup(self.dirname.to_path_buf(), stderr))
+ }
+ }
+
pub fn set_version(&mut self, version: &str) -> Result<String, BumperError> {
for filename in self.version_pys.iter() {
info!("writing Python version to {}", filename.display());
diff --git a/src/rust.rs b/src/rust.rs
index 0ffe8c0..2dbe1f7 100644
--- a/src/rust.rs
+++ b/src/rust.rs
@@ -27,6 +27,10 @@ impl Rust {
Err(BumperError::UnknownProjectKind(dirname.to_path_buf()))
}
+ pub fn name(&mut self) -> Result<String, BumperError> {
+ self.cargo_toml.name()
+ }
+
pub fn set_version(&mut self, version: &str) -> Result<String, BumperError> {
debug!("parsing Cargo.toml from {}", self.dirname.display());
// debug!("Cargo.toml:\n{:#?}", self.cargo_toml);
diff --git a/src/tag.rs b/src/tag.rs
new file mode 100644
index 0000000..5a6b4df
--- /dev/null
+++ b/src/tag.rs
@@ -0,0 +1,168 @@
+use crate::errors::BumperError;
+
+/// Name git tags after a pattern.
+///
+/// A `Tag` is created from a text template, which can embed the
+/// following for values to be filled in later:
+///
+/// - `%%` &ndash; a single percent character
+/// - `%n` &ndash; the name of the project
+/// - `%v` &ndash; the release version number
+///
+/// The template is parsed by `Tag::new`, which can fail, and the
+/// values for project name and release version are filled in by
+/// `Tag::apply`.
+pub struct Tag {
+ patterns: Vec<Pattern>,
+}
+
+impl Tag {
+ pub fn new(template: &str) -> Result<Self, BumperError> {
+ Ok(Self {
+ patterns: parse_template(template)?,
+ })
+ }
+
+ pub fn apply(&self, name: &str, version: &str) -> String {
+ let mut result = String::new();
+ for p in self.patterns.iter() {
+ match p {
+ Pattern::Fixed(s) => result.push_str(s),
+ Pattern::Name => result.push_str(name),
+ Pattern::Version => result.push_str(version),
+ }
+ }
+ result
+ }
+}
+
+#[derive(Debug)]
+enum Pattern {
+ Fixed(String),
+ Name,
+ Version,
+}
+
+impl PartialEq for &Pattern {
+ fn eq(&self, other: &Self) -> bool {
+ match self {
+ Pattern::Fixed(s) => match other {
+ Pattern::Fixed(t) => s == t,
+ _ => false,
+ },
+ Pattern::Name => matches!(other, Pattern::Name),
+ Pattern::Version => matches!(other, Pattern::Version),
+ }
+ }
+}
+
+fn parse_template(t: &str) -> Result<Vec<Pattern>, BumperError> {
+ if !t.is_ascii() {
+ return Err(BumperError::TagPatternNotAscii(t.to_string()));
+ }
+
+ let mut result = vec![];
+ let mut t = t.to_string();
+
+ while !t.is_empty() {
+ let p = if t.starts_with("%%") {
+ t.drain(..2);
+ Pattern::Fixed("%".to_string())
+ } else if t.starts_with("%v") {
+ t.drain(..2);
+ Pattern::Version
+ } else if t.starts_with("%n") {
+ t.drain(..2);
+ Pattern::Name
+ } else {
+ let c = t.get(..1).unwrap().to_string();
+ t.drain(..1);
+ Pattern::Fixed(c)
+ };
+
+ // Combine fixed string with previous fixed string.
+ if let Pattern::Fixed(b) = &p {
+ if let Some(last) = result.pop() {
+ // There was a previous pattern. Is it a fixed string?
+ if let Pattern::Fixed(a) = last {
+ let mut ab = a.clone();
+ ab.push_str(&b);
+ result.push(Pattern::Fixed(ab))
+ } else {
+ result.push(last);
+ result.push(p);
+ }
+ } else {
+ // No previous pattern.
+ result.push(p);
+ }
+ } else {
+ result.push(p);
+ };
+ }
+
+ Ok(result)
+}
+
+#[cfg(test)]
+mod test {
+ use super::{parse_template, Pattern, Tag};
+
+ fn vecs_eq(this: &[Pattern], that: &[Pattern]) -> bool {
+ println!();
+ println!("this: {:?}", this);
+ println!("that: {:?}", that);
+ this.iter().eq(that.iter())
+ }
+
+ #[test]
+ fn empty_template() {
+ assert!(vecs_eq(&parse_template("").unwrap(), &[]));
+ }
+
+ #[test]
+ fn fixed_string() {
+ assert!(vecs_eq(
+ &parse_template("foo").unwrap(),
+ &[Pattern::Fixed("foo".to_string())],
+ ));
+ }
+
+ #[test]
+ fn percent() {
+ assert!(vecs_eq(
+ &parse_template("%%").unwrap(),
+ &[Pattern::Fixed("%".to_string())]
+ ));
+ }
+
+ #[test]
+ fn version() {
+ assert!(vecs_eq(&parse_template("%v").unwrap(), &[Pattern::Version]));
+ }
+
+ #[test]
+ fn name() {
+ assert!(vecs_eq(&parse_template("%n").unwrap(), &[Pattern::Name]));
+ }
+
+ #[test]
+ fn many_parts() {
+ assert!(vecs_eq(
+ &parse_template("this-is-a-%n-%v-RELEASE").unwrap(),
+ &[
+ Pattern::Fixed("this-is-a-".to_string()),
+ Pattern::Name,
+ Pattern::Fixed("-".to_string()),
+ Pattern::Version,
+ Pattern::Fixed("-RELEASE".to_string())
+ ]
+ ));
+ }
+
+ #[test]
+ fn apply() {
+ let tag = Tag::new("%n-%v").unwrap();
+ assert_eq!(tag.apply("foo", "1.2.3"), "foo-1.2.3");
+ }
+}
diff --git a/subplot/bumper.py b/subplot/bumper.py
index 3edad5e..3c2f8fd 100644
--- a/subplot/bumper.py
+++ b/subplot/bumper.py
@@ -11,6 +11,10 @@ def install_bumper(ctx):
runcmd_prepend_to_path(ctx, dirname=os.path.join(srcdir, "target", "debug"))
+def chmod_exec(ctx, filename=None):
+ os.chmod(filename, 0o755)
+
+
def git_init_and_commit_everything(ctx, dirname=None):
runcmd_run = globals()["runcmd_run"]
runcmd_exit_code_is_zero = globals()["runcmd_exit_code_is_zero"]
diff --git a/subplot/bumper.yaml b/subplot/bumper.yaml
index 6109c59..83aae28 100644
--- a/subplot/bumper.yaml
+++ b/subplot/bumper.yaml
@@ -1,6 +1,9 @@
- given: "an installed Bumper"
function: install_bumper
+- given: "file {filename} is executable"
+ function: chmod_exec
+
- given: "all files in {dirname} are committed to git"
function: git_init_and_commit_everything