This guide provides instructions for developers who want to build, modify, or contribute to the Concourse codebase.
- Prerequisites
- Building from Source
- Project Structure
- Development Workflow
- Coding Standards
- Common Tasks
- Debugging Tips
-
Java 8+: Concourse is built with Java 8 as the minimum version
java -version # Should show 1.8.x or higher -
Gradle: The build system (wrapper included)
./gradlew --version
-
Git: For version control
git --version
- Docker: For running containerized tests or the server
- Thrift Compiler: For modifying the Thrift interface definitions
- Ruby: For generating manual pages with ronn
- At least 4 GB of RAM (8 GB recommended for running tests)
- At least 2 GB of free disk space
git clone https://github.com/cinchapi/concourse.git
cd concourse./utils/install-git-hooks.shThis installs pre-push hooks that ensure code formatting before pushes.
# Full build with tests
./gradlew build
# Build without tests (faster)
./gradlew build -x test
# Build the installer
./gradlew installer
# Clean and rebuild
./gradlew clean buildAfter building, key artifacts are located at:
| Artifact | Location |
|---|---|
| Server Installer | concourse-server/build/distributions/concourse-server-*.bin |
| Java Driver JAR | concourse-driver-java/build/libs/concourse-driver-java-*.jar |
| Shell JAR | concourse-shell/build/libs/concourse-shell-*.jar |
concourse/
├── build.gradle # Root build configuration
├── settings.gradle # Gradle settings and module definitions
├── gradlew # Gradle wrapper script
│
├── concourse-server/ # Core database server
│ ├── src/main/java/ # Server source code
│ ├── src/test/java/ # Server unit tests
│ ├── conf/ # Configuration templates
│ └── scripts/ # Server management scripts
│
├── concourse-driver-java/ # Java client driver
│ ├── src/main/java/ # Driver source code
│ └── src/test/java/ # Driver tests
│
├── concourse-shell/ # Interactive shell (CaSH)
├── concourse-cli/ # CLI tools
├── concourse-plugin-core/ # Plugin framework
├── concourse-import/ # Import tools
├── concourse-export/ # Export tools
│
├── concourse-driver-python/ # Python driver
├── concourse-driver-php/ # PHP driver
├── concourse-driver-ruby/ # Ruby driver
│
├── concourse-integration-tests/ # Integration tests
├── concourse-ete-test-core/ # ETE test framework
├── concourse-ete-tests/ # End-to-end tests
├── concourse-unit-test-core/ # Unit test base classes
│
├── interface/ # Thrift interface definitions
│ ├── concourse.thrift # Main API definition
│ ├── data.thrift # Data type definitions
│ ├── exceptions.thrift # Exception definitions
│ └── shared.thrift # Shared types
│
├── docs/ # Documentation
│ ├── guide/ # User guide
│ ├── shell/ # Shell command docs
│ └── dev/ # Developer docs
│
├── utils/ # Development utilities
├── examples/ # Example projects
└── fixtures/ # Test fixtures
# Build the installer
./gradlew installer
# Install
sh concourse-server/build/distributions/concourse-server-*.bin
# Start the server
concourse start
# Connect with shell
concourse shell# Build and run
./gradlew dockerize
docker run -p 1717:1717 cinchapi/concourseImport the project into your IDE and use the launch configurations in:
concourse-server/launch/concourse-shell/launch/
Connect to a running server:
# Default connection (localhost:1717)
concourse shell
# With custom connection
concourse shell --host myserver --port 1717 --username admin --password admin# Run all tests
./gradlew test
# Run tests for a specific module
./gradlew :concourse-server:test
# Run a specific test class
./gradlew :concourse-server:test --tests "*.EngineTest"
# Run integration tests
./gradlew :concourse-integration-tests:test
# Run with test output
./gradlew test --infoConcourse uses Spotless for code formatting:
# Check formatting
./gradlew spotlessCheck
# Apply formatting
./gradlew spotlessApply- Classes: PascalCase, clear and descriptive (e.g.,
BufferedStore,TransactionManager) - Methods: camelCase, verbs for actions (e.g.,
add,remove,find) - Getters: Avoid
getprefix (e.g.,size()notgetSize()) - Variables: camelCase, concise but descriptive
- Constants: UPPER_SNAKE_CASE
// Good: Clear, well-documented method
/**
* Add {@code key} as {@code value} to {@code record} if the mapping does
* not already exist.
*
* @param key the field name
* @param value the value to add
* @param record the record id
* @return {@code true} if the value is added
*/
public boolean add(String key, Object value, long record) {
// Implementation
}
// Avoid: Vague names, missing documentation
public boolean a(String k, Object v, long r) {
// Implementation
}Favor immutable classes where possible:
// Good: Immutable value class
public final class Position implements Byteable {
private final Identifier record;
private final int index;
private Position(Identifier record, int index) {
this.record = record;
this.index = index;
}
public static Position of(Identifier record, int index) {
return new Position(record, index);
}
}- Line Length: Do not exceed 80 characters per line
- Purpose: Document "what" and "why", not "how"
- Contract: Describe inputs, outputs, and side effects
- Links: Use
{@link ClassName}for cross-references - Return: Always document return values with
@return
/**
* Return a {@link Set} containing all the records that have a value stored
* for {@code key} that satisfies the {@code operator} in relation to
* {@code value}.
* <p>
* This method supports ad-hoc queries with automatic index utilization.
* </p>
*
* @param key the field name to query
* @param operator the {@link Operator} to use for comparison
* @param value the value to compare against
* @return the matching record ids
*/
public Set<Long> find(String key, Operator operator, Object value) {
// Implementation
}Use inline comments sparingly, only for non-obvious implementation details:
// Good: Explains WHY, not WHAT
// We must acquire the lock before reading to ensure visibility
// of writes from other threads
lock.readLock().lock();
// Avoid: States the obvious
// Get the value from the map
value = map.get(key);Imports are organized by Spotless in this order:
java.*javax.*- Third-party libraries
- Project imports (
com.cinchapi.*)
- Every new feature should have corresponding tests
- Use meaningful test method names:
testAddReturnsFalseWhenValueExists - Register test variables for debugging:
Variables.register("key", key)
-
Define in Thrift (
interface/concourse.thrift):TObject myNewMethod( 1: string key, 2: i64 record, 3: shared.AccessToken creds, 4: shared.TransactionToken transaction, 5: string environment ) throws ( 1: exceptions.SecurityException ex, 2: exceptions.TransactionException ex2 ); -
Compile Thrift:
./utils/compile-thrift-java.sh
-
Implement in ConcourseServer:
@Override public TObject myNewMethod(String key, long record, AccessToken creds, TransactionToken transaction, String environment) throws TException { // Implementation }
-
Add to Java Driver (
Concourse.java):public abstract Object myNewMethod(String key, long record);
-
Write Tests:
- Unit test in
concourse-server - Integration test in
concourse-integration-tests
- Unit test in
-
Create plugin class:
public class MyPlugin extends Plugin { @PluginEndpoint public String myMethod(String arg) { return "Result: " + arg; } }
-
Build as bundle:
- Create a Gradle project with
concourse-plugin-coredependency - Package as ZIP with all dependencies
- Create a Gradle project with
-
Install:
concourse plugin install /path/to/my-plugin.zip
-
Understand the component hierarchy:
Engine→ coordinates all storageBuffer→ write-optimized storageDatabase→ read-optimized storageSegment→ immutable storage unit
-
Key classes to study:
concourse-server/src/main/java/com/cinchapi/concourse/server/storage/
-
Always add tests:
- Unit tests for isolated behavior
- Integration tests for end-to-end behavior
Concourse uses SLF4J with Logback:
import com.cinchapi.concourse.util.Logger;
// Use the Logger utility
Logger.debug("Processing record {}", record);
Logger.info("Server started on port {}", port);
Logger.error("Failed to process", exception);Configure logging in conf/logback.xml.
Use the Variables class to track test state:
@Test
public void testMyFeature() {
String key = Variables.register("key", TestData.getString());
long record = Variables.register("record", TestData.getLong());
// If test fails, registered variables are dumped to console
assertTrue(concourse.add(key, "value", record));
}Configure memory and garbage collection in conf/concourse.yaml:
# Set heap size (see concourse.yaml for sizing recommendations)
heap_size: 4g
# Force G1 garbage collector on JDK 8 (recommended for large heaps)
force_g1gc: trueEnable remote debugging by setting the remote_debugger_port in conf/concourse.yaml:
# Enable remote debugging on port 5005
remote_debugger_port: 5005Then attach your IDE debugger to the configured port.
Configure log verbosity in conf/concourse.yaml:
# Options: ERROR, WARN, INFO, DEBUG
log_level: DEBUG
# Also print logs to console
enable_console_logging: true- Architecture Overview - Understand system internals
- Testing Guide - Learn about test frameworks
- Contribution Guidelines - How to contribute