2025-09-20 16:11:27 -07:00
import Lake
open System Lake DSL
package SDL3
2025-09-24 14:54:29 -07:00
-- pin to a specific commit to avoid breakages
2026-01-19 20:40:24 -08:00
structure GitDep where
repo : String
rev : String
2026-01-22 02:38:05 -08:00
name : String
2026-01-23 20:59:23 -08:00
/-- The directory where this cloned repo should live --/
2026-01-22 02:38:05 -08:00
def GitDep . dir ( dep : GitDep ) ( pkg : Package ) : FilePath : =
pkg . dir / "vendor" / dep . name
2026-01-19 20:40:24 -08:00
2026-01-23 20:59:23 -08:00
/-- Invoke git to clone this dependency to a vendored directory --/
def GitDep . clone ( dep : GitDep ) ( pkg : Package ) : FetchM Unit : = do
let dstDir : = dep . dir pkg
if ( <- dstDir . pathExists ) then
logInfo s! "Directory {dstDir} already exists, skipping clone"
else
logInfo s! "Cloning {dep.repo} into {dstDir}"
proc {
cmd : = "git"
args : = # [ "clone" , "--revision" , dep . rev ,
"--single-branch" , "--depth" , "1" , "--recursive" , dep . repo , dstDir . toString
]
}
2026-01-19 20:40:24 -08:00
def sdlDep : GitDep : = {
repo : = "https://github.com/libsdl-org/SDL.git"
rev : = "f3a9f66292d49322652be01ee93412d0e9b74f0b"
2026-01-22 02:38:05 -08:00
name : = "SDL"
2026-01-19 20:40:24 -08:00
}
def sdlImageDep : GitDep : = {
repo : = "https://github.com/libsdl-org/SDL_image.git"
rev : = "d354e3d5146117f8b2f14096800965e56f9f7bfc"
2026-01-22 02:38:05 -08:00
name : = "SDL_image"
2026-01-19 20:40:24 -08:00
}
def sdlTtfDep : GitDep : = {
repo : = "https://github.com/libsdl-org/SDL_ttf.git"
rev : = "6b6bd588e8646360b08f624fb601cc2ec75c6ada"
2026-01-22 02:38:05 -08:00
name : = "SDL_ttf"
2026-01-19 20:40:24 -08:00
}
def sdlMixerDep : GitDep : = {
repo : = "https://github.com/libsdl-org/SDL_mixer.git"
rev : = "5cdf029bae982df1d6c210f915fc151a616d982f"
2026-01-22 02:38:05 -08:00
name : = "SDL_mixer"
2026-01-19 20:40:24 -08:00
}
2026-03-16 19:42:12 -07:00
def sdlDeps : List GitDep : = [ sdlDep , sdlImageDep , sdlTtfDep , sdlMixerDep ]
2025-09-21 19:36:32 -07:00
-- TODO: at some point, we should figure out a better way to set the C compiler
def compiler : = if Platform . isWindows then "gcc" else "cc"
2025-09-20 16:11:27 -07:00
2025-09-24 20:00:33 -07:00
def buildCMakeProject ( repoDir : FilePath ) ( args : Array String ): FetchM ( Unit ) : = do
logInfo s! "Building {repoDir} with CMake with args {args}"
let buildDir : = repoDir / "build"
2026-01-23 21:07:49 -08:00
if ( <- buildDir . pathExists ) then
logInfo "Build directory already exists, skipping configuration step"
else
2025-09-24 20:00:33 -07:00
let configureBuild ← IO . Process . output {
cmd : = "cmake" ,
args : = # [
"-S" , repoDir . toString ,
"-B" , buildDir . toString ,
"-DBUILD_SHARED_LIBS=ON" ,
"-DCMAKE_BUILD_TYPE=Release" ,
s! "-DCMAKE_C_COMPILER={compiler}" ,] ++ args
}
if configureBuild . exitCode != 0 then
logError s! "Error configuring build: {configureBuild.stderr}"
logInfo "Build configured successfully"
2026-01-23 21:07:49 -08:00
let buildProject ← IO . Process . output {
cmd : = "cmake" ,
args : = # [ "--build" , buildDir . toString , "--config" , "Release" ]
}
2025-09-24 20:00:33 -07:00
if buildProject . exitCode != 0 then
logError s! "Error building project: {buildProject.exitCode}"
logError s! "Project build stderr: {buildProject.stderr}"
logInfo s! "{repoDir} built successfully"
2025-09-24 19:20:17 -07:00
2026-03-16 19:53:33 -07:00
target libSDL3 pkg : Dynlib : =
return . pure { name : = "SDL3" , path : = sdlDep . dir pkg / "build" / nameToSharedLib "SDL3" }
2025-09-20 16:11:27 -07:00
2026-03-16 19:53:33 -07:00
target libSDL3Image pkg : Dynlib : =
return . pure { name : = "SDL3_image" , path : = sdlImageDep . dir pkg / "build" / nameToSharedLib "SDL3_image" }
2025-09-21 19:36:32 -07:00
2026-03-16 19:53:33 -07:00
target libSDL3Ttf pkg : Dynlib : =
return . pure { name : = "SDL3_ttf" , path : = sdlTtfDep . dir pkg / "build" / nameToSharedLib "SDL3_ttf" }
2025-09-24 19:20:17 -07:00
2026-03-16 19:53:33 -07:00
target libSDL3Mixer pkg : Dynlib : =
return . pure { name : = "SDL3_mixer" , path : = sdlMixerDep . dir pkg / "build" / nameToSharedLib "SDL3_mixer" }
2025-09-23 20:54:45 -07:00
2025-09-21 20:06:29 -07:00
target libleansdl pkg : FilePath : = do
2026-01-22 02:38:05 -08:00
-- clone the git repositories we need so we can build them later
2026-03-16 19:42:12 -07:00
for dep in sdlDeps do
2026-01-23 20:59:23 -08:00
dep . clone pkg
2025-09-24 19:20:17 -07:00
-- build all the libraries we need
2026-01-23 04:34:32 -08:00
let sdlRepoBuildDir : = ( sdlDep . dir pkg / "build" ) . toString
2026-01-23 20:59:23 -08:00
let sdlDirFlag : = s! "-DSDL3_DIR={sdlRepoBuildDir}"
2026-01-23 04:34:32 -08:00
2026-01-22 02:38:05 -08:00
buildCMakeProject ( sdlDep . dir pkg ) # []
2026-01-23 20:59:23 -08:00
buildCMakeProject ( sdlImageDep . dir pkg ) # [ sdlDirFlag ]
buildCMakeProject ( sdlTtfDep . dir pkg ) # [ sdlDirFlag , "-DSDLTTF_VENDORED=true" ]
buildCMakeProject ( sdlMixerDep . dir pkg ) # [ sdlDirFlag , "-DSDLMIXER_VENDORED=true" ]
2025-09-24 19:20:17 -07:00
logInfo "All libraries built successfully"
2026-01-23 04:34:32 -08:00
-- copy binaries
let binaryDstDir : = (( <- getRootPackage ) . binDir )
IO . FS . createDirAll binaryDstDir
2025-09-21 20:06:29 -07:00
2026-01-23 04:34:32 -08:00
-- manually copy the DLLs we need to .lake/build/bin/ in the root directory for the game to work
2026-03-16 19:42:12 -07:00
for dep in sdlDeps do
2026-01-23 04:34:32 -08:00
let sourceDir : = dep . dir pkg
logInfo s! "Copying binaries from {sourceDir} to {binaryDstDir}"
let buildDir : = sourceDir / "build"
for entry in ( ← buildDir . readDir ) do
if entry . path . extension != none then
copyFile entry . path ( binaryDstDir / entry . path . fileName . get! )
2026-03-16 20:25:50 -07:00
-- compile the Lean-SDL C glue code and bundle into a static library
let srcJob ← inputTextFile ( pkg . dir / "c" / "sdl.c" )
let oFile : = pkg . buildDir / "c" / "sdl.o"
let leanInclude : = <- getLeanIncludeDir
let includeDirs : List FilePath : = sdlDeps . map
( fun dep => dep . dir pkg / "include/" )
let flags : List String : = [ "-fPIC" ] ++
includeDirs . map ( fun dir => s! "-I{dir}" ) ++
[ "-D_REENTRANT" , s! "-I{leanInclude}" ]
let sdlO ← buildO oFile srcJob # [] flags . toArray compiler
2025-09-21 20:06:29 -07:00
let name : = nameToStaticLib "leansdl"
2025-09-20 16:11:27 -07:00
buildStaticLib ( pkg . staticLibDir / name ) # [ sdlO ]
2026-03-16 19:50:17 -07:00
def sdlLinkObjs : TargetArray FilePath : = # [ libleansdl ]
def sdlLinkLibs : TargetArray Dynlib : = # [ libSDL3 , libSDL3Image , libSDL3Ttf , libSDL3Mixer ]
2025-09-23 21:21:42 -07:00
2026-01-20 00:41:49 -08:00
-- make sure to copy these link args into whatever project is using this library in order for it to work
-- This is because without "-rpath=$ORIGIN", the Linux executable will not load dynlibs next to the executable (i.e., the SDL ones you've copied there).
2026-09-08 15:14:24 -07:00
-- macOS's ld64 spells both flags differently: `-undefined dynamic_lookup` and `@executable_path` in place of `$ORIGIN`.
def sdlLinkArgs : Array String : =
if Platform . isWindows then
2026-01-20 00:41:49 -08:00
# []
2026-09-08 15:14:24 -07:00
else if Platform . isOSX then
# [ "-Wl,-undefined,dynamic_lookup" , "-Wl,-rpath,@executable_path" ]
2026-01-20 00:41:49 -08:00
else
# [ "-Wl,--allow-shlib-undefined" , "-Wl,-rpath=$ORIGIN" ]
2025-09-20 16:11:27 -07:00
@[ default_target ]
lean_lib SDL where
2026-03-16 19:50:17 -07:00
moreLinkObjs : = sdlLinkObjs
moreLinkLibs : = sdlLinkLibs
moreLinkArgs : = sdlLinkArgs
2025-09-23 20:06:23 -07:00
lean_exe « test - app » where
root : = `TestApp
2026-03-16 19:50:17 -07:00
moreLinkObjs : = sdlLinkObjs
moreLinkLibs : = sdlLinkLibs
moreLinkArgs : = sdlLinkArgs
2025-12-15 19:40:35 -08:00
lean_exe « webcam - app » where
2026-03-16 19:50:17 -07:00
root : = `WebcamApp
moreLinkObjs : = sdlLinkObjs
moreLinkLibs : = sdlLinkLibs
moreLinkArgs : = sdlLinkArgs
2025-12-15 19:40:35 -08:00