First of all, super exciting that you want to contribute to Marathon! 🎉👍🚀
The goal of the Marathon project is ambitious but also simple: "To provide a world class development environment for Swift scripting, making it the easiest to use and most powerful way of creating scripts, automation & developer tools".
You can help us reach that goal by contributing. Here are some ways you can contribute:
- Report any issues or bugs that you find
- Open issues for any new features you'd like Marathon to have
- Implement one of our starter tasks
- Implement other tasks selected for development
- Help answer questions asked by the community
- Share a script as an example
- Spread the word about Marathon
All contributors are expected to follow our Code of conduct. Please read it before making any contributions.
Marathon uses the Swift Package Manager as its build system.
No Xcode project is checked into the repository, and is instead git ignored and ad-hoc generated.
To generate an Xcode project to begin development, run $ swift package generate-xcodeproj
in the Marathon repository.
It's recommended that you re-generate the Xcode project whenever you pull down new changes, as files might've been added or removed.
Marathon uses SwiftLint to enforce Swift style and conventions. The easiest way to install SwiftLint is using Homebrew:
brew install swiftlint
Make sure there are no linting warnings or errors by running $ swiftlint
in the Marathon repository before submitting your changes. You can integrate SwiftLint into your generated Xcode project to get warnings and errors displayed inline in the editor, by adding the following as a new "Run Script Phase":
if which swiftlint >/dev/null; then
swiftlint
else
echo "warning: SwiftLint not installed, download from https://github.com/realm/SwiftLint"
fi
Unfortunately, you have to add it again if you re-generate the Xcode project for now, so it's recommended to use $ swiftlint
in the Marathon repository.
Tests should be added for all functionality, both when adding new behaviors to existing features, and implementing new ones.
Marathon uses XCTest
to run its tests, which can either be run through Xcode or by running $ swift test
in the repository.
Marathon is available both on macOS and Linux. For that reason there are two CI systems running simultaneously:
- BuddyBuild for macOS
- Travis for Linux
Platform specific tests are run on each respective CI and results from both are integrated into every PR. If one fails, you can easily determine which platform the tests failed on.
Marathon realies heavily on the file system to do its work. For that reason there are some convenience methods available in test suite which may help you achieve things easily and will keep an eye on what's happening in test suite. Here are some tips for writing new test cases:
- Be sure to prefix tests with
test
eg.testAllTestsRunOnLinux
. - If the test is macOS specific, be sure to add
MacOS
in the function's signature, eg.testEditingScriptWithXcodeOnMacOS
. - After writing a new test function, make sure you've added it to the
allTests
array, otherwise you'll get an error. - Since we run tests on CI, if you want to reference the main
~/.marathon
folder in a test, plese use property namedfolder
since a special folder is created for each test run. - Make sure each test runs Marathon, otherwise it hasn't been installed and isn't present. To do this, simply use the
run(with:)
method, which is a convenience wrapper. You just pass and array of arguments and you're good to go.
Here is a quick overview of the architecture of Marathon, to help you orient yourself in the project.
Marathon consists of 3 modules (+ its dependencies). These are:
- Marathon (the command line app that the user can run)
- MarathonCore (a framework that Marathon links against)
- MarathonTests (test suite for Marathon, run against MarathonCore)
For more information, take a look at Package.swift.
Whenever the user runs a command (such as marathon run
), the arguments passed into Marathon will be resolved into a
Command
. The command contains
information such as usage instructions, but its most important job is to resolve a Task
to be executed.
Marathon uses a modular architecture, where each part of its functionality is implemented as a Task
.
For example, running a script is done by RunTask
and editing a script is done by EditTask
. If a new feature should be implemented, this should be done as a new task.
Tasks that manipulate scripts in any way do this through ScriptManager
, which is responsible for managing all scripts that Marathon knows about.
It performs tasks such as creating cache folders, removing script data, and loading script files. For each script, it creates a Script
instance, that
can be used to work with the script in an object-oriented way.
The equivalent of ScriptManager
for packages is PackageManager
. It is responsible for managing the
packages that have been added to Marathon through marathon add
, as well as through a Marathonfile
. For each package, it stores metadata in a JSON file, and loads this data into a Package
instance before working with the package.
If you have a question about the inner workings of Marathon, or if you want to discuss a new feature - feel free to open an issue. If you're planning to make a non-trivial change to the project, it's usually a good idea to discuss it in an issue first, to make the workflow as smooth as possible.
Happy contributing! 🚀