0.3.0 • Published 6 years ago

tag-cli v0.3.0

Weekly downloads
2
License
MIT
Repository
github
Last release
6 years ago

STATUS: Tag is in initial development. Don't use it yet - you won't have a fun time when things change.

Tag is a task runner/build script generator that employs a very basic DSL in order to specify job dependencies with potentially complex build configurations (variants).

Tag is most similar to GNU Make, and consists of just a few primitives:

  • Rules - map inputs to targets (outputs)
  • Tasks - pseudo-commands to build a pre-determined set of targets (similar to .PHONY rules in GNU Make)
  • Commands - run via the shell on the host system (e.g. /bin/bash on unix, cmd.exe on windows, and so on)
  • Tags - boolean flags that enable or disable rules/commands/tasks (hence the project name)

... just a few philosophies ...

  • Build contexts - similar to Docker contexts, Tag forces out-of-source builds by using a root directory as a "context" for which files can be referenced. This allows Tag to run builds in a variety of ways.
  • Strict variables - unlike other build tools, Tag enforces the presence of variables when they're used. This is similar to set -u in shell script.
  • Unopinionated - Tag doesn't make any assumptions about what you're trying to run. It simply runs it in various ways. Use tag as a build system, tag runner, scripting language, etc.

Installation

Install Tag by running one of the following commands:

$ yarn global add tag-cli
$ npm i -g tag-cli

Usage

$ tag --help

 tag - yet another task runner

 $ tag -v [task ...] [@tag ...] [NAME=[value] ...]
 $ tag --help

 OPTIONS
   --help                    shows this help message
   --version, -V             shows the version string
   --verbose, -v             verbose output

   --tagfile, -F filename    the filename to read as the Tagfile
                             (defaults to './Tagfile')

   @tag_name                 enables a tag by name
                             (can be specified multiple times)

   NAME=[value]              sets a variable
                             (can be specified multiple times)

For example, to run Tag using the working directory's Tagfile with the all task (the default), simply run

$ tag

To enable the foo tag:

$ tag @foo

To set the variable FOO to Bar:

$ tag FOO=Bar

To run clean and then all (a full rebuild):

$ tag clean all

And finally, you may combine all of these together:

$ tag clean all @foo @bar SOME_VAR='Some value'

Why?

Because writing cross-platform scripts with various configurations is annoying. Further, I needed a jumping off point for a tool that can run various scripts on multiple platforms/environments, with the ability to create specific edge-case variants of commands in very specific situations.

Non-goals

There are some things Tag doesn't care too much about:

  • Configuration speed - reconfiguration doesn't happen often (at least, not as often as builds), so Tag takes the liberty to perform some potentially hefty filesystem operations in order to add a few more features while keeping this a bit more deterministic and 'correct'.
  • Build system primitives - Tag won't come bundled with any add_shared_c++_library()-type functions. It aims to be more like GNU Make and less like CMake.

Basics

# This is a comment. They can appear anywhere (even alongside Tag code),
# starting with a `#' and continuing until a newline (`\n').

# Set variable values by using the `SET' builtin.
# Values are consumed until the end of the line.
# Quotes have no special meaning and are considered literals.
#
# Although not strictly enforced by Tag, it is convention
# to name variables with ALL_CAPS to differentiate between variables
# and tags.
#
# Note that once an identifier has been used in a tag or a variable,
# it cannot be re-used as another type (Tag will error). This is another
# reason for the above convention.
SET FOO=bar

# You can reference variables anywhere in Tag code by
# wrapping the variable in curly braces (`{}')
SET FOO={FOO} bar2  # FOO now equates to "bar bar2"

# Use tag's built-in preset called "os", which
# provides the @win32, @macos, @linux, etc. tags.
USE os

# `TAGPATH' is used to resolve the argument to `USE'.
#
# Note that its path separator is always a colon (`:'),
# regardless of `PATHSEP'.
#
# Here is the default `TAGPATH' value.
#
# By default, all environment variables are included as variables.
SET TAGPATH=./%.tag:./node_modules/tag-preset-%/index.tag:./node_modules/%/index.tag

# `PATHSEP` is used to delimit multi-element strings in variables.
# If you `USE os', `PATHSEP' is set for you.
#
# (If you're curious about the `@' and `!', keep reading.)
@win32 SET PATHSEP=;
!win32 SET PATHSEP=:

# Tags are boolean (1 or 0) values that can be turned on or off.
# Turning on/off a tag that is already respectively enabled/disabled
# has no effect.
#
# As a convention (though not enforced), tags should be lower case
# in order to differentiate between tags and variables.
ON foo
OFF foo

# Tags can then be used to enable/disable statements. Yes, any statement.
@foo ECHO Foo is enabled!
!foo ECHO Foo is disabled!

# The `ECHO' builtin prints out information about whatever is running.
ECHO Hello from Tag!
@win32 ECHO You're running on Windows.
!win32 ECHO You're not running on Windows.

# Remember the variable substitution syntax above (`{VAR}')? It's more than
# just substitution.
#
# By prefixing substitutions, you can conditionally ignore/include the
# whole substitution.
ECHO You are {!win32 NOT }running on windows.

# Note that an empty substitution emits nothing.
ECHO {} # echoes nothing.

# If a variable is used in place of a tag prefix, the "boolean" is whether or
# not the variable exists.
@SOMETHING echo Something is {SOMETHING}  # not echoed.
SET SOMETHING=Hello
@SOMETHING echo Something is {SOMETHING}  # echoes

# This can, of course, be used in a substitution as well.
ECHO C flags that are set: {@CFLAGS {CFLAGS}}

# Substitutions have a little syntactic sugar to help in the cases
# where a variable doesn't _have_ to exist via the use of the question
# mark (`?') operator.
#
# It's worth mentioning that the question mark operator is available ONLY
# inside of substitutions - nowhere else.
#
# The following two lines are semantically identical.
ECHO Foobar is: {@FOOBAR {FOOBAR}}
ECHO Foobar is: {?FOOBAR}

# This means Tag is strict about unknown variables and will error if
# it encounters a variable that has not been seen before if used
# in a substitution.
#
# Assuming the variable `FIZZBUZZ' does not exist, the following line
# will cause Tag to fatally exit with an error.
ECHO {FIZZBUZZ}   # causes fatal error (FIZZBUZZ is not a tag or a variable)
ECHO {?FIZZBUZZ}  # echoes an empty line

# TODO `RUN'
# TODO `CALL'
# TODO `IN'
# TODO `DEF`
# TODO `TASK`
# TODO `MAIN`

As a bonus, here's some absurd advanced stuff you can do with the Tag language.

# Double substitution works like you'd expect.
SET FOO=BAR
SET BAR=baz
ECHO {{FOO}} # echos "baz"

# If, for whatever reason, you want to localize something, this is one
# way you could do it.
SET LANG=en_US
ON foo

ON {LANG}
@en_US SET ON_TXT=is enabled
@en_US SET OFF_TXT=is disabled
@de_DE SET ON_TXT=ist aktiviert
@de_DE SET OFF_TXT=ist nicht aktiviert

ECHO foo {{@foo ON_TXT}{!foo OFF_TXT}}

# Some alternative ways to write the above line:
ECHO foo {@foo {ON_TXT}}{!foo {OFF_TXT}}
ECHO foo {{@foo ON}{!foo OFF}_TXT}
@foo ECHO foo {ON_TXT}
!foo ECHO foo {OFF_TXT}

# Force Tag to exit with a fatal error if a tag is enabled
# (though this is a strange and not-so-recommended way of doing this)
@throw_if_on SET _={DOESNOTEXIST}  # assumes `DOESNOTEXIST' really doesn't exist ;)

Built-in Tasks

Tag currently provides one built-in task: all. Note that built-in tasks can be overridden.

The all task evaluates all rules and builds up a graph of all applicable files, building each one. While this is probably suitable for small projects, larger projects will want to override the entry point using the MAIN keyword.

Examples

C/C++ compilation

# specify @fast for fast code
# specify @small for small code
# specify @debug for debug symbols to be generated

USE os # @win32, @linux, @macos, etc.

@win32 !mingw SET OBJ=obj
!OBJ SET OBJ=o

OFF lang_c
OFF lang_c++
OFF linker
IN PATH gcc on lang_c linker gcc
IN PATH g++ on lang_c++ linker g++
IN PATH clang on lang_c linker clang
IN PATH clang++ on lang_c++ linker clang++
IN PATH ld on linker ld
@win32 IN PATH cl.exe on lang_c lang_c++ msvc
@win32 IN PATH link.exe on msvc linker

!lang_c ERROR no C toolchain was detected
!lang_c++ ERROR no C++ toolchain was detected
!linker ERROR no linker was detected

@lang_c++ DEF bin/$1.{OBJ} : src/(.+)\.c(c|pp)
	@msvc RUN cl.exe /c /OUT:{OUT} {IN} {@fast /O2 /Ox} {@small /O1 /Ox} {@debug /Od}
	# TODO @clang
	# TODO @gcc

@lang_c DEF bin/$1.{OBJ} : src/(.+)\.c
	@msvc RUN cl.exe /c /OUT:{OUT} {IN} {@fast /O2 /Ox} {@small /O1 /Ox} {@debug /Od}
	# TODO @clang
	# TODO @gcc

@linker DEF bin/$1 {@msvc @debug OUT_PDB=bin/$1.pdb} : bin/(.+)\.{OBJ}
	@msvc RUN link.exe {@debug /DEBUG /PDB:{OUT_PDB} /OUT:{OUT}} {IN}
	# TODO @clang
	# TODO @gcc