Skip to main content

Overview

BuckSample supports multiple build configurations (Debug and Release) through Buck’s configuration file system. This allows you to optimize builds for different purposes: fast iteration during development (Debug) or performance and size for production (Release).

Configuration Architecture

Buck uses a hierarchical configuration system where you can define custom values and reference them throughout your build files.

Base Configuration

The .buckconfig file defines default (Debug) configuration:
.buckconfig
[custom]
  config = debug
  optimization = -Onone
  config_swift_compiler_flags = -DDEBUG -enable-testing -g
These custom values are referenced in compiler flags:
.buckconfig
[swift]
  compiler_flags = -DBUCK -whole-module-optimization $(config custom.optimization) $(config custom.config_swift_compiler_flags) $(config code_coverage.swift_flags)
The $(config custom.optimization) syntax references the optimization value from the [custom] section, allowing you to change optimization levels without modifying compiler flag definitions.

Release Configuration

The BuildConfigurations/Release.buckconfig file overrides values for Release builds:
BuildConfigurations/Release.buckconfig
[custom]
  config = release
  optimization = -Osize
  config_swift_compiler_flags = -DRELEASE
When this file is applied using --config-file, it overrides the values from .buckconfig.

Debug Configuration

The Debug configuration is optimized for fast compilation and debugging support.

Optimization Level

optimization = -Onone
-Onone: No optimization
  • Fastest compilation times
  • Variables retain their values (easier debugging)
  • Code matches source line-by-line
  • Larger binary size
  • Slower runtime performance

Swift Compiler Flags

config_swift_compiler_flags = -DDEBUG -enable-testing -g
1

-DDEBUG

Defines the DEBUG preprocessor macro, allowing conditional compilation:
#if DEBUG
  print("Debug mode: \(detailedInfo)")
#endif
2

-enable-testing

Enables the @testable import feature:
@testable import MyApp

class MyTests: XCTestCase {
  func testInternalFunction() {
    // Can access internal members
    let result = InternalClass().internalMethod()
  }
}
3

-g

Generates debug symbols (DWARF format) for debugging:
  • Enables breakpoints
  • Shows variable values
  • Provides stack traces with symbols

C/C++ Flags

The Debug configuration also affects C/C++ compilation:
.buckconfig
[cxx]
  cflags = -g -fmodules -fobjc-arc -D BUCK -w $(config code_coverage.clang_flags)
  cxxflags = -fobjc-arc -std=c++14 -D DEBUG -g $(config code_coverage.clang_flags)
  • -g: Debug symbols
  • -D DEBUG: DEBUG preprocessor macro
  • -D BUCK: BUCK preprocessor macro

Release Configuration

The Release configuration is optimized for size and performance.

Optimization Level

optimization = -Osize
-Osize: Optimize for binary size
  • Smaller app binary
  • Reduced memory footprint
  • Balanced performance
  • Longer compilation times
Alternative optimization levels:
  • -O: Optimize for speed (fastest runtime)
  • -Osize: Optimize for size (smallest binary)
  • -Onone: No optimization (Debug default)

Swift Compiler Flags

config_swift_compiler_flags = -DRELEASE
Release builds:
  • Define RELEASE preprocessor macro
  • Remove -enable-testing (no @testable imports)
  • Remove -g (no debug symbols by default, though you can add them)
#if RELEASE
  // Production analytics
  Analytics.enableProductionTracking()
#endif

Whole Module Optimization

Both configurations use whole module optimization:
[swift]
  compiler_flags = -DBUCK -whole-module-optimization ...
Benefits:
  • Better optimization across file boundaries
  • Smaller final binary
  • Longer compilation but better runtime performance

Building with Configurations

Debug Build (Default)

Build with Debug configuration:
make build
Which runs:
Makefile
build:
	$(BUCK) build //App:ExampleApp
This uses the default .buckconfig settings (Debug).

Release Build

Build with Release configuration:
make build_release
Which runs:
Makefile
build_release:
	$(BUCK) build //App:ExampleApp --config-file ./BuildConfigurations/Release.buckconfig
The --config-file flag applies the Release configuration on top of .buckconfig.

Running with Configurations

make debug
The commands:
Makefile
debug:
	$(BUCK) install //App:ExampleApp --run --simulator-name 'iPhone 8'

debug_release:
	$(BUCK) install //App:ExampleApp --run --simulator-name 'iPhone 8' --config-file ./BuildConfigurations/Release.buckconfig

Creating Custom Configurations

You can create additional configurations for specific purposes.
1

Create Configuration File

Create a new .buckconfig file in BuildConfigurations/:
touch BuildConfigurations/Staging.buckconfig
2

Define Custom Values

Add configuration overrides:
BuildConfigurations/Staging.buckconfig
[custom]
  config = staging
  optimization = -O
  config_swift_compiler_flags = -DSTAGING -g
This creates a Staging configuration with:
  • Speed optimization (-O)
  • STAGING preprocessor macro
  • Debug symbols for debugging production issues
3

Use in Makefile

Add a Make target:
build_staging:
	$(BUCK) build //App:ExampleApp --config-file ./BuildConfigurations/Staging.buckconfig
4

Build with Custom Config

make build_staging

Conditional Compilation

Use preprocessor macros to conditionally compile code:

Swift

#if DEBUG
  func debugLog(_ message: String) {
    print("[DEBUG] \(message)")
  }
#else
  func debugLog(_ message: String) {
    // No-op in release
  }
#endif

#if RELEASE
  let apiEndpoint = "https://api.production.com"
#elseif STAGING
  let apiEndpoint = "https://api.staging.com"
#else
  let apiEndpoint = "https://api.dev.com"
#endif

Objective-C

#ifdef DEBUG
  NSLog(@"Debug mode enabled");
#endif

#ifdef RELEASE
  [Analytics enableProductionMode];
#endif

Configuration Best Practices

1

Use Debug for Development

Always use Debug configuration during development:
  • Faster compilation
  • Better debugging experience
  • Access to testing utilities
make build
make debug
2

Test Release Builds

Periodically test Release builds to catch optimization-related bugs:
make build_release
make debug_release
Some bugs only appear in optimized builds (race conditions, logic errors revealed by optimization).
3

Use Macros Sparingly

Avoid overusing preprocessor macros. Prefer runtime configuration when possible:
// Better: Runtime configuration
struct Config {
  static let isDebug: Bool = {
    #if DEBUG
      return true
    #else
      return false
    #endif
  }()
}

if Config.isDebug {
  print("Debug mode")
}
4

Keep Configurations Consistent

Ensure all configurations define the same set of custom values to avoid undefined behavior:
[custom]
  config = <name>
  optimization = <level>
  config_swift_compiler_flags = <flags>

Advanced Configuration

Per-Target Configuration

You can apply different configurations to different targets:
BUCK
apple_binary(
  name = "AppBinary",
  srcs = glob(["**/*.swift"]),
  configs = {
    "Debug": {"SWIFT_OPTIMIZATION_LEVEL": "-Onone"},
    "Release": {"SWIFT_OPTIMIZATION_LEVEL": "-O"},
  },
)

Environment-Specific Settings

Combine with environment variables:
BuildConfigurations/Production.buckconfig
[custom]
  config = production
  optimization = -O
  config_swift_compiler_flags = -DPRODUCTION
  api_endpoint = https://api.production.com
Reference in code:
#if PRODUCTION
  let endpoint = "https://api.production.com"
#endif

Combining Configuration Files

You can specify multiple configuration files:
buck build //App:ExampleApp \
  --config-file ./BuildConfigurations/Release.buckconfig \
  --config-file ./BuildConfigurations/Production.buckconfig
Later files override earlier ones.

Comparing Configurations

AspectDebugRelease
Optimization-Onone-Osize
Compilation SpeedFastSlower
Binary SizeLargerSmaller
Runtime PerformanceSlowerFaster
Debug SymbolsYes (-g)Optional
Testing SupportYes (-enable-testing)No
Preprocessor MacroDEBUGRELEASE
Use CaseDevelopment, debuggingProduction, App Store

Troubleshooting

Configuration Not Applied

If your configuration seems ignored:
  1. Verify the file path is correct:
    ls -la BuildConfigurations/Release.buckconfig
    
  2. Check for syntax errors in the config file
  3. Ensure you’re using --config-file flag:
    buck build //App:ExampleApp --config-file ./BuildConfigurations/Release.buckconfig
    

Macro Not Defined

If preprocessor macros aren’t working:
  1. Verify the macro is defined in config_swift_compiler_flags:
    config_swift_compiler_flags = -DRELEASE
    
  2. Check that flags are referenced correctly:
    compiler_flags = ... $(config custom.config_swift_compiler_flags)
    
  3. Clean and rebuild:
    make clean
    make build_release
    

Unexpected Optimization

If code behaves differently between configurations:
  1. This is often due to optimization revealing bugs (uninitialized variables, race conditions)
  2. Test with different optimization levels to isolate:
    optimization = -Onone  # No optimization
    optimization = -O      # Speed optimization
    optimization = -Osize  # Size optimization
    
  3. Use sanitizers to catch issues:
    [swift]
      compiler_flags = ... -sanitize=address,thread
    
  • Debugging - Debug builds with proper symbol support
  • Code Coverage - Coverage configuration
  • CI/CD - Testing both Debug and Release in CI

Build docs developers (and LLMs) love