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