1 #!/usr/bin/env rdmd
2 /*******************************************************************************
3 
4     Standalone build script for DUB
5 
6     This script can be called from anywhere, as it deduces absolute paths
7     based on the script's placement in the repository.
8 
9     Invoking it while making use of all the options would like like this:
10     DMD=ldmd2 DFLAGS="-O -inline" ./build.d my-dub-version
11     Using an environment variable for the version is also supported:
12     DMD=dmd DFLAGS="-w -g" GITVER="1.2.3" ./build.d
13 
14     Copyright: D Language Foundation
15     Authors: Mathias 'Geod24' Lang
16     License: MIT
17 
18 *******************************************************************************/
19 module build;
20 
21 private:
22 
23 import std.algorithm;
24 static import std.file;
25 import std.format;
26 import std.path;
27 import std.process;
28 import std.stdio;
29 import std.string;
30 
31 /// Root of the `git` repository
32 immutable RootPath = __FILE_FULL_PATH__.dirName;
33 /// Path to the version file
34 immutable VersionFilePath = RootPath.buildPath("source", "dub", "version_.d");
35 /// Path to the file containing the files to be built
36 immutable SourceListPath = RootPath.buildPath("build-files.txt");
37 /// Path at which the newly built `dub` binary will be
38 immutable DubBinPath = RootPath.buildPath("bin", "dub");
39 
40 // Flags for DMD
41 immutable OutputFlag = "-of" ~ DubBinPath;
42 immutable IncludeFlag = "-I" ~ RootPath.buildPath("source");
43 immutable DefaultDFLAGS = [ "-g", "-O", "-w" ];
44 
45 
46 /// Entry point
47 int main(string[] args)
48 {
49     // This does not have a 'proper' CLI interface, as it's only used in
50     // special cases (e.g. package maintainers can use it for bootstrapping),
51     // not for general / everyday usage by newcomer.
52     // So the following is just an heuristic / best effort approach.
53     if (args.canFind("--help", "/?", "-h"))
54     {
55         writeln("USAGE: ./build.d [compiler args (default:", DefaultDFLAGS, "]");
56         writeln();
57         writeln("  In order to build DUB, a version module must first be generated.");
58         writeln("  If the GITVER environment variable is present, it will be used to generate the version module.");
59         writeln("  Otherwise this script will look for a pre-existing version module.");
60         writeln("  If no GITVER is provided and no version module exists, `git describe` will be called");
61         writeln("  Build flags can be provided as arguments.");
62         writeln("  LDC or GDC can be used by setting the `DMD` value to " ~
63                 "`ldmd2` and `gdmd` (or their path), respectively.");
64         return 1;
65     }
66 
67     immutable dubVersion = environment.get("GITVER", "");
68     if (!writeVersionFile(dubVersion))
69         return 1;
70 
71     immutable dmd = getCompiler();
72     if (!dmd.length) return 1;
73     const dflags = args.length > 1 ? args[1 .. $] : DefaultDFLAGS;
74 
75     // Compiler says no to immutable (because it can't handle the appending)
76     const command = [
77         dmd,
78         OutputFlag, IncludeFlag,
79         "-version=DubUseCurl", "-version=DubApplication",
80         ] ~ dflags ~ [ "@build-files.txt" ];
81 
82     writeln("Building dub using ", dmd, " (dflags: ", dflags, "), this may take a while...");
83     auto proc = execute(command);
84     if (proc.status != 0)
85     {
86         writeln("Command `", command, "` failed, output was:");
87         writeln(proc.output);
88         return 1;
89     }
90 
91     writeln("DUB has been built as: ", DubBinPath);
92     version (Posix)
93         writeln("You may want to run `sudo ln -s ", DubBinPath, " /usr/local/bin` now");
94     else version (Windows)
95         writeln("You may want to add the following entry to your PATH " ~
96                 "environment variable: ", DubBinPath);
97     return 0;
98 }
99 
100 /**
101    Generate the version file describing DUB's version / commit
102 
103    Params:
104      dubVersion = User provided version file. Can be `null` / empty,
105                   in which case the existing file (if any) takes precedence,
106                   or the version is infered with `git describe`.
107                   A non-empty parameter will always override the existing file.
108  */
109 bool writeVersionFile(string dubVersion)
110 {
111     if (!dubVersion.length)
112     {
113         if (std.file.exists(VersionFilePath))
114         {
115             writeln("Using pre-existing version file. To force a rebuild, " ~
116                     "provide an explicit version (first argument) or remove: ",
117                     VersionFilePath);
118             return true;
119         }
120 
121         auto pid = execute(["git", "describe"]);
122         if (pid.status != 0)
123         {
124             writeln("Could not determine version with `git describe`. " ~
125                     "Make sure 'git' is installed and this is a git repository. " ~
126                     "Alternatively, you can provide a version explicitly via the " ~
127                     "`GITVER environment variable or pass it as the first " ~
128                     "argument to this script");
129             return false;
130         }
131         dubVersion = pid.output.strip();
132     }
133 
134     try
135     {
136         std.file.write(VersionFilePath, q{
137 /**
138    DUB version file
139 
140    This file is auto-generated by 'build.d'. DO NOT EDIT MANUALLY!
141  */
142 module dub.version_;
143 
144 enum dubVersion = "%s";
145 }.format(dubVersion));
146         writeln("Wrote version_.d` file with version: ", dubVersion);
147         return true;
148     }
149     catch (Exception e)
150     {
151         writeln("Writing version file to '", VersionFilePath, "' failed: ", e.msg);
152         return false;
153     }
154 }
155 
156 /**
157    Detect which compiler is available
158 
159    Default to DMD, then LDC (ldmd2), then GDC (gdmd).
160    If none is in the PATH, an error will be thrown.
161 
162    Note:
163      It would be optimal if we could get the path of the compiler
164      invoking this script, but AFAIK this isn't possible.
165  */
166 string getCompiler ()
167 {
168     auto env = environment.get("DMD", "");
169     // If the user asked for a compiler explicitly, respect it
170     if (env.length)
171         return env;
172 
173     static immutable Compilers = [ "dmd", "ldmd2", "gdmd" ];
174     foreach (bin; Compilers)
175     {
176         try
177         {
178             auto pid = execute([bin, "--version"]);
179             if (pid.status == 0)
180                 return bin;
181         }
182         catch (Exception e)
183             continue;
184     }
185     writeln("No compiler has been found in the PATH. Attempted values: ", Compilers);
186     writeln("Make sure one of those is in the PATH, or set the `DMD` variable");
187     return null;
188 }