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

[Feature] New CLI #2275

Open
asbjornu opened this issue May 13, 2020 · 18 comments
Open

[Feature] New CLI #2275

asbjornu opened this issue May 13, 2020 · 18 comments

Comments

@asbjornu
Copy link
Member

asbjornu commented May 13, 2020

Detailed Description

This issue is our explorations of how the new CLI of GitVersion 6 7 is going to look like.

Design

The new CLI is going to have a command-based style, allowing for more laser-focused and composable execution. We want stdin and stdout to become the default way to both read and write version variables, logging instead to stderr so stdout can be kept as valid JSON throughout.

Since we want to reduce the amount of work being done for each command, GitVersion may have to be executed several times for certain builds, perhaps introducing some latency, but also making it much easier to pinpoint exactly during which step an error occurs so we can fix more easily and users of GitVersion can circumvent and monkey-patch the bugs much more efficiently.

Architecture

The new and more laser-focused command-based CLI allows us to refactor the GitVersion codebase into smaller, more compartementalized parts. This should lead to an almost dependency-free Core with a few optional addon projects that can live their own lives. The API between GitVersion.Core and the addons will be based on the CLI infrastructure we choose. Our current bet is on System.CommandLine.

Addons will be able to add their own commands to GitVersion and besides the Command implementations and arguments they can add to it, they will be able to read the version variables produced by GitVersion as JSON and then perform whatever task they wish. The output from an addon should be JSON written to stdout so different commands can be composed with piping.

Possible Implementation

Below is an example of some of the commands the new CLI may support, with some of their corresponding arguments.

# Write version to stdout
gitversion --version

# Write help to stdout
gitversion --help

# Normalize the repository to its required state:
gitversion normalize 

# Normalize the repository inside `./project/` to its required state:
gitversion normalize --repository ./project/

# Initialize GitVersion.yml
gitversion config init

# Write the effective GitVersion configuration (defaults + custom from GitVersion.yml) in yaml format to stdout
gitversion config show

# Calculate the version number and output to stdout. Only the JSON with the version variables will go to stdout, errors and warnings will be logged to stderr
gitversion calculate

# Calculate the version number and write it gitversion.json
gitversion calculate > gitversion.json

# Calculate the version number and output to stdout. Include logging information depending on the verbosity level in the logging to stderr.
gitversion calculate --verbosity verbose

# Calculate the version number and output to stdout. Include diagnostics info in the logging to stderr (requires `git` executable on PATH).
gitversion [diag] calculate

# Calculate the version number and log to the file `/var/logs/gitversion.log`
gitversion calculate --logfile /var/logs/gitversion.log

# Calculate the version number based on the configuration file `/etc/gitversion.yml`
gitversion calculate --configfile /etc/gitversion.yml

# Calculate the version and override the `tag-prefix` configuration.
gitversion calculate --override-config tag-prefix=foo

# Calculate the version with caching disabled.
gitversion calculate --no-cache

# Read version variables from stdin and write to globbed AssemblyInfo.cs files
cat gitversion.json | gitversion output --type assemblyinfo --path ./**/AssemblyInfo.cs

# Read version variables from stdin and write to globbed .csproj files
cat gitversion.json | gitversion output --type projectfiles --path ./**/*.csproj

# Read version variables from stdin and write to an auto-detected build server. Without an `--in` argument, stdin is the default input.
cat gitversion.json | gitversion output --type buildserver

# Read version variables from stdin and write to Jenkins.
cat gitversion.json | gitversion output --type buildserver --buildserver Jenkins

# Read version variables from stdin and write to globbed .wxi files.
cat gitversion.json | gitversion output --type wix --path ./**/*.wxi

# Read version variables from stdin and output them to environment variables
cat gitversion.json | gitversion output --type environment

# Read version variables from stdin and output only the `FullSemVer` property to stdout.
cat gitversion.json | gitversion output --property FullSemVer 

# Pipe the output of calculate to gitversion output
gitversion calculate | gitversion output --type assemblyinfo --path ./**/AssemblyInfo.cs

#NOTES [diag] can be used only with calculate command

Related Issue

#358, #428, #598, #572.

Motivation and Context

The CLI of GitVersion is a road that has been built as we have walked it, with no planning and no idea of scope or feature set before implementation was started. It has proved difficult to support POSIX file systems due to forward slash / being chosen as the argument separator originally, the argument system doesn't allow for easy documentation generation and perhaps most importantly: Argument parsing is not a core concern of GitVersion, so we're best served to outsource this entire thing to a third-party library.

@dazinator
Copy link
Member

For this one, what would overriding multiple config values look like?

gitversion calculate --override-config tag-prefix=foo

@asbjornu
Copy link
Member Author

I think the cleanest is to repeat the argument:

gitversion calculate --override-config tag-prefix=foo --override-config next-version=1.2.3 --override-config mode=Mainline

We might go for colon : instead of equals = to align more with the YAML syntax.

gitversion calculate --override-config tag-prefix:foo --override-config next-version:1.2.3 --override-config mode:Mainline

Not sure.

@arturcic
Copy link
Member

@arturcic
Copy link
Member

it's up to the user to decide which syntax they prefer. For our docs we'll need to choose one syntax to be consistent

@arturcic arturcic pinned this issue May 16, 2020
@asbjornu
Copy link
Member Author

asbjornu commented May 16, 2020

Sorry if I wan't entirely clear on where the separator occurs. I meant the separator between the key and the value in the value for the argument --override-config.

Afaict, the separator being discussed is between the argument and its value, i.e. the space between --override-config and tag-prefix:foo. So we could write --override-config:tag-prefix=foo or --override-config=tag-prefix:foo.

But I think tag-prefix:foo and tag-prefix=foo is just seen as one opaque value to System.CommandLine and thus we would have to split the key and value by a chosen delimiter.

@arturcic
Copy link
Member

Actually I remember they had a way to parse that too, as the intent is to be able to have something similar to the dotnet build --property:name=value, the way they pass properties to msbuild

@asbjornu
Copy link
Member Author

Neat. Perhaps we should just drop --override-config and go with --tag-prefix=foo anyway? We just need to be sure that none of the configuration properties collide with other arguments. I don't think we have anny collisions now, but if we do, we should take our chance to rename them for v6.

@asbjornu
Copy link
Member Author

I just discovered docopt which may be worth considering for the argument parsing.

@arturcic
Copy link
Member

arturcic commented Jan 7, 2021

The current POC is done in the new-cli folder

@ericnewton76
Copy link

btw, instead of docopt, i would suggest CommandLineParser library: https://github.com/commandlineparser/commandline

I help manage that library.

@arturcic
Copy link
Member

@ericnewton76 we decided to go with System.Commandline, and on the mentioned branch we're already working on that

@ericnewton76
Copy link

ericnewton76 commented Sep 12, 2022

We want stdin and stdout to become the default way to both read and write version variables, logging instead to stderr so stdout can be kept as valid JSON throughout.

BTW, I suggested in #3184 to add an option for flat output as opposed to JSON, via something like /output:flat instead of the current implied /output:json Reason is due to having to use cmd script parsing (yuck) and using for (delims) gets complicated with json ironically (ie, is the lead-in for each variable 2 spaces or 4 spaces or tabs???)

Also, git uses a --porcelain option that ensures the output is stable for parsing. I would suggest, In the beginning --porcelain could be the implied option, but I would immediately establish that as a required and encouraged option for stable output going forward. So for example, --porcelain --output=json would be the current implied standard and would output json in standard pretty print with 2 spaces ident. ...which btw still makes it difficult to parse via cmd for (delims)

UPDATE 2022-09-22: I think I read the porcelain stuff wrong in git docs, --porcelain seems to imply a user interface and might change so not good for scripting. I happen to be reading this doc: https://www.git-scm.com/docs/git/2.36.0#_low_level_commands_plumbing and it seems to imply porcelain is UX and might change.

@ericnewton76
Copy link

ericnewton76 commented Sep 16, 2022

BTW, I was just thinking, its been mentioned that certain operations might require multiple runs which makes sense for certain types of operations.

In doing so, I would setup the scenario where, by default, the output of the version resolution is stored (as the environment variable, GITVERSION_SEMVER?) and subsequent runs could skip the version resolution because this environment variable is present... from the previous run

Unless an option like --always-calculate-version is present. Reasoning is that typically GitVersion is run on a build server/build agent using an environment that is mostly persistent per-build. The tricky part is testing and the reverse might be needed (--no-persist) in that scenario (or $env.GITVERSION_NOPERSIST=true)

This would support additional scenarios where updating particular types of files can be separated. For instance, we had a VersionStamp.py script that could inject the already set env.MAJOR_MINOR_PATCH into various files that we displayed the version to the user, like MainTemplate.master, docs/MyProgram.html, etc. My thought was a more generic file updater (considering AssemblyVersionUpdater) could be implemented with a more generic use of tokens. So for example, inside MainTemplate.master we see <div>Version $(GITVERSION_SEMVER)</div> and similar files specified.

$ echo $GITVERSION_SEMVER

$ gitversion --output:none --set-environment
$ echo $GITVERSION_SEMVER
1.0.0
$ GITVERSION_SEMVER=
$ echo $GITVERSION_SEMVER

$ gitversion update assemblyinfo.cs ## (first run sets env.GITVERSION_SEMVER, etc)
Performing version resolution.
Detected standard AssemblyInfo.cs.  Using AssemblyInfoUpdater.
Updated [AssemblyVersionInfo] in AssemblyInfo.cs
$ echo $GITVERSION_SEMVER
1.0.0
$ gitversion update --use=AssembyInfoUpdater assemblyinfo.cs
Version resolution skipped.  GITVERSION_SEMVER already set.
Using AssemblyInfoUpdater.
Updated [AssemblyVersionInfo] in AssemblyInfo.cs
$ gitversion update MainTemplate.master docs/index.html
Version resolution skipped.  GITVERSION_SEMVER already set.
Using TokenizedFileUpdater.
Updated $(GITVERSION_SEMVER) in MainTemplate.master
Updated $(GITVERSION_SEMVER) in docs/index.html
$ gitversion update --use=TokenizedFileUpdater --quiet **/*
$ 

@asbjornu
Copy link
Member Author

If you read my examples, I've already suggested using gitversion.json or something similar to persist variables so they only have to be calculated once. Persisting them to one or more environment variables is probably also an option. GitVersion already does that on build servers.

@HHobeck HHobeck unpinned this issue Mar 5, 2023
@dazinator
Copy link
Member

Might be worth adding one more - the ability to pass the gitversion config as STDIN to gitversion calculate so that if present gitversion calculate doesn't have to look got the config file on disk - examples:

gitversion config show | gitversion calculate`
cat gitversion.config | gitversion calculate`

@arturcic
Copy link
Member

arturcic commented Apr 8, 2023

Might be worth adding one more - the ability to pass the gitversion config as STDIN to gitversion calculate so that if present gitversion calculate doesn't have to look got the config file on disk - examples:

gitversion config show | gitversion calculate`
cat gitversion.config | gitversion calculate`

I think we have this one instead
gitversion calculate --configfile gitversion.yml

@dazinator
Copy link
Member

dazinator commented Apr 8, 2023

@arturcic fair.
I don't mind either way but just wanted to elaborate that, as gitversion config command manages the config, it may be nice to keep the logic around where the config file is initialised and how it's resolved in that command.. then If we passed the yaml config as STDIN to calculate command (and any others that may need it) those commands are freed from having to source it. The user could also then pipe in the config from other "non file" sources should they wish to - such as:

  • a variable in their devops / build pipeline shared library
  • a team wide azure blob storage blob read inline

Not an important scenario for me personally, not sure if this flexibility is particularly sought after! The above is still possible with the way the command is currently proposed ofcourse but it would require the config be saved by the user to a file on disk first and then the path of that config passed to the calculate command, not a huge deal but not as nice imho.

@rcdailey
Copy link

rcdailey commented Apr 7, 2024

I wanted to throw in my thoughts here. If we use the Microsoft recommended Dockerfile for modern .NET, they load the entire source repo into the docker context. This is primarily to build the application, but the .git directory could be excluded if GitVersionTask read from a file, as @asbjornu suggests. In addition, the gittools/actions/gitversion/execute@v0 Github Action (in my scenario) could spit out a gitversion.json file to some predetermined directory that gets transparently pulled into the docker context when it does COPY . ., thus eliminating the need for .git/. It would also pull in my GitVersion.yml. I can't think of more that it would need.

This is mostly a performance concern for me. There's no other reason to have the .git/ directory loaded into the docker context.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging a pull request may close this issue.

5 participants