3

I've got a project that builds two sets of targets. One is firmware for various version of a handheld device that ultimately creates a .dfu file for each device. This uses the arm compilation chain. The other set are emulators for those same devices that compile the code for the build platform (well, usually anyway). The Makefiles I inherited don't work well in any environment where cc != gcc because of issues with passing the compiler into recursive makes.

I'm thinking about rewriting the build files in shake, but wondered how well it can deal with wanting to do a "make release" that then compiles the same set of source files multiple times with different compilers or flags to generate different kinds of binaries. Not a problem I've faced before, and based on my google searches not one a lot of people have publicly talked about.

2 Answers2

2

I'm unaware of people using Shake for cross-compilation (although I imagine it happens), but lots of people build different configurations with one Shake script, e.g. debug/profile/release, and that requires roughly the same operations. The usual approach is to place the output files in different directories, and define different rules (or the same rule but parameterised) based on the output directory. As one example:

"release/arm//*.obj" *> \out -> command "arm-cc" ["source-file.c","-o",out]
"release/x86//*.obj" *> \out -> command "gcc" ["source-file.c","-o",out]

Alternatively, you can have the same rule for each, and just switch compiler based on the directory:

"release//*.obj" *> \out ->
    let cc = if takeDirectory1 $ dropDirectory1 out == "arm" then "arm-cc else "gcc"
    command cc ["source-file.c","-o",out]

Which works best usually depends if arm and x86 are mostly identical but switch the compiler, or just very different things.

At the end, you just call want ["release/arm/install.tar.gz","release/x86/install.tar.gz"] and Shake will recursively create both releases at once.

Neil Mitchell
  • 9,090
  • 1
  • 27
  • 85
2

Maybe have a look at shake-language-c. It is currently being used with mobile and web cross-compilation toolchains but it should be straight-forward to interface to other toolchains.

Here's an example for compiling a static library for iOS device and simulator:

import Control.Applicative
import Control.Arrow
import Control.Monad
import Development.Shake
import Development.Shake.FilePath
import Development.Shake.Language.C
import qualified Development.Shake.Language.C.Target.OSX as OSX

main :: IO ()
main = shakeArgs shakeOptions { shakeFiles = "build/" } $ do
  let -- Target the device and simulator
      targets = map (uncurry OSX.target)
                    [ (OSX.iPhoneOS, (Arm Armv7s))
                    , (OSX.iPhoneSimulator, (X86 I386)) ]

  -- Declare static library for each target
  libs <- forM targets $ \target -> do
    let toolChain = OSX.toolChain
                    <$> OSX.getSDKRoot
                    <*> (maximum <$> OSX.getPlatformVersions (targetPlatform target))
                    <*> pure target
    staticLibrary toolChain
          -- Unique name for each library target
          ("build" </> toBuildPrefix target </> "libexample.a")
          (return $ 
               append compilerFlags [(Just Cpp, ["-std=c++11"])]
           >>> append compilerFlags [(Nothing, ["-O3"])]
           >>> append userIncludes ["include"] )
          (getDirectoryFiles "" ["src//*.cpp"])

  -- Build them all
  want libs