1 2 /** 3 High-level rules for compiling D files. For a D-only application with 4 no dub dependencies, $(D scriptlike) should suffice. If the app depends 5 on dub packages, consult the reggae.rules.dub module instead. 6 */ 7 8 module reggae.rules.d; 9 10 import reggae.types; 11 import reggae.build; 12 import reggae.path: buildPath; 13 import reggae.sorting; 14 import reggae.rules.common; 15 import std.algorithm; 16 import std.array; 17 18 19 Target[] dlangObjects( 20 alias sourcesFunc = Sources!(), 21 CompilerFlags compilerFlags = CompilerFlags(), 22 ImportPaths importPaths = ImportPaths(), 23 StringImportPaths stringImportPaths = StringImportPaths(), 24 ProjectDir projectDir = ProjectDir(), 25 ) 26 () 27 { 28 return dlangObjectFiles( 29 sourcesToFileNames!sourcesFunc, 30 compilerFlags.value, 31 importPaths.value, 32 stringImportPaths.value, 33 [], // implicits 34 projectDir.value, 35 ); 36 } 37 38 39 Target[] dlangObjectsPerPackage( 40 alias sourcesFunc = Sources!(), 41 CompilerFlags compilerFlags = CompilerFlags(), 42 ImportPaths importPaths = ImportPaths(), 43 StringImportPaths stringImportPaths = StringImportPaths(), 44 ProjectDir projectDir = ProjectDir(), 45 ) 46 () 47 { 48 return dlangObjectFilesPerPackage( 49 sourcesToFileNames!sourcesFunc, 50 compilerFlags.value, 51 importPaths.value, 52 stringImportPaths.value, 53 [], // implicits 54 projectDir.value, 55 ); 56 } 57 58 Target[] dlangObjectsPerModule( 59 alias sourcesFunc = Sources!(), 60 CompilerFlags compilerFlags = CompilerFlags(), 61 ImportPaths importPaths = ImportPaths(), 62 StringImportPaths stringImportPaths = StringImportPaths(), 63 ProjectDir projectDir = ProjectDir(), 64 ) 65 () 66 { 67 return dlangObjectFilesPerModule( 68 sourcesToFileNames!sourcesFunc, 69 compilerFlags.value, 70 importPaths.value, 71 stringImportPaths.value, 72 [], // implicits 73 projectDir.value, 74 ); 75 } 76 77 78 79 /** 80 Generate object file(s) for D sources. 81 Depending on command-line options compiles all files together, per package, or per module. 82 */ 83 Target[] dlangObjectFiles(in string[] srcFiles, 84 in string[] flags = [], 85 in string[] importPaths = [], 86 in string[] stringImportPaths = [], 87 Target[] implicits = [], 88 in string projDir = "$project") 89 @safe 90 { 91 92 import reggae.config: options; 93 94 auto func = options.perModule 95 ? &dlangObjectFilesPerModule 96 : options.allAtOnce 97 ? &dlangObjectFilesTogether 98 : &dlangObjectFilesPerPackage; 99 100 return func(srcFiles, flags, importPaths, stringImportPaths, implicits, projDir); 101 } 102 103 /// Generate object files for D sources, compiling the whole package together. 104 Target[] dlangObjectFilesPerPackage(in string[] srcFiles, 105 in string[] flags = [], 106 in string[] importPaths = [], 107 in string[] stringImportPaths = [], 108 Target[] implicits = [], 109 in string projDir = "$project") 110 @trusted pure 111 { 112 113 if(srcFiles.empty) return []; 114 115 auto command(in string[] files) { 116 return compileCommand(files[0].packagePath ~ ".d", 117 flags, 118 importPaths, 119 stringImportPaths, 120 projDir); 121 } 122 123 // the object file for a D package containing pkgFiles 124 static string outputFileName(in string[] pkgFiles) { 125 import std.path: baseName; 126 const path = packagePath(pkgFiles[0]) ~ "_" ~ pkgFiles[0].baseName(".d"); 127 return objFileName(path); 128 } 129 130 return srcFiles 131 .byPackage 132 .map!(a => Target(outputFileName(a), 133 command(a), 134 a.map!(a => Target(a)).array, 135 implicits)) 136 .array; 137 } 138 139 /// Generate object files for D sources, compiling each module separately 140 Target[] dlangObjectFilesPerModule(in string[] srcFiles, 141 in string[] flags = [], 142 in string[] importPaths = [], 143 in string[] stringImportPaths = [], 144 Target[] implicits = [], 145 in string projDir = "$project") 146 @trusted pure 147 { 148 return srcFiles 149 .map!(a => objectFile(const SourceFile(a), 150 const Flags(flags), 151 const ImportPaths(importPaths), 152 const StringImportPaths(stringImportPaths), 153 implicits, 154 projDir)) 155 .array; 156 } 157 158 /// Generate object files for D sources, compiling all of them together 159 Target[] dlangObjectFilesTogether(in string[] srcFiles, 160 in string[] flags = [], 161 in string[] importPaths = [], 162 in string[] stringImportPaths = [], 163 Target[] implicits = [], 164 in string projDir = "$project") 165 @safe pure 166 { 167 import reggae.rules.common: objFileName; 168 return dlangTargetTogether( 169 &objFileName, 170 srcFiles, 171 flags, 172 importPaths, 173 stringImportPaths, 174 implicits, 175 projDir 176 ); 177 } 178 179 180 /** 181 Generate a static library for D sources, compiling all of them together. 182 With dmd, this results in a different static library than compiling the 183 source into object files then using `ar` to create the .a. 184 */ 185 Target[] dlangStaticLibraryTogether(in string[] srcFiles, 186 in string[] flags = [], 187 in string[] importPaths = [], 188 in string[] stringImportPaths = [], 189 Target[] implicits = [], 190 in string projDir = "$project") 191 @safe pure 192 { 193 import reggae.rules.common: libFileName; 194 return dlangTargetTogether( 195 &libFileName, 196 srcFiles, 197 "-lib" ~ flags, 198 importPaths, 199 stringImportPaths, 200 implicits, 201 projDir 202 ); 203 } 204 205 206 private Target[] dlangTargetTogether( 207 string function(in string) @safe pure toFileName, 208 in string[] srcFiles, 209 in string[] flags = [], 210 in string[] importPaths = [], 211 in string[] stringImportPaths = [], 212 Target[] implicits = [], 213 in string projDir = "$project", 214 ) 215 @safe pure 216 { 217 if(srcFiles.empty) return []; 218 219 // when building a .o or .a for multiple source files, this generates a name 220 // designed to avoid filename clashes (see arsd-official) 221 string outputNameForSrcFiles() @safe pure { 222 import reggae.sorting: packagePath; 223 import std.array: join; 224 import std.path: stripExtension, baseName; 225 import std.range: take; 226 227 // then number in `take` is arbitrary but larger than 1 to try to get 228 // unique file names without making the file name too long. 229 const name = srcFiles 230 .take(4) 231 .map!baseName 232 .map!stripExtension 233 .join("_") 234 235 ~ ".d"; 236 return packagePath(srcFiles[0]) ~ "_" ~ name; 237 } 238 239 const outputFileName = toFileName(outputNameForSrcFiles); 240 auto command = compileCommand(srcFiles[0], flags, importPaths, stringImportPaths, projDir); 241 242 return [Target(outputFileName, command, srcFiles.map!(a => Target(a)).array, implicits)]; 243 } 244 245 246 247 248 249 250 /** 251 Currently only works for D. This convenience rule builds a D scriptlike, automatically 252 calculating which files must be compiled in a similar way to rdmd. 253 All paths are relative to projectPath. 254 This template function is provided as a wrapper around the regular runtime version 255 below so it can be aliased without trying to call it at runtime. Basically, it's a 256 way to use the runtime scriptlike without having define a function in reggaefile.d, 257 i.e.: 258 $(D 259 alias myApp = scriptlike!(...); 260 mixin build!(myApp); 261 ) 262 vs. 263 $(D 264 Build myBuld() { return scriptlike(..); } 265 ) 266 */ 267 Target scriptlike(App app, 268 Flags flags = Flags(), 269 ImportPaths importPaths = ImportPaths(), 270 StringImportPaths stringImportPaths = StringImportPaths(), 271 alias linkWithFunction = () { return cast(Target[])[];}) 272 () @trusted 273 { 274 auto linkWith = linkWithFunction(); 275 import reggae.config: options; 276 return scriptlike(options.projectPath, app, flags, importPaths, stringImportPaths, linkWith); 277 } 278 279 280 //regular runtime version of scriptlike 281 //all paths relative to projectPath 282 //@trusted because of .array 283 Target scriptlike 284 () 285 (in string projectPath, 286 in App app, in Flags flags, 287 in ImportPaths importPaths, 288 in StringImportPaths stringImportPaths, 289 Target[] linkWith) 290 @trusted 291 { 292 293 import reggae.dependencies: dMainDepSrcs; 294 295 if(getLanguage(app.srcFileName.value) != Language.D) 296 throw new Exception("'scriptlike' rule only works with D files"); 297 298 auto mainObj = objectFile(SourceFile(app.srcFileName.value), flags, importPaths, stringImportPaths); 299 const output = runDCompiler(projectPath, buildPath(projectPath, app.srcFileName.value), flags.value, 300 importPaths.value, stringImportPaths.value); 301 302 const files = dMainDepSrcs(output).map!(a => a.removeProjectPath).array; 303 auto dependencies = [mainObj] ~ dlangObjectFiles(files, flags.value, 304 importPaths.value, stringImportPaths.value); 305 306 return link(ExeName(app.exeFileName.value), dependencies ~ linkWith); 307 } 308 309 310 //@trusted because of splitter 311 private auto runDCompiler(in string projectPath, 312 in string srcFileName, 313 in string[] flags, 314 in string[] importPaths, 315 in string[] stringImportPaths) @trusted { 316 import reggae.config: options; 317 import std.process: execute; 318 import std.exception: enforce; 319 import std.conv:text; 320 321 immutable compiler = options.dCompiler; 322 const compArgs = [compiler] ~ flags ~ 323 importPaths.map!(a => "-I" ~ buildPath(projectPath, a)).array ~ 324 stringImportPaths.map!(a => "-J" ~ buildPath(projectPath, a)).array ~ 325 ["-o-", "-v", "-c", srcFileName]; 326 const compRes = execute(compArgs); 327 enforce(compRes.status == 0, 328 text("scriptlike could not run ", compArgs.join(" "), ":\n", compRes.output)); 329 return compRes.output; 330 }