1 /*
2 Copyright 2020 René Ferdinand Rivera Morell
3 Distributed under the Boost Software License, Version 1.0.
4 (See accompanying file LICENSE_1_0.txt or http://www.boost.org/LICENSE_1_0.txt)
5 */
6 
7 #include "startup.h"
8 #include "rules.h"
9 #include "frames.h"
10 #include "object.h"
11 #include "pathsys.h"
12 #include "cwd.h"
13 #include "filesys.h"
14 #include "output.h"
15 #include "variable.h"
16 
17 #include <string>
18 #include <algorithm>
19 
20 namespace
21 {
bind_builtin(char const * name_,LIST * (* f)(FRAME *,int flags),int flags,char const ** args)22     void bind_builtin(
23         char const *name_, LIST *(*f)(FRAME *, int flags),
24         int flags, char const **args)
25     {
26         FUNCTION *func;
27         OBJECT *name = object_new(name_);
28         func = function_builtin(f, flags, args);
29         new_rule_body(root_module(), name, func, 1);
30         function_free(func);
31         object_free(name);
32     }
33 } // namespace
34 
load_builtins()35 void b2::startup::load_builtins()
36 {
37     {
38         char const *args[] = {"dir", "?", 0};
39         bind_builtin("boost-build", builtin_boost_build, 0, args);
40     }
41 }
42 
builtin_boost_build(FRAME * frame,int flags)43 LIST *b2::startup::builtin_boost_build(FRAME *frame, int flags)
44 {
45     b2::jam::list dir_arg{lol_get(frame->args, 0)};
46     std::string dir;
47     if (!dir_arg.empty()) dir = b2::jam::object(*dir_arg.begin());
48 
49     b2::jam::variable dot_bootstrap_file{".bootstrap-file"};
50     if (dot_bootstrap_file)
51     {
52         err_printf(
53             "Error: Illegal attempt to re-bootstrap the build system by invoking\n"
54             "\n"
55             "   'boost-build '%s' ;\n"
56             "\n"
57             "Please consult the documentation at "
58             "'https://boostorg.github.io/build/'.\n\n",
59            dir.c_str());
60         return L0;
61     }
62 
63     // # Add the given directory to the path so we can find the build system. If
64     // # dir is empty, has no effect.
65     b2::jam::variable dot_boost_build_file{".boost-build-file"};
66     b2::jam::list dot_boost_build_file_val{static_cast<b2::jam::list>(dot_boost_build_file)};
67     std::string boost_build_jam = b2::jam::object{*dot_boost_build_file_val.begin()};
68     std::string boost_build_dir;
69     if (b2::paths::is_rooted(dir))
70         boost_build_dir = dir;
71     else
72         boost_build_dir = b2::paths::normalize(
73             std::string{boost_build_jam}+"/../"+dir);
74     b2::jam::list search_path{b2::jam::object{boost_build_dir}};
75     b2::jam::variable BOOST_BUILD_PATH{"BOOST_BUILD_PATH"};
76     search_path.append(BOOST_BUILD_PATH);
77 
78     // We set the global, and env, BOOST_BUILD_PATH so that the loading of the
79     // build system finds the initial set of modules needed for starting it up.
80     BOOST_BUILD_PATH = search_path;
81 
82     // The code that loads the rest of B2, in particular the site-config.jam
83     // and user-config.jam configuration files uses os.environ, so we need to
84     // update the value there.
85     b2::jam::variable dot_ENVIRON__BOOST_BUILD_PATH{".ENVIRON", "BOOST_BUILD_PATH"};
86     dot_ENVIRON__BOOST_BUILD_PATH = search_path;
87 
88     // # Try to find the build system bootstrap file 'bootstrap.jam'.
89     std::string bootstrap_file;
90     for (auto path: search_path)
91     {
92         std::string file = b2::jam::object{path};
93         file = b2::paths::normalize(file+"/bootstrap.jam");
94         if (b2::filesys::is_file(file))
95         {
96             bootstrap_file = file;
97             break;
98         }
99     }
100 
101     // # There is no bootstrap.jam we can find, exit with an error.
102     if (bootstrap_file.empty())
103     {
104         err_printf(
105             "Unable to load B2: could not find build system.\n"
106             "-----------------------------------------------\n"
107             "%s attempted to load the build system by invoking\n"
108             "\n"
109             "   'boost-build %s ;'\n"
110             "\n"
111             "but we were unable to find 'bootstrap.jam' in the specified directory "
112             "or in BOOST_BUILD_PATH:\n",
113             boost_build_jam.c_str(), dir.c_str());
114         for (auto path: search_path)
115         {
116             std::string file = b2::jam::object{path};
117             err_printf("    %s\n", file.c_str());
118         }
119         err_puts(
120             "Please consult the documentation at "
121             "'https://boostorg.github.io/build/'.\n\n");
122         return L0;
123     }
124 
125     // Set the bootstrap=file var as it's used by the build system to refer to
126     // the rest of the build system files.
127     dot_bootstrap_file = b2::jam::list{b2::jam::object{bootstrap_file}};
128 
129     // Show where we found it, if asked.
130     b2::jam::variable dot_OPTION__debug_configuration{".OPTION", "debug-configration"};
131     if (dot_OPTION__debug_configuration)
132     {
133         out_printf("notice: loading B2 from %s\n", bootstrap_file.c_str());
134     }
135 
136     // # Load the build system, now that we know where to start from.
137     parse_file(b2::jam::object{bootstrap_file}, frame);
138 
139     return L0;
140 }
141 
142 extern char const *saved_argv0;
143 
bootstrap(FRAME * frame)144 bool b2::startup::bootstrap(FRAME *frame)
145 {
146     b2::jam::list ARGV = b2::jam::variable{"ARGV"};
147     b2::jam::object opt_debug_configuration{"--debug-configuration"};
148     b2::jam::variable dot_OPTION__debug_configuration{".OPTION", "debug-configration"};
149     for (auto arg: ARGV)
150     {
151         if (opt_debug_configuration == arg)
152         {
153             dot_OPTION__debug_configuration = b2::jam::list{b2::jam::object{"true"}};
154             break;
155         }
156     }
157 
158     const std::string b2_exe_path{executable_path(saved_argv0)};
159     const std::string boost_build_jam{"boost-build.jam"};
160     std::string b2_file_path;
161 
162     // Attempt to find the `boost-build.jam` boot file in work directory tree.
163     if (b2_file_path.empty())
164     {
165         std::string work_dir{b2::paths::normalize(b2::cwd_str()) + "/"};
166         while (b2_file_path.empty() && !work_dir.empty())
167         {
168             if (b2::filesys::is_file(work_dir + boost_build_jam))
169                 b2_file_path = work_dir + boost_build_jam;
170             else if (work_dir.length() == 1 && work_dir[0] == '/')
171                 work_dir.clear();
172             else
173             {
174                 auto parent_pos = work_dir.rfind('/', work_dir.length() - 2);
175                 if (parent_pos != std::string::npos)
176                     work_dir.erase(parent_pos + 1);
177                 else
178                     work_dir.clear();
179             }
180         }
181     }
182 
183     // Check relative to the executable for portable install location.
184     if (b2_file_path.empty())
185     {
186         const std::string path{
187             b2::paths::normalize(
188                 b2_exe_path + "/../.b2/kernel/" + boost_build_jam)};
189         if (b2::filesys::is_file(path))
190             b2_file_path = path;
191     }
192 
193     // Check relative to the executable for portable install location.
194     if (b2_file_path.empty())
195     {
196         const std::string path{
197             b2::paths::normalize(
198                 b2_exe_path + "/../../share/boost-build/" + boost_build_jam)};
199         if (b2::filesys::is_file(path))
200             b2_file_path = path;
201     }
202 
203     // Check the BOOST_BUILD_PATH paths.
204     if (b2_file_path.empty())
205     {
206         b2::jam::list BOOST_BUILD_PATH = b2::jam::variable{"BOOST_BUILD_PATH"};
207         for (auto search_path: BOOST_BUILD_PATH)
208         {
209             std::string path = b2::jam::object{search_path};
210             path = b2::paths::normalize(path+"/"+boost_build_jam);
211             if (b2::filesys::is_file(path))
212             {
213                 b2_file_path = path;
214                 break;
215             }
216         }
217     }
218 
219     // Indicate a load failure when we can't find the build file.
220     if (b2_file_path.empty())
221     {
222         const char * not_found_error =
223             "Unable to load B2: could not find 'boost-build.jam'\n"
224             "---------------------------------------------------\n"
225             "Attempted search from '%s' up to the root "
226             "at '%s'\n"
227             "Please consult the documentation at "
228             "'https://boostorg.github.io/build/'.\n\n";
229         err_printf(not_found_error, b2::cwd_str().c_str(), b2_exe_path.c_str());
230         return false;
231     }
232 
233     // Show where we found it, if asked.
234     if (dot_OPTION__debug_configuration)
235     {
236         out_printf("notice: found boost-build.jam at %s\n", b2_file_path.c_str());
237     }
238 
239     // Load the build system bootstrap file we found. But check we did that.
240     b2::jam::variable dot_boost_build_file{".boost-build-file"};
241     dot_boost_build_file = b2_file_path;
242     b2::jam::object b2_file_path_sym{b2_file_path};
243     parse_file(b2_file_path_sym, frame);
244     b2::jam::list dot_dot_bootstrap_file_val = b2::jam::variable{".bootstrap-file"};
245     if (dot_dot_bootstrap_file_val.empty())
246     {
247         err_printf(
248             "Unable to load B2\n"
249             "-----------------\n"
250             "'%s' was found by searching from %s up to the root.\n"
251             "\n"
252             "However, it failed to call the 'boost-build' rule to indicate "
253             "the location of the build system.\n"
254             "\n"
255             "Please consult the documentation at "
256             "'https://boostorg.github.io/build/'.\n\n",
257             b2_file_path.c_str(), b2::cwd_str().c_str());
258         return false;
259     }
260 
261     return true;
262 }
263