Skip to content

Commit

Permalink
Support version.h files of C(++) projects
Browse files Browse the repository at this point in the history
  • Loading branch information
vweevers committed Jul 29, 2022
1 parent 1714e98 commit 8bc6af3
Show file tree
Hide file tree
Showing 4 changed files with 179 additions and 23 deletions.
100 changes: 80 additions & 20 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,38 +1,26 @@
# dotnet-bump

**CLI to increment and tag assembly version(s) in a .NET project. Supports SDK-style and non-SDK projects.**
**CLI to increment and git-tag the version of .NET, C(++) and npm projects.** Geared towards Visual Studio projects.

[![npm status](http://img.shields.io/npm/v/dotnet-bump.svg)](https://www.npmjs.org/package/dotnet-bump)
[![node](https://img.shields.io/node/v/dotnet-bump.svg)](https://www.npmjs.org/package/dotnet-bump)
![Test](https://github.com/vweevers/dotnet-bump/workflows/Test/badge.svg)
![Release](https://github.com/vweevers/dotnet-bump/workflows/Release/badge.svg)
[![Common Changelog](https://common-changelog.org/badge.svg)](https://common-changelog.org)

## Table of Contents

<details><summary>Click to expand</summary>

- [Example](#example)
- [Usage](#usage)
- [Options](#options)
- [Install](#install)
- [License](#license)

</details>

## Example

```
> dotnet-bump minor --dry-run
- Would stage Foo\Foo.csproj
- Would stage Bar\Properties\AssemblyInfo.cs
- Would stage Bar\version.h
- Would commit and tag v1.1.0
```

```
> dotnet-bump minor
- Stage Foo\Foo.csproj
- Stage Bar\Properties\AssemblyInfo.cs
- Stage Bar\version.h
- Commit and tag v1.1.0
```

Expand All @@ -52,11 +40,13 @@ Bump to `target` version, one of:

Files can be glob patterns or paths to a:

- Visual Studio Solution (`*.sln`) (parsed to find projects)
- Project (`*.csproj` or `*.fsproj`) (parsed to find a `Version` element or `AssemblyInfo` file)
- A `.nuspec` file (containing a `version` element)
- C# or F# source code file
- JSON or JSON5 file;
- `*.sln` Visual Studio solution (parsed to find projects)
- `*.csproj` or `*.fsproj` project (parsed to find a `Version` element or `AssemblyInfo` file)
- `*.cs` or `*.fs` file (containing assembly attributes, see below)
- `*.nuspec` file (containing a `version` element)
- `*.vcxproj` project (used to discover `version.h` files in the same directory)
- `version.h` file (see below)
- `*.json` or `*.json5` file (containing a `version`);
- Directory containing any of the above.

Default is the current working directory. Files must reside in a git working tree (or multiple working trees).
Expand All @@ -73,6 +63,76 @@ Default is the current working directory. Files must reside in a git working tre
--help -h Print usage and exit
```

## Supported patterns

### .NET projects

Both legacy-style projects (that use assembly attributes) and SDK-style projects (that commonly use a `Version` element) are supported. For example, `dotnet-bump` would replace the `1.2.3` string here:

```xml
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<Version>1.2.3</Version>
</PropertyGroup>
</Project>
```

If the project is published as a NuGet package, the project version can usually serve as the source of truth. Other times a custom `*.nuspec` file may be necessary. For example:

```xml
<package xmlns="..">
<metadata>
<id>Example</id>
<version>1.2.3</version>
</metadata>
<files>
<file src="Example.dll" target="build\native\x64\bin" />
<file src="Example.targets" target="build\Example.targets" />
</files>
</package>
```

### Assembly attributes (C# / F#)

If an `AssemblyInfo.cs` file is found then `dotnet-bump` will replace the following attribute and leave other attributes as-is. If a version has four numeric components (`1.2.3.0`) then the last component (`.0`) will be stripped.

```cs
[assembly: AssemblyVersion("1.2.3")]
```

If `AssemblyFileVersion` and / or `AssemblyInformationalVersion` attributes are present they will be updated as well, but only if `AssemblyVersion` is present because it is used to determine the current version.

```cs
[assembly: AssemblyFileVersion("1.2.3")]
[assembly: AssemblyInformationalVersion("1.2.3")]
```

### `version.h` (C / C++)

One of the following combination of constants can be used, and must be written exactly as below with optional added whitespace (though `dotnet-bump` will strip such whitespace). Other lines in the `version.h` file will be left alone.

```c
#define VERSION_MAJOR 1
#define VERSION_MINOR 2
#define VERSION_PATCH 3
```
```c
#define VERSION_MAJOR 1
#define VERSION_MINOR 2
#define VERSION_PATCH 3
#define VERSION_BUILD 0
```

```c
#define VERSION_MAJOR 1
#define VERSION_MINOR 2
#define VERSION_BUILD 3
#define VERSION_REVISION 0
```
If the combination has four constants, the last constant will be ignored (on read) and set to `0` (on write).
## Install
Download [a portable binary](https://github.com/vweevers/dotnet-bump/releases) or install with [npm](https://npmjs.org):
Expand Down
20 changes: 18 additions & 2 deletions lib/finder.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ const detectLanguage = require('language-detect')
const AssemblyFile = require('./assembly-file')
const XmlFile = require('./xml-file')
const JsonFile = require('./json-file')
const VersionHeaderFile = require('./version-header-file')

const SOURCE_EXT_LANGUAGES = {
'.cs': 'C#',
Expand Down Expand Up @@ -97,9 +98,12 @@ module.exports = class Finder extends Emitter {
this.readSolution(file, done)
} else if (ext === '.json' || ext === '.json5') {
this.readJson(file, done)
} if (ext === '.nuspec') {
} else if (ext === '.nuspec') {
this.readNuspec(file)
done()
} else if (path.basename(file) === 'version.h') {
this.readVersionHeader(file)
done()
} else if (PROJECT_EXT_LANGUAGES[ext]) {
this.readProject(file, PROJECT_EXT_LANGUAGES[ext], done)
} else if (SOURCE_EXT_LANGUAGES[ext]) {
Expand Down Expand Up @@ -146,6 +150,9 @@ module.exports = class Finder extends Emitter {
const nuspecDebugFile = path.join(dir, projectName + '.Debug.nuspec')
if (fs.existsSync(nuspecDebugFile)) this.readNuspec(nuspecDebugFile)

const versionHeaderFile = path.join(dir, 'version.h')
if (fs.existsSync(versionHeaderFile)) this.readVersionHeader(versionHeaderFile)

if (language !== 'C#' && language !== 'F#') {
return done()
}
Expand Down Expand Up @@ -191,10 +198,16 @@ module.exports = class Finder extends Emitter {
this.found(new XmlFile(nuspecFile))
}

readVersionHeader (versionHeaderFile) {
if (this.skip('versionheader', versionHeaderFile)) return
const result = VersionHeaderFile.maybe(versionHeaderFile)
if (result) this.found(result)
}

scanDirectory (dir, done) {
if (this.skip('directory', dir)) return done()

glob('{*,config/*}.{sln,csproj,fsproj,vcxproj,json,json5,nuspec}', { cwd: dir, nodir: true, absolute: true }, (err, files) => {
glob('{*,config/*}.{sln,csproj,fsproj,vcxproj,json,json5,nuspec,h}', { cwd: dir, nodir: true, absolute: true }, (err, files) => {
if (err) return done(err)

const next = after(files.length, done)
Expand All @@ -211,6 +224,9 @@ module.exports = class Finder extends Emitter {
} else if (ext === '.nuspec') {
this.readNuspec(file)
next()
} else if (path.basename(file) === 'version.h') {
this.readVersionHeader(file)
next()
} else if (PROJECT_EXT_LANGUAGES[ext]) {
this.readProject(file, PROJECT_EXT_LANGUAGES[ext], next)
} else {
Expand Down
80 changes: 80 additions & 0 deletions lib/version-header-file.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
'use strict'

const fs = require('fs')
const AbstractFile = require('./abstract-file')

const kSource = Symbol('kSource')
const kConstants = Symbol('constants')

class VersionHeaderFile extends AbstractFile {
constructor (path, source, constants) {
super(path)

this[kSource] = source
this[kConstants] = constants
}

[AbstractFile.read] (callback) {
process.nextTick(callback)
}

[AbstractFile.write] (callback) {
fs.writeFile(this.path, this[kSource], callback)
}

[AbstractFile.getVersion] () {
return Array.from(this[kConstants]).slice(0, 3).map(x => x.value).join('.')
}

[AbstractFile.setVersion] (version) {
const parts = version.split(/[.+-]/).slice(0, 3).map(x => parseInt(x, 10))

if (!parts.every(Number.isInteger)) {
return
}

for (const constant of this[kConstants]) {
const value = parts.shift() || 0
const oldDirective = constant.directive
const newDirective = `#define ${constant.name} ${value}`

this[kSource] = this[kSource].replace(oldDirective, newDirective)

constant.directive = newDirective
constant.value = value
}
}
}

VersionHeaderFile.maybe = function (fp) {
const source = fs.readFileSync(fp, 'utf8')
const lines = source.split(/[\r\n]+/)
const constants = []

// Look for common combinations of constants
const combos = [
['VERSION_MAJOR', 'VERSION_MINOR', 'VERSION_BUILD', 'VERSION_REVISION'],
['VERSION_MAJOR', 'VERSION_MINOR', 'VERSION_PATCH', 'VERSION_BUILD'],
['VERSION_MAJOR', 'VERSION_MINOR', 'VERSION_PATCH']
]

for (const line of lines) {
const match = /^#define\s+(VERSION_(?:MAJOR|MINOR|PATCH|BUILD|REVISION))\s+(\d+)$/
.exec(line)

if (match !== null) {
const [directive, name, value] = match
constants.push({ name, directive, value: parseInt(value, 10) })
}
}

for (const combo of combos) {
// Order matters
if (combo.length === constants.length &&
combo.every((name, i) => constants[i].name === name)) {
return new VersionHeaderFile(fp, source, constants)
}
}
}

module.exports = VersionHeaderFile
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "dotnet-bump",
"version": "1.6.0",
"description": "CLI to increment and tag assembly version(s) in a .NET project",
"description": "CLI to increment and git-tag the version of .NET, C(++) and npm projects",
"bin": "cli.js",
"license": "MIT",
"author": "Vincent Weevers",
Expand Down

0 comments on commit 8bc6af3

Please sign in to comment.