import Lake open Lake DSL set_option autoImplicit false def splitArgStr (s : String) : Array String := Id.run <| do let mut quoted := false let mut escaped := false let mut cur := "" let mut acc := #[] for c in s.toList do match c with | ' ' => if quoted || escaped then cur := cur.push ' ' escaped := false else if cur.isEmpty.not then acc := acc.push cur cur := "" | '\\' => if escaped then cur := cur.push '\\' escaped := false else escaped := true | '\"' => if escaped then cur := cur.push '\"' escaped := false else quoted := quoted.not if cur.isEmpty.not then acc := acc.push cur cur := "" | 'n' => if escaped then cur := cur.push '\n' escaped := false else cur := cur.push 'n' | c => escaped := false cur := cur.push c if cur.isEmpty.not then acc := acc.push cur return acc inductive FilePathPartPatternPart where | literal (s : Substring) | wildcard deriving BEq, Inhabited, Repr abbrev FilePathPartPattern := Array FilePathPartPatternPart def FilePathPartPattern.getLiteral (pat : FilePathPartPattern) : Option String := flip pat.foldlM "" λ acc x ↦ match x with | .wildcard => none | .literal s => some (s.foldl String.push acc) def FilePathPartPattern.matches (pat : FilePathPartPattern) (s : String) : Bool := Id.run <| do let mut leads : Array (Subarray FilePathPartPatternPart × Substring) := #[(pat.toSubarray, s.toSubstring)] while h: leads.size > 0 do let (pat, s) := leads[0] leads := leads.pop if h: pat.size > 0 then match pat[0] with | .literal s' => match s.dropPrefix? s' with | some sSuffix => leads := leads.push (pat.drop 1, sSuffix) | none => return false | .wildcard => if pat.size < 2 then -- ends with wildcard return true let nextPart := pat[1]! match nextPart with | .wildcard => return false -- can't contain two consecutive wildcards | .literal nextPart => let restParts := pat.drop 2 let mut sSuffix := s repeat do match sSuffix.dropPrefix? nextPart with | none => if sSuffix.isEmpty then break | some sSuffix' => leads := leads.push (restParts, sSuffix') sSuffix := sSuffix.drop 1 else if s.isEmpty && pat.size == 0 then return true return false def FilePathPartPattern.validate (pat : FilePathPartPattern) : Except String FilePathPartPattern := do for i in [0:pat.size - 1] do if pat[i]! == .wildcard && pat[i + 1]! == .wildcard then throw s!"File pattern has invalid part pattern: wildcards can't be consecutive {repr pat}" return pat inductive FilePathPatternPart where | pattern (p : FilePathPartPattern) | doubleWildcard deriving BEq, Repr, Inhabited structure FilePathPattern where «package» : Option String parts : Array FilePathPatternPart deriving Repr def FilePathPattern.parse (pattern : String) : Except String FilePathPattern := do let isSep := λ c ↦ c == '/' || c == '\\' let mut «package» := none let mut parts := #[] let mut s := pattern.toSubstring let mut partParts : FilePathPartPattern := #[] if s.front == '@' then let packageName := s.drop 1 |>.takeWhile (not ∘ isSep) s := { s with startPos := packageName.stopPos } «package» := some packageName.toString if isSep s.front then s := s.drop 1 else if isSep s.front then partParts := partParts.push (.literal <| s.take 1) s := s.drop 1 while s.startPos < s.stopPos do if s.front = '*' then s := s.drop 1 partParts := partParts.push .wildcard continue if isSep s.front then if partParts.size == 2 && partParts[0]! == .wildcard && partParts[1]! == .wildcard then parts := parts.push .doubleWildcard else if partParts.size > 0 then parts ← FilePathPartPattern.validate partParts |> Except.mapError (· ++ s!"(in pattern {pattern})") |>.map (parts.push ∘ .pattern) partParts := #[] s := s.drop 1 continue if h: partParts.size > 0 then let lastPartPart := partParts[partParts.size - 1] match lastPartPart with | .literal lit => partParts := partParts.set (partParts.size - 1) (.literal { lit with stopPos := lit.str.next lit.stopPos }) | .wildcard => partParts := partParts.push (.literal <| s.take 1) else partParts := partParts.push (.literal <| s.take 1) s := s.drop 1 if partParts.size > 0 then parts ← FilePathPartPattern.validate partParts |> Except.mapError (· ++ s!"(in pattern {pattern})") |>.map (parts.push ∘ .pattern) if parts.size = 0 then throw s!"File pattern can't have zero parts: {pattern}" for h: i in [0:parts.size] do if h: i < parts.size - 1 then if parts[i] == .doubleWildcard && parts[i + 1] == .doubleWildcard then throw s!"File pattern can't contain two consecutive double-wildcards: {pattern}" else if parts[i] == .doubleWildcard then throw s!"File pattern can't end with double-wildcard: {pattern}" return { «package», parts } private partial def doubleWildcardLeads (curPath : System.FilePath) (nextPart : FilePathPartPattern) (restParts : Subarray FilePathPatternPart) : StateT (Array (System.FilePath × Subarray FilePathPatternPart)) IO Unit := do for entry in ← curPath.readDir do if nextPart.matches entry.fileName then modify (·.push (entry.path, restParts)) let entryPath := entry.path if (← entryPath.isDir).not then continue doubleWildcardLeads entryPath nextPart restParts def FilePathPattern.find {m} [Monad m] [MonadWorkspace m] [MonadLiftT IO m] (files : Array System.FilePath) (pattern : FilePathPattern) : m (Array System.FilePath) := do let mut files : Array System.FilePath := files let mut leads : Array (System.FilePath × Subarray FilePathPatternPart) := #[] match pattern.package with | none => let part0 := pattern.parts[0]! match part0 with | .doubleWildcard => leads := leads.push (".", pattern.parts.toSubarray) | .pattern part0Pat => match part0Pat.getLiteral with | none => leads := leads.push (".", pattern.parts.toSubarray) | some s => leads := leads.push (s, pattern.parts.toSubarray.drop 1) | some dep => match ← findPackage? (Lean.Name.mkStr1 dep) with | none => IO.eprintln s!"File pattern referenced package not found: {dep}" return #[] | some p => leads := leads.push (p.dir, pattern.parts.toSubarray) while h: leads.size > 0 do let (path, parts) := leads[leads.size - 1] leads := leads.pop if h: parts.size > 0 then let part := parts[0] let restParts := parts.drop 1 match part with | .pattern partPat => if (← path.isDir.toIO) then for subPath in ← path.readDir do if partPat.matches subPath.fileName then leads := leads.push (subPath.path, restParts) | .doubleWildcard => let nextPart := restParts[0]! match nextPart with | .doubleWildcard => continue | .pattern nextPartPat => leads ← Prod.snd <$> StateT.run (doubleWildcardLeads path nextPartPat (restParts.drop 1)) leads else if (← path.metadata).type == .file then files := files.push path pure files inductive RaylibSrc | System | Submodule | Custom | Unknown (name : String) def raylibSrc : RaylibSrc := match get_config? raylib with | none => .Submodule | some "system" => .System | some "submodule" => .Submodule | some "custom" => .Custom | some name => .Unknown name def optionFork := get_config? fork |>.isSome def submoduleDir := cond optionFork "raylibFork" "raylib" def optionExtraCmakeArgs := get_config? extraCmakeArgs |>.map splitArgStr |>.getD #[] def optionPrecompileRaymath := get_config? precompileRaymath |>.isSome def optionEmcmake := get_config? emcmake |>.getD "emcmake" def optionEmcc := get_config? emcc |>.getD "emcc" def optionExtraEmccArgs := get_config? extraEmccArgs |>.map splitArgStr |>.getD #[] def optionWebShell := get_config? webShell |>.getD s!"{submoduleDir}/src/minshell.html" def optionWasmToolchain := get_config? wasmToolchain def optionExtraEmccSources : Array String := get_config? extraEmccSources |>.map splitArgStr |>.getD #[] def optionDisableMacosLinkArgs := get_config? disableMacosLinkArgs |>.isSome def optionEnableWindowsMingw := get_config? enableWindowsMingw |>.isSome inductive RaylibPlatform where | desktop | web | android | raspberryPi | drm | sdl deriving BEq def RaylibPlatform.toString : RaylibPlatform → String | .desktop => "Desktop" | .web => "Web" | .android => "Android" | .raspberryPi => "Raspberry Pi" | .drm => "DRM" | .sdl => "SDL" def platform : RaylibPlatform := match (get_config? platform).map String.toLower with | none | some "desktop" => .desktop | some "web" => .web | some "android" => .android | some "raspberry pi" => .raspberryPi | some "drm" => .drm | some "sdl" => .sdl | some unknown => dbg_trace s!"Unknown `platform`: {unknown}" .desktop def podConfig : Lean.NameMap String := Id.run $ do let mut cfg := NameMap.empty if let some cc := get_config? cc then cfg := cfg.insert `cc cc if let some alloc := get_config? alloc then cfg := cfg.insert `alloc alloc cfg require pod from git "https://github.com/KislyjKisel/lean-pod" @ "6552d3c" with podConfig def packagesDir := defaultPackagesDir package raylib { packagesDir := packagesDir leanOptions := #[⟨`autoImplicit, false⟩] } @[default_target] lean_lib Raylib { srcDir := "src" } lean_lib Raymath { srcDir := "src" precompileModules := optionPrecompileRaymath } lean_lib Raygui { srcDir := "src" } def leanSystemLibDir := run_io (Option.map (·.systemLibDir)) <$> Lake.findLeanInstall? def leanSystemLibDirLinkArgs := match leanSystemLibDir with | none => #[] | some libDir => #[s!"-L{libDir}"] def examplesLinkArgs := (splitArgStr <| (get_config? lflags).getD "") ++ (if optionDisableMacosLinkArgs || System.Platform.isOSX.not then #[] else leanSystemLibDirLinkArgs ++ #[ "-framework", "CoreVideo", "-framework", "IOKit", "-framework", "Cocoa", "-framework", "GLUT", "-framework", "OpenGL" ]) ++ (if optionEnableWindowsMingw && System.Platform.isWindows then leanSystemLibDirLinkArgs ++ #[ "-LC:/Windows/System32", "-lgdi32", "-lopengl32", "-lmsvcrt", ] else #[]) ++ (match raylibSrc with | .System => #["-L/usr/local/lib64", "-lraylib"] | .Submodule => #[s!"-L{submoduleDir}/build/raylib", "-lraylib"] | .Custom => #[] | .Unknown _ => #[]) lean_exe "raylib-examples-minimal" { moreLinkArgs := examplesLinkArgs; srcDir := "examples"; root := `Minimal } lean_exe "raylib-examples-gui" { moreLinkArgs := examplesLinkArgs; srcDir := "examples"; root := `Gui } lean_exe "raylib-examples-audio" { moreLinkArgs := examplesLinkArgs; srcDir := "examples"; root := `Audio } lean_exe "raylib-examples-videoMode" { moreLinkArgs := examplesLinkArgs; srcDir := "examples"; root := `VideoMode } lean_exe "raylib-examples-extras" { moreLinkArgs := examplesLinkArgs; srcDir := "examples"; root := `Extras } def tryRunProcess {m} [Monad m] [MonadError m] [MonadLiftT IO m] (sa : IO.Process.SpawnArgs) : m IO.Process.Output := do let output ← IO.Process.output sa if output.exitCode ≠ 0 then error s!"'{sa.cmd} {sa.args}' returned {output.exitCode}: {output.stderr}" else pure output def tryRunProcessStreaming {m} [Monad m] [MonadError m] [MonadLiftT IO m] (printCmdOutput : Bool) (sa : IO.Process.SpawnArgs) : m Unit := do let process ← IO.Process.spawn { sa with toStdioConfig := if printCmdOutput then { stdout := .inherit, stderr := .inherit, stdin := .null } else { stdout := .null, stderr := .null, stdin := .null } } let exit ← process.wait if exit != 0 then error s!"'{sa.cmd} {sa.args}' returned {exit}" def updateRayguiSubmodule {m} [Monad m] [MonadError m] [MonadLiftT IO m] (printCmdOutput : Bool) : m Unit := do let gitCmd := (get_config? git).getD "git" if gitCmd != "" then tryRunProcessStreaming printCmdOutput { cmd := gitCmd args := #["submodule", "update", "--init", "--force", "--recursive", "raygui"] cwd := __dir__ } def buildRaylibSubmodule {m} [Monad m] [MonadError m] [MonadLiftT IO m] (printCmdOutput : Bool) : m Unit := do let gitCmd := (get_config? git).getD "git" if gitCmd != "" then tryRunProcessStreaming printCmdOutput { cmd := gitCmd args := #["submodule", "update", "--init", "--force", "--recursive", submoduleDir] cwd := __dir__ } IO.FS.createDirAll s!"{__dir__}/{submoduleDir}/build" let cmakeCmd := (get_config? cmake).getD "cmake" let mut cmakeBuildArgs : Array String := if platform == .web then #[cmakeCmd, "-DCMAKE_C_FLAGS=-pthread"] else #[] cmakeBuildArgs := cmakeBuildArgs ++ #[ "-DCUSTOMIZE_BUILD=ON", "-DWITH_PIC=ON", "-DCMAKE_BUILD_TYPE=Release", "-DBUILD_EXAMPLES=OFF" ] if optionEnableWindowsMingw && System.Platform.isWindows then cmakeBuildArgs := cmakeBuildArgs ++ #["-G Unix Makefiles", "-DSUPPORT_WINMM_HIGHRES_TIMER=OFF"] if let some extGlfw := get_config? externalGlfw then let values := #["OFF", "ON", "IF_POSSIBLE"] if values.contains extGlfw then cmakeBuildArgs := cmakeBuildArgs.push s!"-DUSE_EXTERNAL_GLFW={extGlfw}" else error s!"Invalid config option value: externalGlfw={extGlfw}; expected one of {values}" cmakeBuildArgs := cmakeBuildArgs.push s!"-DPLATFORM={platform.toString}" if let some oglVer := get_config? opengl then let mut oglVers := #["OFF", "4.3", "3.3", "2.1", "1.1", "ES 2.0", "ES 3.0"] if optionFork then oglVers := oglVers.push "4.6" if oglVers.contains oglVer then cmakeBuildArgs := cmakeBuildArgs.push s!"-DOPENGL_VERSION={oglVer}" else error s!"Invalid config option value: opengl={oglVer}; expected one of {oglVers}" let binCfgToOpt (opts : Array String) (cfgKey : String) (optPref : String) : Option String → IO (Array String) | some cfg => if cfg == "ON" || cfg == "OFF" then pure $ opts.push $ optPref ++ cfg else error s!"Invalid config option value: {cfgKey}={cfg}; expected ON or OFF" | none => pure opts cmakeBuildArgs ← binCfgToOpt cmakeBuildArgs "wayland" "-DGLFW_BUILD_WAYLAND=" (get_config? wayland) cmakeBuildArgs ← binCfgToOpt cmakeBuildArgs "x11" "-DGLFW_BUILD_X11=" (get_config? x11) cmakeBuildArgs ← binCfgToOpt cmakeBuildArgs "macosFatlib" "-DMACOS_FATLIB=" (get_config? macosFatlib) cmakeBuildArgs ← binCfgToOpt cmakeBuildArgs "audio" "-DUSE_AUDIO=" (get_config? audio) cmakeBuildArgs ← binCfgToOpt cmakeBuildArgs "busyWaitLoop" "-DSUPPORT_BUSY_WAIT_LOOP=" (get_config? busyWaitLoop) cmakeBuildArgs ← binCfgToOpt cmakeBuildArgs "cameraSystem" "-DSUPPORT_CAMERA_SYSTEM=" (get_config? cameraSystem) cmakeBuildArgs ← binCfgToOpt cmakeBuildArgs "defaultFont" "-DSUPPORT_DEFAULT_FONT=" (get_config? defaultFont) cmakeBuildArgs ← binCfgToOpt cmakeBuildArgs "screenCapture" "-DSUPPORT_SCREEN_CAPTURE=" (get_config? screenCapture) cmakeBuildArgs ← binCfgToOpt cmakeBuildArgs "gifRecording" "-DSUPPORT_GIF_RECORDING=" (get_config? gifRecording) cmakeBuildArgs ← binCfgToOpt cmakeBuildArgs "gestures" "-DSUPPORT_GESTURES_SYSTEM=" (get_config? gestures) cmakeBuildArgs ← binCfgToOpt cmakeBuildArgs "mouseGestures" "-DSUPPORT_MOUSE_GESTURES=" (get_config? mouseGestures) cmakeBuildArgs ← binCfgToOpt cmakeBuildArgs "vrSimulator" "-DSUPPORT_VR_SIMULATOR=" (get_config? vrSimulator) cmakeBuildArgs ← binCfgToOpt cmakeBuildArgs "distortionShader" "-DSUPPORT_DISTORTION_SHADER=" (get_config? distortionShader) cmakeBuildArgs ← binCfgToOpt cmakeBuildArgs "fontTexture" "-DSUPPORT_FONT_TEXTURE=" (get_config? fontTexture) cmakeBuildArgs ← binCfgToOpt cmakeBuildArgs "quadsDrawMode" "-DSUPPORT_QUAD_DRAW_MODE=" (get_config? quadsDrawMode) cmakeBuildArgs ← binCfgToOpt cmakeBuildArgs "imageGen" "-DSUPPORT_IMAGE_GENERATION=" (get_config? imageGen) cmakeBuildArgs ← binCfgToOpt cmakeBuildArgs "meshGen" "-DSUPPORT_MESH_GENERATION=" (get_config? meshGen) cmakeBuildArgs ← binCfgToOpt cmakeBuildArgs "imageManip" "-DSUPPORT_IMAGE_MANIPULATION=" (get_config? imageManip) cmakeBuildArgs ← binCfgToOpt cmakeBuildArgs "tracelog" "-DSUPPORT_TRACELOG=" (get_config? tracelog) cmakeBuildArgs ← binCfgToOpt cmakeBuildArgs "savePng" "-DSUPPORT_SAVE_PNG=" (get_config? savePng) cmakeBuildArgs ← binCfgToOpt cmakeBuildArgs "saveBmp" "-DSUPPORT_SAVE_BMP=" (get_config? saveBmp) cmakeBuildArgs ← binCfgToOpt cmakeBuildArgs "formatPng" "-DSUPPORT_FILEFORMAT_PNG=" (get_config? formatPng) cmakeBuildArgs ← binCfgToOpt cmakeBuildArgs "formatGif" "-DSUPPORT_FILEFORMAT_GIF=" (get_config? formatGif) cmakeBuildArgs ← binCfgToOpt cmakeBuildArgs "formatQoi" "-DSUPPORT_FILEFORMAT_QOI=" (get_config? formatQoi) cmakeBuildArgs ← binCfgToOpt cmakeBuildArgs "formatDds" "-DSUPPORT_FILEFORMAT_DDS=" (get_config? formatDds) cmakeBuildArgs ← binCfgToOpt cmakeBuildArgs "formatBmp" "-DSUPPORT_FILEFORMAT_BMP=" (get_config? formatBmp) cmakeBuildArgs ← binCfgToOpt cmakeBuildArgs "formatTga" "-DSUPPORT_FILEFORMAT_TGA=" (get_config? formatTga) cmakeBuildArgs ← binCfgToOpt cmakeBuildArgs "formatJpg" "-DSUPPORT_FILEFORMAT_JPG=" (get_config? formatJpg) cmakeBuildArgs ← binCfgToOpt cmakeBuildArgs "formatPsd" "-DSUPPORT_FILEFORMAT_PSD=" (get_config? formatPsd) cmakeBuildArgs ← binCfgToOpt cmakeBuildArgs "formatHdr" "-DSUPPORT_FILEFORMAT_HDR=" (get_config? formatHdr) cmakeBuildArgs ← binCfgToOpt cmakeBuildArgs "formatPic" "-DSUPPORT_FILEFORMAT_PIC=" (get_config? formatPic) cmakeBuildArgs ← binCfgToOpt cmakeBuildArgs "formatKtx" "-DSUPPORT_FILEFORMAT_KTX=" (get_config? formatKtx) cmakeBuildArgs ← binCfgToOpt cmakeBuildArgs "formatAstc" "-DSUPPORT_FILEFORMAT_ASTC=" (get_config? formatAstc) cmakeBuildArgs ← binCfgToOpt cmakeBuildArgs "formatPkm" "-DSUPPORT_FILEFORMAT_PKM=" (get_config? formatPkm) cmakeBuildArgs ← binCfgToOpt cmakeBuildArgs "formatPvr" "-DSUPPORT_FILEFORMAT_PVR=" (get_config? formatPvr) cmakeBuildArgs ← binCfgToOpt cmakeBuildArgs "formatSvg" "-DSUPPORT_FILEFORMAT_SVG=" (get_config? formatSvg) cmakeBuildArgs ← binCfgToOpt cmakeBuildArgs "formatObj" "-DSUPPORT_FILEFORMAT_OBJ=" (get_config? formatObj) cmakeBuildArgs ← binCfgToOpt cmakeBuildArgs "formatMtl" "-DSUPPORT_FILEFORMAT_MTL=" (get_config? formatMtl) cmakeBuildArgs ← binCfgToOpt cmakeBuildArgs "formatIqm" "-DSUPPORT_FILEFORMAT_IQM=" (get_config? formatIqm) cmakeBuildArgs ← binCfgToOpt cmakeBuildArgs "formatGltf" "-DSUPPORT_FILEFORMAT_GLTF=" (get_config? formatGltf) cmakeBuildArgs ← binCfgToOpt cmakeBuildArgs "formatVox" "-DSUPPORT_FILEFORMAT_VOX=" (get_config? formatVox) cmakeBuildArgs ← binCfgToOpt cmakeBuildArgs "formatM3d" "-DSUPPORT_FILEFORMAT_M3D=" (get_config? formatM3d) cmakeBuildArgs ← binCfgToOpt cmakeBuildArgs "formatWav" "-DSUPPORT_FILEFORMAT_WAV=" (get_config? formatWav) cmakeBuildArgs ← binCfgToOpt cmakeBuildArgs "formatOgg" "-DSUPPORT_FILEFORMAT_OGG=" (get_config? formatOgg) cmakeBuildArgs ← binCfgToOpt cmakeBuildArgs "formatXm" "-DSUPPORT_FILEFORMAT_XM=" (get_config? formatXm) cmakeBuildArgs ← binCfgToOpt cmakeBuildArgs "formatMod" "-DSUPPORT_FILEFORMAT_MOD=" (get_config? formatMod) cmakeBuildArgs ← binCfgToOpt cmakeBuildArgs "formatFlac" "-DSUPPORT_FILEFORMAT_FLAC=" (get_config? formatFlac) cmakeBuildArgs ← binCfgToOpt cmakeBuildArgs "formatFnt" "-DSUPPORT_FILEFORMAT_FNT=" (get_config? formatFnt) cmakeBuildArgs ← binCfgToOpt cmakeBuildArgs "formatTtf" "-DSUPPORT_FILEFORMAT_TTF=" (get_config? formatTtf) cmakeBuildArgs ← binCfgToOpt cmakeBuildArgs "formatBdf" "-DSUPPORT_FILEFORMAT_BDF=" (get_config? formatBdf) if platform == .web then cmakeBuildArgs := cmakeBuildArgs.push "-S" cmakeBuildArgs := cmakeBuildArgs.push ".." ++ optionExtraCmakeArgs let cmakeCmd := (get_config? cmake).getD "cmake" if cmakeCmd != "" then tryRunProcessStreaming printCmdOutput { cmd := if platform == .web then optionEmcmake else cmakeCmd args := cmakeBuildArgs cwd := __dir__ / submoduleDir / "build" } tryRunProcessStreaming printCmdOutput { cmd := cmakeCmd args := #["--build", ".", "--config", "Release"] cwd := __dir__ / submoduleDir / "build" } def bindingsCFlags (pkg : NPackage _package.name) : FetchM (Array String × Array String) := do let mut weakArgs := #["-I", (← getLeanIncludeDir).toString] let mut traceArgs := #["-fPIC"].append $ splitArgStr $ (get_config? cflags).getD "" match ← findPackage? `pod with | none => error "Missing dependency 'Pod'" | some pod => weakArgs := weakArgs ++ #[ "-I", (pod.dir / "ffi" / "include").toString ] let printCmdOutput := (get_config? cmdout).isSome match raylibSrc with | .System => let pkgConfigOutput ← tryRunProcess { cmd := "pkg-config" args := #["--cflags", "raylib", "glfw3"] } weakArgs := weakArgs.append $ splitArgStr $ pkgConfigOutput.stdout.replace "\n" " " | .Submodule => do buildRaylibSubmodule printCmdOutput weakArgs := weakArgs.append #[ "-I", (pkg.dir / submoduleDir / "build" / "raylib" / "include").toString, "-I", (pkg.dir / submoduleDir / "src" / "external" / "glfw" / "include").toString ] | .Custom => pure () | .Unknown name => error s!"Unknown 'raylib' source: {name}" if (get_config? raygui).isSome then updateRayguiSubmodule printCmdOutput weakArgs := weakArgs.append #[ "-I", (pkg.dir / "raygui" / "src").toString ] if optionFork then traceArgs := traceArgs.push "-DLEAN_RAYLIB_FORK" match get_config? alloc with | .none | .some "lean" => pure () | .some "native" => traceArgs := traceArgs.push "-DLEAN_RAYLIB_ALLOC_NATIVE" | .some _ => error "Unknown `alloc` option value" if (get_config? cc).isNone && (← getLeanCc?).isNone && (← IO.getEnv "CC").isNone then weakArgs := weakArgs ++ #["-I", ((← getLeanIncludeDir) / "clang").toString] pure (weakArgs, traceArgs) extern_lib «raylib-lean» pkg := do let name := nameToStaticLib "raylib-lean" let (weakArgs, traceArgs) ← bindingsCFlags pkg let mut bindingsSources := #[ "structures", "functions", "callbacks", "raymath", "glfw" ] if (get_config? raygui).isSome then bindingsSources := bindingsSources.push "raygui" let bindingsHeaders := #[ "include/raylib_lean", "include/raymath_lean" ] let nativeSrcDir := pkg.dir / "ffi" let objectFileDir := pkg.irDir / "__ffi__" let headerFile (h : String) : System.FilePath := nativeSrcDir / (h ++ ".h") let extraTrace : BuildTrace ← mixTraceArray <$> (bindingsHeaders.mapM $ computeTrace ∘ headerFile) let extraTrace : BuildTrace := mixTrace extraTrace (← getLeanTrace) buildStaticLib (pkg.staticLibDir / name) (← bindingsSources.mapM λ stem ↦ do buildO (objectFileDir / (stem ++ ".o")) (← inputTextFile $ nativeSrcDir / (stem ++ ".c")) weakArgs traceArgs ((get_config? cc).getD (← getLeanCc).toString) (pure extraTrace) ) script buildSubmodule do buildRaylibSubmodule true return 0 script cleanCmakeCache do let path := s!"{__dir__}/{submoduleDir}/build" if ← System.FilePath.pathExists path then IO.println s!"Removing: {path}" IO.FS.removeDirAll path return 0 script options do IO.println s!"platform={platform.toString}" IO.println s!"raylib={get_config? raylib}" IO.println s!"raygui? {(get_config? raygui).isSome}" IO.println s!"cc={get_config? cc}" IO.println s!"cflags={get_config? cflags}" IO.println s!"lflags={get_config? lflags}" IO.println s!"fork? {optionFork}" IO.println s!"alloc={get_config? alloc}" IO.println s!"git={get_config? git}" IO.println s!"cmake={get_config? cmake}" IO.println s!"extraCmakeArgs: {optionExtraCmakeArgs}" IO.println s!"precompileRaymath? {optionPrecompileRaymath}" IO.println s!"emcmake={optionEmcmake}" IO.println s!"emcc={optionEmcc}" IO.println s!"extraEmccArgs: {optionExtraEmccArgs}" IO.println s!"webShell={optionWebShell}" IO.println s!"wasmToolchain={optionWasmToolchain}" IO.println s!"extraEmccSources: {optionExtraEmccSources}" return 0 /-- `LeanExe.recBuildExe` changed to exclude linking. Used when building for wasm as linking is not needed here and will also fail. -/ def buildExeWithoutLinking (self : LeanExe) : FetchM (Job Unit) := withRegisterJob s!"{self.name}" do /- Remark: We must build the root before we fetch the transitive imports so that errors in the import block of transitive imports will not kill this job before the root is built. -/ let mut objJobs := #[] let mut libJobs := #[] let shouldExport := self.supportInterpreter for facet in self.root.nativeFacets shouldExport do objJobs := objJobs.push <| ← facet.fetch self.root let imports ← (← self.root.transImports.fetch).await for mod in imports do for facet in mod.nativeFacets shouldExport do objJobs := objJobs.push <| ← facet.fetch mod let libs := imports.foldl (·.insert ·.lib) OrdHashSet.empty |>.toArray for lib in libs do for link in lib.moreLinkObjs do objJobs := objJobs.push <| ← link.fetchIn lib.pkg for link in lib.moreLinkLibs do libJobs := libJobs.push <| ← link.fetchIn lib.pkg let deps := (← (← self.pkg.transDeps.fetch).await).push self.pkg for dep in deps do for lib in dep.externLibs do objJobs := objJobs.push <| ← lib.static.fetch (Job.collectArray objJobs).bindM λ _ ↦ (Job.collectArray libJobs).mapM λ _ ↦ pure () script buildWeb (args) do let targetName ← if h: args.length = 1 then pure args[0] else error "invalid command line arguments, expected executable target name" let ws ← getWorkspace let some exe := ws.findLeanExe? targetName.toName | error s!"unknown executable `{targetName}`" IO.println "Building exe..." runBuild <| buildExeWithoutLinking exe if platform != .web then IO.eprintln s!"Config option platform has unexpected value: {platform.toString}" return 1 if optionWasmToolchain.isNone then -- TODO: Try downloading using Lake.download IO.eprintln s!"Config option wasmToolchain is missing" return 1 let podDir ← match ← findPackage? `pod with | none => error "Missing dependency 'Pod'" | some pod => pure pod.dir let wasmToolchain := optionWasmToolchain.get! IO.FS.createDirAll ".lake/build/web" -- TODO: configure? let emccArgs := #[ "-o", ".lake/build/web/main.html", "-I", s!"{wasmToolchain}/include", "-L", s!"{wasmToolchain}/lib/lean", "-I", s!"{podDir}/ffi/include", "-lInit", "-lLean", "-lleancpp", "-lleanrt", "-lStd", s!"-L{__dir__}/{submoduleDir}/build/raylib", "-lraylib", "-sMAIN_MODULE=1", "-pthread", "-sUSE_WEBGL2=1", "-sUSE_GLFW=3", "-sASYNCIFY", -- TODO: configure? "-sALLOW_MEMORY_GROWTH=1", "-O1", "-flto", "-sASSERTIONS=1", -- TODO: remove/configure "--shell-file", optionWebShell -- TODO: see also: -- -sMODULARIZE, -sEXIT_RUNTIME=0, -fwasm-exceptions, -sEXPORT_ALL=0, -- -sLINKABLE=0, -sMAIN_MODULE=2, -sFORCE_FILESYSTEM ] let cSourcesPatterns := optionExtraEmccSources ++ #[ ".lake/packages/*/.lake/build/ir/**/*.c", ".lake/build/ir/**/*.c", "ffi/**/*.c", s!"{podDir}/ffi/**/*.c" ] match cSourcesPatterns.mapM (λ x ↦ Prod.mk x <$> FilePathPattern.parse x) with | .error e => IO.eprintln e return 1 | .ok cSourcePatterns => let cSources ← flip cSourcePatterns.foldlM #[] λ acc (raw, x) ↦ IO.println s!"Searching files with pattern {raw}" *> FilePathPattern.find acc x let emccArgs := emccArgs ++ cSources.map System.FilePath.toString ++ optionExtraEmccArgs IO.println s!"Running {optionEmcc} with args {repr emccArgs}" tryRunProcessStreaming true { cmd := optionEmcc args := emccArgs } IO.println "Success." IO.println "Example usage:" IO.println "xdg-open http://localhost:8080/main.html && python3 -m http.server 8080 -d .lake/build/web" return 0