xref: /PHP-7.3/win32/build/mkdist.php (revision f23bd488)
1<?php
2/* piece together a windows binary distro */
3
4$php_version = $argv[1];
5$build_dir = $argv[2];
6$php_build_dir = $argv[3];
7$phpdll = $argv[4];
8$sapi_targets = explode(" ", $argv[5]);
9$ext_targets = explode(" ", $argv[6]);
10$pecl_targets = explode(" ", $argv[7]);
11$snapshot_template = $argv[8];
12
13$is_debug = preg_match("/^debug/i", $build_dir);
14
15echo "Making dist for $build_dir\n";
16
17$dist_dir = $build_dir . "/php-" . $php_version;
18$test_dir = $build_dir . "/php-test-pack-" . $php_version;
19$pecl_dir = $build_dir . "/pecl-" . $php_version;
20
21@mkdir($dist_dir);
22@mkdir("$dist_dir/ext");
23@mkdir("$dist_dir/dev");
24@mkdir("$dist_dir/extras");
25@mkdir($pecl_dir);
26
27/* figure out additional DLL's that are required */
28$extra_dll_deps = array();
29$per_module_deps = array();
30$pecl_dll_deps = array();
31
32function get_depends($module)
33{
34	static $no_dist = array(
35		/* windows system dlls that should not be bundled */
36		'advapi32.dll', 'comdlg32.dll', 'crypt32.dll', 'gdi32.dll', 'kernel32.dll', 'ntdll.dll',
37		'odbc32.dll', 'ole32.dll', 'oleaut32.dll', 'rpcrt4.dll',
38		'shell32.dll', 'shlwapi.dll', 'user32.dll', 'ws2_32.dll', 'ws2help.dll',
39		'comctl32.dll', 'winmm.dll', 'wsock32.dll', 'winspool.drv', 'msasn1.dll',
40		'secur32.dll', 'netapi32.dll', 'dnsapi.dll', 'psapi.dll', 'normaliz.dll',
41		'iphlpapi.dll', 'bcrypt.dll',
42
43		/* apache */
44		'apachecore.dll',
45
46		/* apache 2 */
47		'libhttpd.dll', 'libapr.dll', 'libaprutil.dll','libapr-1.dll', 'libaprutil-1.dll',
48
49		/* oracle */
50		'oci.dll', 'ociw32.dll',
51
52		/* sybase */
53		'libcs.dll', 'libct.dll',
54
55		/* firebird */
56		'fbclient.dll',
57
58		/* visual C++; mscvrt.dll is present on everyones system,
59		 * but the debug version (msvcrtd.dll) and those from visual studio.net
60		 * (msvcrt7x.dll) are not */
61		'msvcrt.dll',
62		'msvcr90.dll',
63		'wldap32.dll',
64		'vcruntime140.dll',
65		'msvcp140.dll',
66		);
67	static $no_dist_re = array(
68		"api-ms-win-crt-.+\.dll",
69	);
70	global $build_dir, $extra_dll_deps, $ext_targets, $sapi_targets, $pecl_targets, $phpdll, $per_module_deps, $pecl_dll_deps;
71
72	$bd = strtolower(realpath($build_dir));
73
74	$is_pecl = in_array($module, $pecl_targets);
75
76	$cmd = "$GLOBALS[build_dir]\\deplister.exe \"$module\" \"$GLOBALS[build_dir]\"";
77	$proc = proc_open($cmd,
78			array(1 => array("pipe", "w")),
79			$pipes);
80
81	$n = 0;
82	while (($line = fgetcsv($pipes[1]))) {
83		$n++;
84
85		$dep = strtolower($line[0]);
86		$depbase = basename($dep);
87		/* ignore stuff in our build dir, but only if it is
88	     * one of our targets */
89		if (((in_array($depbase, $sapi_targets) ||
90			   	in_array($depbase, $ext_targets) || in_array($depbase, $pecl_targets)) ||
91				$depbase == $phpdll) && file_exists($GLOBALS['build_dir'] . "/$depbase")) {
92			continue;
93		}
94		/* ignore some well-known system dlls */
95		if (in_array(basename($dep), $no_dist)) {
96			continue;
97		} else {
98			$skip = false;
99			foreach ($no_dist_re as $re) {
100				if (preg_match(",$re,", basename($dep)) > 0) {
101					$skip = true;
102					break;
103				}
104			}
105			if ($skip) {
106				continue;
107			}
108		}
109
110		if ($is_pecl) {
111			if (!in_array($dep, $pecl_dll_deps)) {
112				$pecl_dll_deps[] = $dep;
113			}
114		} else {
115			if (!in_array($dep, $extra_dll_deps)) {
116				$extra_dll_deps[] = $dep;
117			}
118		}
119
120		if (!isset($per_module_deps[basename($module)]) || !in_array($dep, $per_module_deps[basename($module)])) {
121			$per_module_deps[basename($module)][] = $dep;
122			//recursively check dll dependencies
123			get_depends($dep);
124		}
125	}
126	fclose($pipes[1]);
127	proc_close($proc);
128//echo "Module $module [$n lines]\n";
129}
130
131function copy_file_list($source_dir, $dest_dir, $list)
132{
133	global $is_debug, $dist_dir;
134
135	foreach ($list as $item) {
136		if (empty($item)) {
137			continue;
138		} elseif (!is_file($source_dir . DIRECTORY_SEPARATOR . $item)) {
139			echo "WARNING: $item not found\n";
140			continue;
141		}
142
143		echo "Copying $item from $source_dir to $dest_dir\n";
144		copy($source_dir . DIRECTORY_SEPARATOR . $item, $dest_dir . DIRECTORY_SEPARATOR . $item);
145		if ($is_debug) {
146			$itemdb = preg_replace("/\.(exe|dll|lib)$/i", ".pdb", $item);
147			if (file_exists("$source_dir/$itemdb")) {
148				copy("$source_dir/$itemdb", "$dist_dir/dev/$itemdb");
149			}
150		}
151		if (preg_match("/\.(exe|dll)$/i", $item)) {
152			get_depends($source_dir . '/' . $item);
153		}
154	}
155}
156
157function copy_text_file($source, $dest)
158{
159	$text = file_get_contents($source);
160	$text = preg_replace("/(\r\n?)|\n/", "\r\n", $text);
161	$fp = fopen($dest, "w");
162	fwrite($fp, $text);
163	fclose($fp);
164}
165
166/* very light-weight function to extract a single named file from
167 * a gzipped tarball.  This makes assumptions about the files
168 * based on the PEAR info set in $packages. */
169function extract_file_from_tarball($pkg, $filename, $dest_dir) /* {{{ */
170{
171	global $packages;
172
173	$name = $pkg . '-' . $packages[$pkg];
174	$tarball = $dest_dir . "/" . $name . '.tgz';
175	$filename = $name . '/' . $filename;
176	$destfilename = $dest_dir . "/" . basename($filename);
177
178	$fp = gzopen($tarball, 'rb');
179
180	$done = false;
181	do {
182		/* read the header */
183		$hdr_data = gzread($fp, 512);
184	   	if (strlen($hdr_data) == 0)
185			break;
186		$checksum = 0;
187		for ($i = 0; $i < 148; $i++)
188			$checksum += ord($hdr_data[$i]);
189		for ($i = 148; $i < 156; $i++)
190			$checksum += 32;
191		for ($i = 156; $i < 512; $i++)
192			$checksum += ord($hdr_data[$i]);
193
194		$hdr = unpack("a100filename/a8mode/a8uid/a8gid/a12size/a12mtime/a8checksum/a1typeflag/a100link/a6magic/a2version/a32uname/a32gname/a8devmajor/a8devminor", $hdr_data);
195
196		$hdr['checksum'] = octdec(trim($hdr['checksum']));
197
198		if ($hdr['checksum'] != $checksum) {
199			echo "Checksum for $tarball $hdr[filename] is invalid\n";
200			print_r($hdr);
201			return;
202		}
203
204		$hdr['size'] = octdec(trim($hdr['size']));
205		echo "File: $hdr[filename] $hdr[size]\n";
206
207		if ($filename == $hdr['filename']) {
208			echo "Found the file we want\n";
209			$dest = fopen($destfilename, 'wb');
210			$x = stream_copy_to_stream($fp, $dest, $hdr['size']);
211			fclose($dest);
212			echo "Wrote $x bytes into $destfilename\n";
213			break;
214		}
215
216		/* skip body of the file */
217		$size = 512 * ceil((int)$hdr['size'] / 512);
218		echo "Skipping $size bytes\n";
219		gzseek($fp, gztell($fp) + $size);
220
221	} while (!$done);
222
223} /* }}} */
224
225
226/* the core dll */
227copy("$build_dir/php.exe", "$dist_dir/php.exe");
228/* copy dll and its dependencies */
229copy_file_list($build_dir, "$dist_dir", [$phpdll]);
230
231/* and the .lib goes into dev */
232$phplib = str_replace(".dll", ".lib", $phpdll);
233copy("$build_dir/$phplib", "$dist_dir/dev/$phplib");
234/* debug builds; copy the symbols too */
235if ($is_debug) {
236	$phppdb = str_replace(".dll", ".pdb", $phpdll);
237	copy("$build_dir/$phppdb", "$dist_dir/dev/$phppdb");
238}
239/* copy the sapi */
240copy_file_list($build_dir, "$dist_dir", $sapi_targets);
241
242/* copy the extensions */
243copy_file_list($build_dir, "$dist_dir/ext", $ext_targets);
244
245/* pecl sapi and extensions */
246if(sizeof($pecl_targets)) {
247	copy_file_list($build_dir, $pecl_dir, $pecl_targets);
248}
249
250/* populate reading material */
251$text_files = array(
252	"LICENSE" => "license.txt",
253	"NEWS" => "news.txt",
254	"INSTALL" => "install.txt",
255	"README.REDIST.BINS" => "readme-redist-bins.txt",
256	"php.ini-development" => "php.ini-development",
257	"php.ini-production" => "php.ini-production"
258);
259
260foreach ($text_files as $src => $dest) {
261	copy_text_file($src, $dist_dir . '/' . $dest);
262}
263
264/* general other files */
265$general_files = array(
266	"php.gif"				=>	"php.gif",
267	"$GLOBALS[build_dir]\\deplister.exe"	=>	"deplister.exe",
268);
269
270foreach ($general_files as $src => $dest) {
271	copy($src, $dist_dir . '/' . $dest);
272}
273
274/* include a snapshot identifier */
275$branch = "HEAD"; // TODO - determine this from SVN branche name
276$fp = fopen("$dist_dir/snapshot.txt", "w");
277$now = date("r");
278fwrite($fp, <<<EOT
279This snapshot was automatically generated on
280$now
281
282Version: $php_version
283Branch: $branch
284Build: $build_dir
285
286EOT
287);
288/* list build-in extensions */
289$exts = get_loaded_extensions();
290fprintf($fp, "\r\nBuilt-in Extensions\r\n");
291fwrite($fp, "===========================\r\n");
292foreach ($exts as $ext) {
293	fprintf($fp, "%s\r\n", $ext);
294}
295fwrite($fp, "\r\n\r\n");
296
297/* list dependencies */
298fprintf($fp, "Dependency information:\r\n");
299foreach ($per_module_deps as $modulename => $deps) {
300	if (in_array($modulename, $pecl_targets))
301		continue;
302
303	fprintf($fp, "Module: %s\r\n", $modulename);
304	fwrite($fp, "===========================\r\n");
305	foreach ($deps as $dll) {
306		fprintf($fp, "\t%s\r\n", basename($dll));
307	}
308	fwrite($fp, "\r\n");
309}
310fclose($fp);
311
312/* Now add those dependencies */
313foreach ($extra_dll_deps as $dll) {
314	if (!file_exists($dll)) {
315		/* try template dir */
316		$tdll = $snapshot_template . "/dlls/" . basename($dll);
317		if (!file_exists($tdll)) {
318			$tdll = $php_build_dir . '/bin/' . basename($dll);
319			if (!file_exists($tdll)) {
320				echo "WARNING: distro depends on $dll, but could not find it on your system\n";
321				continue;
322			}
323		}
324		$dll = $tdll;
325	}
326	copy($dll, "$dist_dir/" . basename($dll));
327}
328
329/* TODO:
330add sanity check and test if all required DLLs are present, per version
331This version works at least for 3.6, 3.8 and 4.0 (5.3-vc6, 5.3-vc9 and HEAD).
332Add ADD_DLLS to add extra DLLs like dynamic dependencies for standard
333deps. For example, libenchant.dll loads libenchant_myspell.dll or
334libenchant_ispell.dll
335*/
336$ENCHANT_DLLS = array(
337	array('', 'glib-2.dll'),
338	array('', 'gmodule-2.dll'),
339	array('lib/enchant', 'libenchant_myspell.dll'),
340	array('lib/enchant', 'libenchant_ispell.dll'),
341);
342foreach ($ENCHANT_DLLS as $dll) {
343	$dest  = "$dist_dir/$dll[0]";
344	$filename = $dll[1];
345
346	if (!file_exists("$dest") || !is_dir("$dest")) {
347		if (!mkdir("$dest", 0777, true)) {
348			echo "WARNING: couldn't create '$dest' for enchant plugins ";
349		}
350	}
351
352	if (!copy($php_build_dir . '/bin/' . $filename, "$dest/" . basename($filename))) {
353			echo "WARNING: couldn't copy $filename into the dist dir";
354	}
355}
356
357$SASL_DLLS = $php_build_dir . "/bin/sasl2/sasl*.dll";
358$fls = glob($SASL_DLLS);
359if (!empty($fls)) {
360	$sasl_dest_dir = "$dist_dir/sasl2";
361	if (!file_exists($sasl_dest_dir) || !is_dir($sasl_dest_dir)) {
362		if (!mkdir("$sasl_dest_dir", 0777, true)) {
363			echo "WARNING: couldn't create '$sasl_dest_dir' for SASL2 auth plugins ";
364		}
365	}
366	foreach ($fls as $fl) {
367		if (!copy($fl, "$sasl_dest_dir/" . basename($fl))) {
368			echo "WARNING: couldn't copy $fl into the $sasl_dest_dir";
369		}
370	}
371}
372
373/* and those for pecl */
374foreach ($pecl_dll_deps as $dll) {
375	if (in_array($dll, $extra_dll_deps)) {
376		/* already in main distro */
377		continue;
378	}
379	if (!file_exists($dll)) {
380		/* try template dir */
381		$tdll = $snapshot_template . "/dlls/" . basename($dll);
382		if (!file_exists($tdll)) {
383			echo "WARNING: distro depends on $dll, but could not find it on your system\n";
384			continue;
385		}
386		$dll = $tdll;
387	}
388	copy($dll, "$pecl_dir/" . basename($dll));
389}
390
391function copy_dir($source, $dest)
392{
393	if (!is_dir($dest)) {
394		if (!mkdir($dest)) {
395			return false;
396		}
397	}
398
399	$d = opendir($source);
400	while (($f = readdir($d)) !== false) {
401		if ($f == '.' || $f == '..' || $f == '.svn') {
402			continue;
403		}
404		$fs = $source . '/' . $f;
405		$fd = $dest . '/' . $f;
406		if (is_dir($fs)) {
407			copy_dir($fs, $fd);
408		} else {
409			copy($fs, $fd);
410		}
411	}
412	closedir($d);
413}
414
415
416
417function copy_test_dir($directory, $dest)
418{
419	if(substr($directory,-1) == '/') {
420		$directory = substr($directory,0,-1);
421	}
422
423	if ($directory == 'tests' || $directory == 'examples') {
424		if (!is_dir($dest . '/tests')) {
425			mkdir($dest . '/tests', 0775, true);
426		}
427		copy_dir($directory, $dest . '/tests/');
428
429		return false;
430	}
431
432	if(!file_exists($directory) || !is_dir($directory)) {
433		echo "failed... $directory\n";
434		return FALSE;
435	}
436
437	$directory_list = opendir($directory);
438
439	while (FALSE !== ($file = readdir($directory_list))) {
440		$full_path = $directory . '/' . $file;
441		if($file != '.' && $file != '..' && $file != '.svn' && is_dir($full_path)) {
442			if ($file == 'tests' || $file == 'examples') {
443				if (!is_dir($dest . '/' . $full_path)) {
444					mkdir($dest . '/' . $full_path , 0775, true);
445				}
446				copy_dir($full_path, $dest . '/' . $full_path . '/');
447				continue;
448			} else {
449				copy_test_dir($full_path, $dest);
450			}
451		}
452	}
453
454	closedir($directory_list);
455}
456
457function make_phar_dot_phar($dist_dir)
458{
459	if (!extension_loaded('phar')) {
460		return;
461	}
462
463	$path_to_phar = realpath(__DIR__ . '/../../ext/phar');
464
465	echo "Generating pharcommand.phar\n";
466	$phar = new Phar($dist_dir . '/pharcommand.phar', 0, 'pharcommand');
467
468	foreach (new DirectoryIterator($path_to_phar . '/phar') as $file) {
469		if ($file->isDir() || $file == 'phar.php') {
470			continue;
471		}
472
473		echo 'adding ', $file, "\n";
474		$phar[(string) $file] = file_get_contents($path_to_phar.  '/phar/' . $file);
475	}
476
477	$phar->setSignatureAlgorithm(Phar::SHA1);
478	$stub = file($path_to_phar . '/phar/phar.php');
479
480	unset($stub[0]); // remove hashbang
481	$phar->setStub(implode('', $stub));
482
483	echo "Creating phar.phar.bat\n";
484	file_put_contents($dist_dir . '/phar.phar.bat', "\"%~dp0php.exe\" \"%~dp0pharcommand.phar\" %*\r\n");
485}
486
487if (!is_dir($test_dir)) {
488	mkdir($test_dir);
489}
490
491$dirs = array(
492	'ext',
493	'Sapi',
494	'Zend',
495	'tests'
496);
497foreach ($dirs as $dir) {
498	copy_test_dir($dir, $test_dir);
499}
500copy('run-tests.php', $test_dir . '/run-test.php');
501
502/* change this next line to true to use good-old
503 * hand-assembled go-pear-bundle from the snapshot template */
504$use_pear_template = true;
505
506if (!$use_pear_template) {
507	/* Let's do a PEAR-less pear setup */
508	mkdir("$dist_dir/PEAR");
509	mkdir("$dist_dir/PEAR/go-pear-bundle");
510
511	/* grab the bootstrap script */
512	echo "Downloading go-pear\n";
513	copy("https://pear.php.net/go-pear.phar", "$dist_dir/PEAR/go-pear.php");
514
515	/* import the package list -- sets $packages variable */
516	include "pear/go-pear-list.php";
517
518	/* download the packages into the destination */
519	echo "Fetching packages\n";
520
521	foreach ($packages as $name => $version) {
522		$filename = "$name-$version.tgz";
523		$destfilename = "$dist_dir/PEAR/go-pear-bundle/$filename";
524		if (file_exists($destfilename))
525			continue;
526		$url = "http://pear.php.net/get/$filename";
527		echo "Downloading $name from $url\n";
528		flush();
529		copy($url, $destfilename);
530	}
531
532	echo "Download complete.  Extracting bootstrap files\n";
533
534	/* Now, we want PEAR.php, Getopt.php (Console_Getopt) and Tar.php (Archive_Tar)
535	 * broken out of the tarballs */
536	extract_file_from_tarball('PEAR', 'PEAR.php', "$dist_dir/PEAR/go-pear-bundle");
537	extract_file_from_tarball('Archive_Tar', 'Archive/Tar.php', "$dist_dir/PEAR/go-pear-bundle");
538	extract_file_from_tarball('Console_Getopt', 'Console/Getopt.php', "$dist_dir/PEAR/go-pear-bundle");
539}
540
541/* add extras from the template dir */
542if (file_exists($snapshot_template)) {
543	$items = glob("$snapshot_template/*");
544	print_r($items);
545
546	foreach ($items as $item) {
547		$bi = basename($item);
548		if (is_dir($item)) {
549			if ($bi == 'dlls' || $bi == 'symbols') {
550				continue;
551			} else if ($bi == 'PEAR') {
552				if ($use_pear_template) {
553					/* copy to top level */
554					copy_dir($item, "$dist_dir/$bi");
555				}
556			} else {
557				/* copy that dir into extras */
558				copy_dir($item, "$dist_dir/extras/$bi");
559			}
560		} else {
561			if ($bi == 'go-pear.bat') {
562				/* copy to top level */
563				copy($item, "$dist_dir/$bi");
564			} else {
565				/* copy to extras */
566				copy($item, "$dist_dir/extras/$bi");
567			}
568		}
569	}
570
571	/* copy c++ runtime */
572	$items = glob("$snapshot_template/dlls/*.CRT");
573
574	foreach ($items as $item) {
575		$bi = basename($item);
576		if (is_dir($item)) {
577			copy_dir($item, "$dist_dir/$bi");
578			copy_dir($item, "$dist_dir/ext/$bi");
579		}
580	}
581} else {
582	echo "WARNING: you don't have a snapshot template, your dist will not be complete\n";
583}
584
585make_phar_dot_phar($dist_dir);
586?>
587